// Scrape users in your Office 365 organisation // github.com/smcclennon/ous // Delete all variables created in this block once execution finishes (async () => { // This needs to be replaced with a valid BaseFolderId or the API request will fail // How to obtain a BaseFolderId: https://github.com/smcclennon/ous#how-to-get-a-basefolderid const base_folder_id = "a000a000-0aa0-0a0a-aa00-a000a0000a0a" // See bottom of the file for the csv export filename // Get date and time. Used for filename when saving .csv function getDateTime() { const year = new Date().getFullYear(); const month = new Date().getMonth() + 1; //0-11 + 1 const day = new Date().getDay(); const date = year + '-' + month + '-' + day; const hours = new Date().getHours(); const minutes = new Date().getMinutes(); const seconds = new Date().getSeconds(); const time = hours + '-' + minutes + '-' + seconds; // 2022-5-2_10-42-40 return date + '_' + time; } // Get a cookie from the browser. Used to get the x-owa-canary authentication cookie // https://www.tabnine.com/academy/javascript/how-to-get-cookies/ function getCookie(cName) { const name = cName + "="; const cDecoded = decodeURIComponent(document.cookie); //to be careful const cArr = cDecoded.split('; '); let res; cArr.forEach(val => { if (val.indexOf(name) === 0) res = val.substring(name.length); }) return res } // Download text to a file // https://stackoverflow.com/a/47359002 function saveAs(text, filename){ var pom = document.createElement('a'); pom.setAttribute('href', 'data:text/plain;charset=urf-8,'+encodeURIComponent(text)); pom.setAttribute('download', filename); pom.click(); }; // Convert 2d array into comma separated values // https://stackoverflow.com/a/14966131 function convertToCsv(rows) { //let csvContent = "data:text/csv;charset=utf-8,"; let csvContent = ""; rows.forEach(function(rowArray) { let row = rowArray.join(","); csvContent += row + "\r\n"; }); return csvContent; } // Reverse engineered API call to retrieve an array of users in an Outlook directory // Example AddressListId: "a000a000-0aa0-0a0a-aa00-a000a0000a0a" // Example Offset: "300" // Example MaxEntriesReturned: "100" async function getUsersFromAddressList(BaseFolderId, Offset, MaxEntriesReturned, x_owa_canary) { console.log('Performing API request...') const response = await fetch("https://outlook.office.com/owa/service.svc?action=FindPeople&app=People", { "credentials": "include", "headers": { "User-Agent": "Mozilla/5.0 (X11; U; Linux x86_64; en-US) Gecko/20072401 Firefox/98.0", //"Accept": "*/*", "Accept-Language": "en-US,en;q=0.5", "action": "FindPeople", "content-type": "application/json; charset=utf-8", //"ms-cv": "", "prefer": "exchange.behavior=\"IncludeThirdPartyOnlineMeetingProviders\"", "x-owa-canary": x_owa_canary, //"x-owa-correlationid": "", //"x-owa-sessionid": "", // For a decoded version of x-owa-urlpostdata, please see: https://github.com/smcclennon/ous#x-owa-urlpostdata-decoded "x-owa-urlpostdata": "%7B%22__type%22%3A%22FindPeopleJsonRequest%3A%23Exchange%22%2C%22Header%22%3A%7B%22__type%22%3A%22JsonRequestHeaders%3A%23Exchange%22%2C%22RequestServerVersion%22%3A%22V2018_01_08%22%2C%22TimeZoneContext%22%3A%7B%22__type%22%3A%22TimeZoneContext%3A%23Exchange%22%2C%22TimeZoneDefinition%22%3A%7B%22__type%22%3A%22TimeZoneDefinitionType%3A%23Exchange%22%2C%22Id%22%3A%22GMT%20Standard%20Time%22%7D%7D%7D%2C%22Body%22%3A%7B%22IndexedPageItemView%22%3A%7B%22__type%22%3A%22IndexedPageView%3A%23Exchange%22%2C%22BasePoint%22%3A%22Beginning%22%2C%22Offset%22%3A"+Offset+"%2C%22MaxEntriesReturned%22%3A"+MaxEntriesReturned+"%7D%2C%22QueryString%22%3Anull%2C%22ParentFolderId%22%3A%7B%22__type%22%3A%22TargetFolderId%3A%23Exchange%22%2C%22BaseFolderId%22%3A%7B%22__type%22%3A%22AddressListId%3A%23Exchange%22%2C%22Id%22%3A%22"+BaseFolderId+"%22%7D%7D%2C%22PersonaShape%22%3A%7B%22__type%22%3A%22PersonaResponseShape%3A%23Exchange%22%2C%22BaseShape%22%3A%22Default%22%2C%22AdditionalProperties%22%3A%5B%7B%22__type%22%3A%22PropertyUri%3A%23Exchange%22%2C%22FieldURI%22%3A%22PersonaAttributions%22%7D%2C%7B%22__type%22%3A%22PropertyUri%3A%23Exchange%22%2C%22FieldURI%22%3A%22PersonaTitle%22%7D%2C%7B%22__type%22%3A%22PropertyUri%3A%23Exchange%22%2C%22FieldURI%22%3A%22PersonaOfficeLocations%22%7D%5D%7D%2C%22ShouldResolveOneOffEmailAddress%22%3Afalse%2C%22SearchPeopleSuggestionIndex%22%3Afalse%7D%7D", //"x-req-source": "People", //"Sec-Fetch-Dest": "empty", //"Sec-Fetch-Mode": "cors", //"Sec-Fetch-Site": "same-origin", //"Sec-GPC": "1", "Pragma": "no-cache", "Cache-Control": "no-cache" }, "method": "POST", //"mode": "cors" }) .then(data => data.json()); // Array(143) [ {…}, {…}, {…} … ] let users = response.Body.ResultSet; return users } // Get x-owa-canary console.debug('Getting x-owa-canary cookie...') const canary = getCookie("X-OWA-CANARY"); if (canary == undefined) { throw "Couldn't retrieve x-owa-canary from your cookies! Please make sure you run this code on a console window for https://outlook.office.com and that you are logged in, then try again." } else { console.debug('Using x-owa-canary: ' + canary); } // Store all extracted user data // [[id1, John Smith, jsmith@example.com], [id2, Foo Bar, fbar@example.com]] // Initiate with field names for when user_db is converted into csv format const user_db = [['PersonaId', 'DisplayName', 'EmailAddress']]; // Get all users const users = await getUsersFromAddressList( base_folder_id, "0", "1000", canary) .catch(e => { // API declined our request const error_description = "API Request failed. Please check your 'x-owa-canary' is correct and valid.\n\nWe automatically collected this from your cookies, so try logging out and logging back into https://outlook.office.com.\n\ncanary = " + canary + '\n\nAPI request/response error:\n' + e; throw error_description; } ); // API accepted our request and responsed console.debug("API request successful!"); // If API response contained no users if (users == null | users.length == 0) { const error_description = "API Request returned no users. Please check your 'BaseFolderId' is valid. You can find this at the top of the program:\nbase_folder_id = " + base_folder_id + '\n\nHow to obtain a BaseFolderId: https://github.com/smcclennon/ous#how-to-get-a-basefolderid\n\nIt is also possible that the user directory you collected the BaseFolderId for is empty and contains no users. If this is the case, please try using the BaseFolderId for a user directory containing at least 1 user and try again.'; throw error_description; } else { console.log('Retrieved ' + users.length + ' users!'); } // Iterate through all users for (let index = 0; index < users.length; index++) { console.debug('\nAccessing user at index: ' + index) // Extract information from user API data let user = users[index]; let displayname = user.DisplayName; let emailaddress = user.EmailAddress.EmailAddress let personaid = user.PersonaId.Id; // Compile extracted information into an array let userdata = [personaid, displayname, emailaddress]; // Save compiled user information user_db.push(userdata); console.debug('New user: ' + userdata); } // Print user_db array to console console.debug('\nUser array:') console.debug(user_db); // .csv export filename let export_filename = "office365_export"; export_filename += "__"; export_filename += base_folder_id; // Add a000a000-0aa0-0a0a-aa00-a000a0000a0a export_filename += "__"; export_filename += getDateTime(); // Add 2022-5-2_10-42-40 export_filename += '.csv'; // Add file extension // Final: office365_export__a000a000-0aa0-0a0a-aa00-a000a0000a0a__2022-5-2_10-42-40.csv // Download database as a .csv file console.debug('Converting user array to csv...') let user_db_csv = convertToCsv(user_db); console.debug('Downloading csv...') saveAs(user_db_csv, export_filename); console.log('Downloaded results to "'+ export_filename+'"!'); })();