Skip to content

Instantly share code, notes, and snippets.

@panreel
Last active October 22, 2020 13:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save panreel/d09cb6b1861a6f42f9f8929552517598 to your computer and use it in GitHub Desktop.
Save panreel/d09cb6b1861a6f42f9f8929552517598 to your computer and use it in GitHub Desktop.
Export Users from a Workplace instance by using SCIM API

Export Workplace users via SCIM API

This PowerShell script allows to export the users of a Workplace instance by using SCIM API.

Setup

  • Create a new Custom Integration in the Workplace Admin Panel: Create a custom Integration.
    This requires at least "Manage Accounts" permissions. Take note of the Access Token.

  • Create a file named accessToken.js with the following content:

    {
          "accessToken" : "YOUR-ACCESS-TOKEN"
    }

Run

  • Run the script by passing the accessToken.js file as input:

    ./exportUsersSCIM.ps1 -WPAccessToken accessToken.js -$SCIMPageSize 100 -WPExportedUsers ./PATHTOEXPORTFILE-XLSX

    Here are the details of the passed params:

    Parameter Description Type Required
    WPAccessToken The path for the JSON file with the access token String Yes
    ParallelGrade The # of threads on which the export process will span Int No
  • A file named workplace_employees_info_[yyyy-mm-dd-HH_mm].xlsx will be created in the same folder where your run the script. It will populate some columns with values from the SCIM API calls (Full Name, Email, User Id, Job Title, Department, Division, Status, Claimed, Claimed Date, Invited, Invited Date, Manager Employee ID, Manager Full Name)

param(
[Parameter(Mandatory=$true, HelpMessage='Path for your Workplace access token in .json format {"accessToken" : 123xyz}')] [string]$WPAccessToken,
[Parameter(Mandatory=$false, HelpMessage='Page size for SCIM requests. Defaults to 100.')] [int]$SCIMPageSize = 100
)
$defJobs = {
$origin = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0
function Get-Status {
If(-Not $_.active) {return "Deactivated"}
Else {
If($_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".invited -eq $False) {return "Not Invited"}
Else {
If($_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".claimed -eq $False) {return "Invited"}
Else {return "Claimed"}
}
}
}
function Get-Timestamp ($UnixDate, $origin) {
If(-Not $UnixDate -Or ($UnixDate -Eq 0)) {return $null}
Else {return Get-Date -Date $origin.AddSeconds($UnixDate) -Format s}
}
function Call-SCIM-X-Pages ($startIndex, $pageNumber, $SCIMPageSize, $token, $filename) {
$pagesResult = @()
For($i = 0; $i -lt $pageNumber; $i++){
$offset = $startIndex + $i * $SCIMPageSize
#$count = $offset + $SCIMPageSize - 1
$next = "https://www.facebook.com/scim/v1/Users?startIndex=$offset&count=$SCIMPageSize"
#Write-Host "Calling GET $next"
#Write-Host "Getting users from $offset to $count..."
$retries = 1
$SCIMSuccess = $False
do {
try {
$results = Invoke-RestMethod -Uri ($next) -Headers @{Authorization = "Bearer " + $token}
$SCIMSuccess = $True
#Write-Host $results.itemsPerPage
If($results.Resources){
$pageUsers = $results.Resources | ForEach-Object -Process {$_} | `
Select-Object -property `
@{N='Full Name';E={$_.name.formatted}}, `
@{N='Email';E={$_.username}}, `
@{N='User Id';E={$_.id}}, `
@{N='Job Title';E={$_.title}}, `
@{N='Department';E={$_."urn:scim:schemas:extension:enterprise:1.0".department}}, `
@{N='Division';E={$_."urn:scim:schemas:extension:enterprise:1.0".division}}, `
@{N='Status';E={$_ | Get-Status}}, `
@{N='Claimed';E={$_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".claimed}}, `
@{N='Claimed Date';E={Get-Timestamp $_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".claimDate $origin}}, `
@{N='Invited';E={$_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".invited}}, `
@{N='Invited Date';E={Get-Timestamp $_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".inviteDate $origin}}, `
@{N='Manager Employee ID';E={$_."urn:scim:schemas:extension:enterprise:1.0".manager.managerID}}, `
@{N='Manager Full Name';E={$_."urn:scim:schemas:extension:enterprise:1.0".manager.displayName}}
$pagesResult += $pageUsers
}
} catch {
#Handle exception when having errors from SCIM API
Write-Host -NoNewLine -ForegroundColor Red "Error when getting users from API ($next)"
Write-Host " - Retries left: $retries"
$retries--
}
} While ($retries -ge 0 -and $SCIMSuccess -eq $False)
}
return $pagesResult
}
}
#Read JSON Access Token
try {
$global:token = (Get-Content $WPAccessToken | Out-String | ConvertFrom-Json -ErrorAction Stop).accessToken
Write-Host -NoNewLine "Access Token JSON File: "
Write-Host -ForegroundColor Green "OK, Read!"
}
catch {
#Handle exception when passed file is not JSON
Write-Host -ForegroundColor Red "Fatal Error when reading JSON file. Is it correctly formatted? {'accessToken' : 123xyz}"
exit;
}
#Setup multi-threading options
$jobs = @()
$maxparal = 8
$totalUsers = [int](Invoke-RestMethod -Uri "https://www.facebook.com/scim/v1/Users" -Headers @{Authorization = "Bearer " + $global:token}).totalResults
$paral = [math]::Min($maxparal, [math]::Ceiling($totalUsers/$SCIMPageSize))
$pages = [math]::Ceiling($totalUsers/$SCIMPageSize)
#Define filename
$dt = (Get-Date).toString("yyyy-mm-dd-HH_mm", $cultureENUS)
$path = (Get-Location).Path
$filename = "$path/workplace_employees_info_$dt.csv"
Write-Host "We're going to export $totalUsers users with $paral jobs to $filename"
$start = (Get-Date)
For($i = 0; $i -lt $paral; $i++) {
$step = ([math]::Floor($pages/$paral))
$jobs += [PSCustomObject]@{
"JobName" = "SCIM-Job-$i"
"StartIndex" = 1 + $i * $step * $SCIMPageSize
"PageNum" = @($step,($step + ($pages % $paral)))[$i -eq ($paral-1)]
"PageSize" = $SCIMPageSize
"AuthToken" = $global:token
"FileName" = $filename
}
#Write-Host $jobs[$i]
}
For($i = 0; $i -lt $paral; $i++) {
Start-Job -Name $jobs[$i].JobName -InitializationScript $defJobs -ScriptBlock {
param($job)
#Write-Host $job.JobName
Call-SCIM-X-Pages $job.StartIndex $job.PageNum $job.PageSize $job.AuthToken $job.FileName
} -ArgumentList $jobs[$i]
}
Get-Job | Wait-Job
Get-Job | Receive-Job | Select-Object "Full Name", "Email", "User Id", "Job Title", "Department", "Division", "Status", `
"Claimed", "Claimed Date", "Invited", "Invited Date", "Manager Employee ID", "Manager Full Name", `
"Active Last Month", "Active Last Week", "Active Last Day", `
"Active Last Month on Workplace Mobile App", "Active Last Week on Workplace Mobile App", "Active Last Day on Workplace Mobile App", `
"Active Last Month on Work Chat Mobile App", "Active Last Week on Work Chat Mobile App", "Active Last Day on Work Chat Mobile App", `
"Messages Last Month", "Messages Last Week", "Messages Last Day", `
"Posts Last Month", "Posts Last Week", "Posts Last Day", `
"Comments Last Month", "Comments Last Week", "Comments Last Day", `
"Contributor Rank Last Month", "Contributor Rank Last Week", "Contributor Rank Last Day" | `
Export-Csv -Path $filename -NoTypeInformation
Write-Host -NoNewLine "`nUsers written to csv: "
Write-Host -ForegroundColor Green "OK, Written!"
$elapsed = (Get-Date)-$start
$formatelapsed = "{0:HH:mm:ss}" -f ([datetime]$elapsed.Ticks)
Write-Host -NoNewLine -ForegroundColor Yellow "`nTotal time: $formatelapsed"
Get-Job | Remove-Job
param(
[Parameter(Mandatory=$true, HelpMessage='Path for your Workplace access token in .json format {"accessToken" : 123xyz}')] [string]$WPAccessToken,
[Parameter(Mandatory=$false, HelpMessage='The # of threads on which the export process will span')] [int]$ParallelGrade = 8
)
#Define vars
$SCIMPageSize = 100
$defJobs = {
$origin = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0
function Get-Status {
If(-Not $_.active) {return "Deactivated"}
Else {
If($_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".invited -eq $False) {return "Not Invited"}
Else {
If($_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".claimed -eq $False) {return "Invited"}
Else {return "Claimed"}
}
}
}
function Get-Timestamp ($UnixDate, $origin) {
If(-Not $UnixDate -Or ($UnixDate -Eq 0)) {return $null}
Else {return Get-Date -Date $origin.AddSeconds($UnixDate) -Format s}
}
function Call-SCIM-X-Pages ($startIndex, $pageNumber, $SCIMPageSize, $token, $filename) {
$pagesResult = @()
For($i = 0; $i -lt $pageNumber; $i++){
try {
$offset = $startIndex + $i * $SCIMPageSize
#$count = $offset + $SCIMPageSize - 1
$next = "https://www.facebook.com/scim/v1/Users?startIndex=$offset&count=$SCIMPageSize"
#Write-Host "Calling GET $next"
#Write-Host "Getting users from $offset to $count..."
$results = Invoke-RestMethod -Uri ($next) -Headers @{Authorization = "Bearer " + $token}
#Write-Host $results.itemsPerPage
If($results.Resources){
$pageUsers = $results.Resources | ForEach-Object -Process {$_} | `
Select-Object -property `
@{N='Full Name';E={$_.name.formatted}}, `
@{N='Email';E={$_.username}}, `
@{N='User Id';E={$_.id}}, `
@{N='Job Title';E={$_.title}}, `
@{N='Department';E={$_."urn:scim:schemas:extension:enterprise:1.0".department}}, `
@{N='Division';E={$_."urn:scim:schemas:extension:enterprise:1.0".division}}, `
@{N='Status';E={$_ | Get-Status}}, `
@{N='Claimed';E={$_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".claimed}}, `
@{N='Claimed Date';E={Get-Timestamp $_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".claimDate $origin}}, `
@{N='Invited';E={$_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".invited}}, `
@{N='Invited Date';E={Get-Timestamp $_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".inviteDate $origin}}, `
@{N='Manager Employee ID';E={$_."urn:scim:schemas:extension:enterprise:1.0".manager.managerID}}, `
@{N='Manager Full Name';E={$_."urn:scim:schemas:extension:enterprise:1.0".manager.displayName}}
$pagesResult += $pageUsers
}
} catch {
#Handle exception when having errors from SCIM API
Write-Host -ForegroundColor Red "Error when getting users from API ($next)"
}
}
return $pagesResult
}
}
#Read JSON Access Token
try {
$global:token = (Get-Content $WPAccessToken | Out-String | ConvertFrom-Json -ErrorAction Stop).accessToken
Write-Host -NoNewLine "Access Token JSON File: "
Write-Host -ForegroundColor Green "OK, Read!"
}
catch {
#Handle exception when passed file is not JSON
Write-Host -ForegroundColor Red "Fatal Error when reading JSON file. Is it correctly formatted? {'accessToken' : 123xyz}"
exit;
}
#Setup multi-threading options
$jobs = @()
$totalUsers = [int](Invoke-RestMethod -Uri "https://www.facebook.com/scim/v1/Users" -Headers @{Authorization = "Bearer " + $global:token}).totalResults
$paral = [math]::Min($ParallelGrade, [math]::Ceiling($totalUsers/$SCIMPageSize))
$pages = [math]::Ceiling($totalUsers/$SCIMPageSize)
#Define filename
$dt = (Get-Date).toString("yyyy-MM-dd-HH_mm", $cultureENUS)
$path = (Get-Location).Path
$filename = "$path/workplace_employees_info_$dt.csv"
Write-Host "We're going to export $totalUsers users with $paral jobs to $filename"
$start = (Get-Date)
For($i = 0; $i -lt $paral; $i++) {
$step = ([math]::Floor($pages/$paral))
$jobs += [PSCustomObject]@{
"JobName" = "SCIM-Job-$i"
"StartIndex" = 1 + $i * $step * $SCIMPageSize
"PageNum" = @($step,($step + ($pages % $paral)))[$i -eq ($paral-1)]
"PageSize" = $SCIMPageSize
"AuthToken" = $global:token
"FileName" = $filename
}
#Write-Host $jobs[$i]
}
For($i = 0; $i -lt $paral; $i++) {
Start-Job -Name $jobs[$i].JobName -InitializationScript $defJobs -ScriptBlock {
param($job)
#Write-Host $job.JobName
Call-SCIM-X-Pages $job.StartIndex $job.PageNum $job.PageSize $job.AuthToken $job.FileName
} -ArgumentList $jobs[$i]
}
Get-Job | Wait-Job
Get-Job | Receive-Job | Export-Csv -Path $filename -NoTypeInformation
Write-Host -NoNewLine "`nUsers written to csv: "
Write-Host -ForegroundColor Green "OK, Written!"
$elapsed = (Get-Date)-$start
$formatelapsed = "{0:HH:mm:ss}" -f ([datetime]$elapsed.Ticks)
Write-Host -NoNewLine -ForegroundColor Yellow "`nTotal time: $formatelapsed"
Get-Job | Remove-Job
param(
[Parameter(Mandatory=$true, HelpMessage='Path for your Workplace access token in .json format {"accessToken" : 123xyz}')] [string]$WPAccessToken,
[Parameter(Mandatory=$false, HelpMessage='Path for the XSLSX export of your Workplace users')] [string]$WPExportedUsers,
[Parameter(Mandatory=$false, HelpMessage='Page size for SCIM requests. Defaults to 100.')] [int]$SCIMPageSize = 100
)
function Get-Status {
If(-Not $_.active) {return "Deactivated"}
Else {
If($_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".invited -eq $False) {return "Not Invited"}
Else {
If($_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".claimed -eq $False) {return "Invited"}
Else {return "Claimed"}
}
}
}
function Get-Timestamp ($UnixDate) {
$origin = New-Object -Type DateTime -ArgumentList 1970, 1, 1, 0, 0, 0, 0
If(-Not $UnixDate -Or ($UnixDate -Eq 0)) {return $null}
Else {return Get-Date -Date $origin.AddSeconds($UnixDate) -Format s}
}
#Install ImportExcel Module
If(!(Get-module ImportExcel)){Install-Module ImportExcel -scope CurrentUser}
#Read JSON Access Token
try {
$global:token = (Get-Content $WPAccessToken | Out-String | ConvertFrom-Json -ErrorAction Stop).accessToken
Write-Host -NoNewLine "Access Token JSON File: "
Write-Host -ForegroundColor Green "OK, Read!"
}
catch {
#Handle exception when passed file is not JSON
Write-Host -ForegroundColor Red "Fatal Error when reading JSON file. Is it correctly formatted? {'accessToken' : 123xyz}"
exit;
}
#Read users from XLSX export file
If($WPExportedUsers) {
try {
$global:exportedUsers = Import-Excel -Path $WPExportedUsers
Write-Host -NoNewLine "Workplace Users Export File: "
Write-Host -ForegroundColor Green "OK, Read!"
#Install Join-Object Module
Install-Module -Name Join-Object -Scope CurrentUser
} catch {
#Handle exception when unable to read file
Write-Host -ForegroundColor Red "Fatal Error when reading XLSX file. Is it the Workplace users export file?"
exit;
}
}
#Get users in a WP instance by using SCIM API
$startIndex = 1
$global:users = @()
$next = "https://www.facebook.com/scim/v1/Users?startIndex=$startIndex&count=$SCIMPageSize"
do {
try {
$count = $startIndex + $SCIMPageSize - 1
Write-Host "Calling GET $next"
Write-Host "Getting users from $startIndex to $count..."
$results = Invoke-RestMethod -Uri ($next) -Headers @{Authorization = "Bearer " + $global:token}
if ($results.startIndex -le $results.totalResults) {
$global:users += $results.Resources
If($results.itemsPerPage -eq 0) {$startIndex++}
Else {$startIndex += $results.itemsPerPage}
$next = "https://www.facebook.com/scim/v1/Users?startIndex=$startIndex&count=$SCIMPageSize"
}
else {$next = $null}
} catch {
#Handle exception when having errors from SCIM API
Write-Host -ForegroundColor Red "Error when getting users from API ($next)"
}
} while($next)
#Add members to XLSX
try {
#Get date
$dt = (Get-Date).toString("yyyy-mm-dd-HH_mm", $cultureENUS)
#Format property names
$filteredUsers = $global:users | `
ForEach-Object -Process {$_} | `
Select-Object -property `
@{N='Full Name';E={$_.name.formatted}}, `
@{N='Email';E={$_.username}}, `
@{N='User Id';E={[string]$_.id}}, `
@{N='Job Title';E={$_.title}}, `
@{N='Department';E={$_."urn:scim:schemas:extension:enterprise:1.0".department}}, `
@{N='Division';E={$_."urn:scim:schemas:extension:enterprise:1.0".division}}, `
@{N='Status';E={$_ | Get-Status}}, `
@{N='Claimed';E={$_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".claimed}}, `
@{N='Claimed Date';E={Get-Timestamp $_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".claimDate}}, `
@{N='Invited';E={$_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".invited}}, `
@{N='Invited Date';E={Get-Timestamp $_."urn:scim:schemas:extension:facebook:accountstatusdetails:1.0".inviteDate}}, `
@{N='Manager Employee ID';E={[string]$_."urn:scim:schemas:extension:enterprise:1.0".manager.managerID}}, `
@{N='Manager Full Name';E={$_."urn:scim:schemas:extension:enterprise:1.0".manager.displayName}}
If($WPExportedUsers){
Join-Object `
-Left $filteredUsers `
-Right $global:exportedUsers `
-LeftJoinProperty 'User Id' `
-RightJoinProperty 'User Id' `
-Type AllInLeft `
-RightProperties "Activity Summary for Date", `
"Active Last Month", "Active Last Week", "Active Last Day", `
"Active Last Month on Workplace Mobile App", "Active Last Week on Workplace Mobile App", "Active Last Day on Workplace Mobile App", `
"Active Last Month on Work Chat Mobile App", "Active Last Week on Work Chat Mobile App", "Active Last Day on Work Chat Mobile App", `
"Messages Last Month", "Messages Last Week", "Messages Last Day", `
"Posts Last Month", "Posts Last Week", "Posts Last Day", `
"Comments Last Month", "Comments Last Week", "Comments Last Day", `
"Contributor Rank Last Month", "Contributor Rank Last Week", "Contributor Rank Last Day" | `
Export-Excel "./workplace_employees_info_$dt.xlsx" -NoNumberConversion *
}
Else {
$filteredUsers | Export-Excel "./workplace_employees_info_$dt.xlsx" -NoNumberConversion *
}
Write-Host -NoNewLine "Users written to XLSX: "
Write-Host -ForegroundColor Green "OK, Written!"
} catch {
#Handle exception when writing to output XLSX
Write-Host -ForegroundColor Red "Fatal Error when writing to XLSX file!"
exit;
}
@10mnor
Copy link

10mnor commented Oct 22, 2020

Hi,
I don't know if you are till active here, but hope to hear from you.
First, thank you for this post. This has helped me.
However, I am intresting in knowing if I it is possible to add on this code to collect more information.

The case:
On workplace, I know I can export a excel file of users containing insight such as "Active last month", "Active Last Week" etc.
In the code provided above, i want to add to collect the rest of the information.

Do you have a code or can help me where I can find what I am looking for. I am new to code and hope to hear from you.

Tim

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment