Skip to content

Instantly share code, notes, and snippets.

@jschlackman
Last active March 14, 2025 21:10
Show Gist options
  • Save jschlackman/2972f5eb315c66e00482fd8802e4c957 to your computer and use it in GitHub Desktop.
Save jschlackman/2972f5eb315c66e00482fd8802e4c957 to your computer and use it in GitHub Desktop.
<#
.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