Last active
March 14, 2025 21:10
-
-
Save jschlackman/2972f5eb315c66e00482fd8802e4c957 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
Assigns an imported list of users to an Okta application using the Okta API. | |
.DESCRIPTION | |
Imports a list of users from a CSV file, matches it against existing Okta users, and sets application assignments for each user in a specified application. | |
Author: James Schlackman <[email protected]> | |
Last Modified: March 14 2025 | |
.PARAMETER ImportPath | |
Path to CSV file with list of users to assign. | |
.PARAMETER AppName | |
Name of Okta application to assign users to. If more than 1 application matches the specified name, a list will be presented for selection. | |
.PARAMETER UserLoginField | |
Name of the field in the CSV file that contains the user login to match against in Okta. | |
.PARAMETER UserLoginFilter | |
Regex expression to filter user logins from the import CSV. | |
.PARAMETER MatchSecondEmail | |
Attempt to match users based on their second email field if unable to match on other fields. | |
.PARAMETER MatchProxyAddresses | |
Attempt to match users based on their proxyAddresses field if unable to match on other fields. | |
.PARAMETER SlowDown | |
Seconds to wait between fast API calls during bulk operations. Used to avoid rate limiting. | |
.PARAMETER ApiBaseUrl | |
Base URL of the target Okta tenant, e.g. https://example.okta.com | |
.PARAMETER ApiClientId | |
Client ID of the configured Okta PowerShell app in the target Okta tenant. | |
See https://developer.okta.com/docs/guides/device-authorization-grant/main/#configure-an-application-to-use-the-device-authorization-grant | |
.NOTES | |
Assignments may fail if the application has provisioning features enabled as this script does not support adding a user assignment role. | |
.LINK | |
https://github.com/okta/okta-powershell-cli | |
#> | |
#Requires -Modules Okta.PowerShell | |
Param( | |
[Parameter(Mandatory)] [String] $ImportPath, | |
[Parameter()] [String] $AppName, | |
[Parameter()] [String] $UserLoginField, | |
[Parameter()] [String] $UserLoginFilter, | |
[Parameter()] [Switch] $MatchSecondEmail, | |
[Parameter()] [Switch] $MatchProxyAddresses, | |
[Parameter()] [ValidateRange('Positive')] [Int] $SlowDown = 1, | |
[Parameter()] [ValidateNotNullorEmpty()] [String] $ApiBaseUrl = $env:OKTA_PS_ORG_URL, | |
[Parameter()] [ValidateNotNullorEmpty()] [String] $ApiClientId = $env:OKTA_PS_CLIENT_ID | |
) | |
<# | |
.SYNOPSIS | |
Retrieves all pages of data for a specified Okta API call | |
.DESCRIPTION | |
Makes a specified Okta API call and checks for pagination, calling each subsequent page if necessary. | |
.PARAMETER CommandName | |
Okta API command to run. | |
.PARAMETER Arguments | |
Additional arguments for the API command. | |
#> | |
function Get-OktaPagedResult { | |
Param( | |
[Parameter(Mandatory)] [String] $CommandName, | |
[Parameter()] [HashTable] $Arguments | |
) | |
# Validate we are running a valid Okta Powershell command | |
If (Get-Command -Name $CommandName -Module 'Okta.PowerShell') { | |
$apiResult = & $CommandName @Arguments -WithHttpInfo | |
$apiResponse = $apiResult.Response | |
while ($apiResult.NextPageUri) { | |
$apiResult = & $CommandName -WithHttpInfo -Uri $apiResult.NextPageUri | |
$apiResponse += $apiResult.Response | |
} | |
Return $apiResponse | |
} Else { | |
Write-Error ('"{0}" is not a valid command in the Okta PowerShell API module.' -f $CommandName) | |
} | |
} | |
# Validate parameters | |
If (!(Test-Path $ImportPath -ErrorAction SilentlyContinue)) { | |
Write-Error ('Import file not found: {0}' -f $ImportPath) | |
Exit | |
} | |
If (!$ApiBaseUrl) { | |
Write-Error 'Unable to connect. ApiBaseUrl not specified and OKTA_PS_ORG_URL environment variable not set.' | |
Exit | |
} | |
If (!$ApiClientId) { | |
Write-Error 'Unable to connect. ApiClientId not specified and OKTA_PS_CLIENT_ID environment variable not set.' | |
Exit | |
} | |
# Import user list | |
$importUsers = Import-Csv -Path $ImportPath | |
# Get list of valid potential user ID fields | |
$fields = (@($importUsers)[0] | Get-Member -MemberType NoteProperty) | Select Name, @{Name='Sample data';Expression={($_.Definition -split '=')[1]}} | Where-Object {$_.'Sample data'} | |
# Select user ID field if not specified | |
If (!$UserLoginField) { | |
$UserLoginField = ($fields | Out-GridView -Title 'Select import field to identify user (e.g. email or user pricipal name)' -OutputMode Single).Name | |
Write-Host ('Import file user login field: {0}' -f $UserLoginField) | |
} Else { | |
If ($fields.Name -notcontains $UserLoginField) { | |
Write-Error 'Specified user login field not found in import file, or field data empty.' | |
Exit | |
} | |
} | |
# Filter imported users if required | |
If ($UserLoginFilter) { | |
$importUsers = $importUsers | Where-Object {$_.$UserLoginField -match $UserLoginFilter} | |
} | |
Write-Host ('Users imported: {0}' -f @($importUsers).Count) | |
# If we successfully imported users to assign | |
If ($importUsers) { | |
Write-Host 'Starting Okta API connection...' | |
Import-Module Okta.PowerShell | |
# Set up connection config | |
$Configuration = Get-OktaConfiguration | |
$Configuration.BaseUrl = $ApiBaseUrl | |
$Configuration.ClientId = $ApiClientId | |
$Configuration.Scope = 'okta.users.read okta.apps.manage' | |
# Check if valid token already established | |
Do { | |
Try { | |
$connectedUser = Get-OktaUser -UserId 'me' | |
} | |
Catch { | |
Try { | |
Invoke-OktaEstablishAccessToken | |
} Catch { | |
Write-Error 'Unable to connect to Okta API. Check your BaseUrl and ClientId parameters or environment variables.' | |
Exit | |
} | |
} | |
} Until ($connectedUser) | |
Write-Host ('Connected as {0}' -f $connectedUser.profile.login) | |
# Try to load specified application details | |
$appDetails = Get-OktaPagedResult -CommandName 'Invoke-OktaListApplications' -Arguments @{Q=$AppName} | |
# If a single application was not identified, present a list for selection | |
If (@($appDetails).Count -ne 1) { | |
Write-Host ('{0} matching applications found' -f @($appDetails).Count) | |
$appDetails = $appDetails | Out-GridView -Title 'Select application to assign users to' -OutputMode Single | |
} | |
If (!$appDetails) { | |
Write-Host 'No application selected.' | |
} Else { | |
# Check for provisioning features | |
If (Compare-Object -Referenceobject $appDetails.features -DifferenceObject 'PUSH_NEW_USERS','PUSH_PROFILE_UPDATES' -ExcludeDifferent) { | |
Write-Warning 'Application has provisioning features enabled. Assignments may fail due to lack of role information.' | |
} | |
Write-Host ('Preparing to assign users to {0} ({1})...' -f $appDetails.label, $appDetails.id) | |
# Load all users from Okta so we can match large quantities while avoiding rate limiting | |
Write-Host 'Loading user details from Okta...' | |
$oktaUsers = Get-OktaPagedResult -CommandName 'Invoke-OktaListUsers' | |
$matchedUsers = @() | |
# Attempt to find a matching Okta user for each imported email address | |
for ($index = 0; $index -lt @($importUsers).Count; $index++) { | |
$userEmail = @($importUsers)[$index].$UserLoginField | |
# Perform basic matching on Okta username or email | |
$match = $oktaUsers | Where-Object {$_.profile.login -eq $userEmail -or $_.profile.email -eq $userEmail} | |
# Try matching on second email if requested and not already matched | |
If (!$match -and $MatchSecondEmail) { | |
$match = $oktaUsers | Where-Object {$_.profile.secondEmail -eq $userEmail} | |
} | |
# Try matching on proxyAddresses attibute if requested and not already matched | |
If (!$match -and $MatchProxyAddresses) { | |
$match = $oktaUsers | Where-Object {$_.profile.proxyAddresses -match ('^smtp:{0}$' -f $userEmail)} | |
} | |
# If we definitely matched 1 user, add it to the matches user list, otherwise return a warning | |
If (@($match).Count -eq 1) { | |
$matchedUsers += $match | Select -Property id,status -ExpandProperty profile | |
} Else { | |
Write-Warning ('{0} users found matching email {1}' -f @($match).Count, $userEmail) | |
} | |
} | |
# Display results and select which user(s) to assign | |
Write-Host ('Users matched: {0}' -f @($matchedUsers).Count) | |
$addUsers = $matchedUsers | Out-GridView -Title 'Select users to assign' -OutputMode Multiple | |
$assignedUsers = @() | |
# Now assign the selected users | |
$progActivity = ('Assigning users to {0}' -f $appDetails.label) | |
for ($index = 0; $index -lt @($addUsers).Count; $index++) { | |
$addUser = @($addUsers)[$index] | |
Write-Progress -Activity $progActivity -Status $addUser.login -PercentComplete ($index * 100 / @($addUsers).Count) | |
# Create credentials object with no password (for use with SSO applications) | |
$ssoCreds = Initialize-OktaAppUserCredentials -UserName $addUser.login | |
$appUser = Initialize-OktaAppUser -Credentials $ssoCreds -Scope 'USER' -Id $addUser.id | |
Try { | |
$assignedUsers += Set-OktaUserToApplication -AppId $appDetails.Id -AppUser $appUser | |
Sleep -Seconds $SlowDown | |
} Catch { | |
Write-Host ('Error assigning user {0}: {1}' -f $addUser.login, $_.Exception.Message) | |
} | |
} | |
Write-Progress -Activity $progActivity -Completed | |
Write-Host ('Assignments completed: {0}' -f @($assignedUsers).Count) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment