Created
November 25, 2025 01:32
-
-
Save rbrayb/c8ad22cea0d84189ddadac613d4f443d to your computer and use it in GitHub Desktop.
Using PowerShell 7 with Entra External ID (EEID) to link identities
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
| # If you haven't done this already... | |
| # Install-Module -Name Microsoft.Graph -Repository PSGallery -Scope CurrentUser -Force -AllowClobber | |
| # Install-Module -Name Microsoft.Entra -Repository PSGallery -Scope CurrentUser -Force -AllowClobber | |
| # Import-Module Microsoft.Graph | |
| # Import-Module Microsoft.Entra | |
| # Sign in interactively to your EEID tenant. | |
| Connect-MgGraph -Scopes 'User.ReadWrite.All', 'Directory.ReadWrite.All' | |
| # Update your values | |
| $userObjectId = "2c...00" # User Object ID, User Principal Name, or email | |
| $identityProvider = "facebook.com" # issuer value as used by the external identity provider | |
| $issuerUserId = "5eecb0cd" # issuerUserId for that external identity provider | |
| $signInType = "federated" # e.g. emailAddress, userName, userPrincipalName, federated | |
| try { | |
| # Read current identities - MUST include $select to get identities property | |
| $user = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$userObjectId`?` | |
| $select=id,displayName,userPrincipalName,identities" | |
| if (-not $user) { | |
| Write-Error "User not found: $userObjectId" | |
| exit 1 | |
| } | |
| Write-Host "Found user: $($user.displayName) ($($user.id))" | |
| Write-Host "`nCurrent identities:" | |
| if ($user.identities -and $user.identities.Count -gt 0) { | |
| $user.identities | Format-Table signInType, issuer, issuerAssignedId -AutoSize | |
| } else { | |
| Write-Host " (No existing identities found)" | |
| } | |
| # Prepare new identity object | |
| $newIdentity = @{ | |
| signInType = $signInType | |
| issuer = $identityProvider | |
| issuerAssignedId = $issuerUserId | |
| } | |
| # Build identities array - preserve ALL existing identities | |
| $identities = [System.Collections.ArrayList]@() | |
| if ($user.identities -and $user.identities.Count -gt 0) { | |
| Write-Host "`nPreserving $($user.identities.Count) existing identities..." | |
| # Convert each existing identity to a proper hashtable | |
| foreach ($identity in $user.identities) { | |
| $existingIdentity = @{ | |
| signInType = $identity.signInType | |
| issuer = if ($identity.issuer) { $identity.issuer } else { $null } | |
| issuerAssignedId = $identity.issuerAssignedId | |
| } | |
| # Remove null issuer if present (for userPrincipalName type) | |
| if ($null -eq $existingIdentity.issuer) { | |
| $existingIdentity.Remove('issuer') | |
| } | |
| [void]$identities.Add($existingIdentity) | |
| Write-Host " - Preserved: $($identity.signInType) | $($identity.issuer) | $($identity.issuerAssignedId)" | |
| } | |
| } | |
| # Check if the new identity already exists | |
| $exists = $false | |
| foreach ($identity in $identities) { | |
| if ($identity.issuer -eq $newIdentity.issuer -and | |
| $identity.issuerAssignedId -eq $newIdentity.issuerAssignedId -and | |
| $identity.signInType -eq $newIdentity.signInType) { | |
| $exists = $true | |
| break | |
| } | |
| } | |
| if (-not $exists) { | |
| # Add the new federated identity to the existing identities | |
| [void]$identities.Add($newIdentity) | |
| Write-Host "`nAdding new identity for provider: $identityProvider" | |
| Write-Host "Total identities to update: $($identities.Count)" | |
| # Debug: Show what we're sending | |
| Write-Host "`nIdentities being sent to API:" | |
| $identities | ForEach-Object { | |
| Write-Host " - $($_.signInType) | $($_.issuer) | $($_.issuerAssignedId)" | |
| } | |
| # Update the user with ALL identities (existing + new) | |
| $body = @{ | |
| identities = @($identities) | |
| } | |
| $jsonBody = $body | ConvertTo-Json -Depth 10 | |
| Write-Host "`nJSON Body:" | |
| Write-Host $jsonBody | |
| Invoke-MgGraphRequest -Method PATCH -Uri "https://graph.microsoft.com/v1.0/users/$($user.id)" -Body $jsonBody | |
| -ContentType "application/json" | |
| Write-Host "`nUser identities updated successfully" | |
| } | |
| else { | |
| Write-Host "`nFederated identity already exists for this user" | |
| } | |
| # Verify the linking - include $select for identities | |
| Write-Host "`nUpdated identities for user:" | |
| Start-Sleep -Seconds 2 # Brief pause to ensure changes are committed | |
| $updatedUser = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/users/$($user.id)?`$select=identities" | |
| if ($updatedUser.identities -and $updatedUser.identities.Count -gt 0) { | |
| Write-Host "Total identities: $($updatedUser.identities.Count)" | |
| foreach ($identity in $updatedUser.identities) { | |
| Write-Host " - Type: $($identity.signInType)" | |
| Write-Host " Issuer: $($identity.issuer)" | |
| Write-Host " IssuerAssignedId: $($identity.issuerAssignedId)" | |
| Write-Host "" | |
| } | |
| } else { | |
| Write-Host " (No identities found - this may indicate an issue)" | |
| } | |
| } | |
| catch { | |
| Write-Error "An error occurred: $_" | |
| Write-Error $_.Exception.Message | |
| if ($_.Exception.Response) { | |
| $reader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream()) | |
| $responseBody = $reader.ReadToEnd() | |
| Write-Error "Response: $responseBody" | |
| } | |
| exit 1 | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://medium.com/the-new-control-plane/using-powershell-7-with-entra-external-id-eeid-to-link-identities-70b51efed981