ResolveEntraID.psm1
function Clear-MeidIdentityCache { <# .SYNOPSIS Clears the Entra ID identiy cache. .DESCRIPTION Clears the Microsoft Entra ID identiy cache. The cache is used to cache the mapping between the ID and resolved name. .EXAMPLE PS C:\> Clear-MeidIdentityCache Clears the Entra ID identiy cache. #> [CmdletBinding()] param () $script:IdNameMappingTable = @{} } function Get-MeidIdentityProvider { <# .SYNOPSIS Get registered Entra ID identity provider. .DESCRIPTION Get by the user registered Microsoft Entra ID identity provider. .PARAMETER Name Can be used to filter for Name of the registered Entra ID identity provider. .EXAMPLE PS C:\> Get-MeidIdentityProvider Get all registered Microsoft Entra ID identity provider. .EXAMPLE PS C:\> Get-MeidIdentityProvider -Name "*User*" Get registered Microsoft Entra ID identity provider where name like "*User*". #> [CmdletBinding()] param ( [PSFArgumentCompleter("ResolveEntraID.Provider")] [string] $Name = '*' ) process { $script:IdentityProvider.Values | Where-Object Name -like $Name } } function Register-MeidIdentityProvider { <# .SYNOPSIS Register Entra ID identity provider. .DESCRIPTION Register Microsoft Entra ID identity provider. .PARAMETER Name Name(s) of the Entra ID identity provider. .PARAMETER NameProperty Property(s) to search for. E.g.: userPrincipalName .PARAMETER Query Graph endpoint url. E.g.: users .EXAMPLE PS C:\> Register-MeidIdentityProvider -Name "UserUPN" -NameProperty "userPrincipalName" -Query "users" Will register a provider with name "UserUPN", the property to search for "userPrincipalName" with the query "users/{0}". .NOTES General notes #> [CmdletBinding()] param ( [Parameter(Mandatory)] [string[]] $Name, [Parameter(Mandatory)] [string[]] $NameProperty, [Parameter(Mandatory)] [string[]] $Query ) process { $queries = foreach ($item in $Query){ if($item -match "\{0\}"){ $item; continue} $item.TrimEnd("/").Replace("{","{{").Replace("}","}}"), "{0}" -join "/" } foreach ($entry in $Name) { $script:IdentityProvider[$entry] = [PSCustomObject]@{ Name = $entry NameProperty = $NameProperty Query = $queries } } } } function Resolve-MeidIdentity { <# .SYNOPSIS Resolve Entra ID identity. .DESCRIPTION Resolve Microsoft Entra ID identity. This function will resolve an ID to a user defined property. Requires an active connection to Azure with MiniGraph. .PARAMETER Id ID(s) that should be resolved by the function. .PARAMETER Provider Provider(s) that should be used to resolve the ID(s). .PARAMETER NoCache Option that no Cache will be created. ID(s) with the mapping property will not be cached. .PARAMETER NameOnly Option that only show the resolved name. .EXAMPLE PS C:\> Resolve-MeidIdentity -ID "xyz" -Provider UserUPN Will resolve the ID "xyz" with defined property in the provider "UserUPN". The written output is ID, Name (Property), Provider and the result will be written in the cache. .EXAMPLE PS C:\> Resolve-MeidIdentity -ID "xyz","abc" -Provider UserUPN,Group Will resolve the IDs "xyz" and "abc" with defined property in the providers "UserUPN" and "Group". The written output is ID, Name (Property), Provider and the result will be written in the cache. .EXAMPLE PS C:\> Resolve-MeidIdentity -ID "xyz","abc" -Provider UserUPN,Group -NoCache Will resolve the IDs "xyz" and "abc" with defined property in the providers "UserUPN" and "Group". The written output is ID, Name (Property), Provider and the result will NOT be written in the cache. .EXAMPLE PS C:\> Resolve-MeidIdentity -ID "xyz","abc" -Provider UserUPN,Group -NoCache Will resolve the IDs "xyz" and "abc" with defined property in the providers "UserUPN" and "Group". The written output is only Name (Property) and the result will NOT be written in the cache. #> [CmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [string[]] $Id, [Parameter(Mandatory)] [PSFArgumentCompleter("ResolveEntraID.Provider")] [PSFValidateSet(TabCompletion = "ResolveEntraID.Provider")] [string[]] $Provider, [switch] $NoCache, [switch] $NameOnly ) begin { function Write-Result { <# .SYNOPSIS Write the result .DESCRIPTION Write the result in several variants: ID, Name (resolved Property), Provider or with NameOnly Name The function will also write the result in the cache (IdNameMappingTable), if NoCache isn't set. #> [OutputType([string])] [CmdletBinding()] param ( [string] $Id, [string] $Provider, [string] $Value, [switch] $NameOnly, [switch] $NoCache ) $result = [PSCustomObject]@{ ID = $Id Name = $Value Provider = $Provider } if ($NameOnly) { $Value } else { $result } if ($NoCache) { return } if (-not $script:IdNameMappingTable[$Provider]) { $script:IdNameMappingTable[$Provider] = @{} } $script:IdNameMappingTable[$Provider][$Id] = $result } } process { :main foreach ($entry in $Id) { if ($entry -notmatch '^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$') { $entry continue } foreach ($providerName in $Provider) { if ($NoCache) { break } if ($script:IdNameMappingTable[$providerName].$entry) { Write-Result -Id $entry -Value $script:IdNameMappingTable[$providerName].$entry.Name -NoCache -Provider $providerName -NameOnly:$NameOnly continue main } } foreach ($providerName in $Provider) { $providerObject = $script:IdentityProvider[$providerName] if (-not $providerObject) { Write-PSFMessage -Level Error -Message "Could not find identity provider {0}. Please register or check the spelling of the provider. Known providers: {1}" -StringValues $providerName, ((Get-MeidIdentityProvider).Name -join ", ") -Target $providerName continue } try { $graphResponse = MiniGraph\Invoke-GraphRequest -Query ($providerObject.Query -f $entry) -ErrorAction Stop if (-not $graphResponse) { continue } foreach ($propertyName in $providerObject.NameProperty) { $resolvedName = $graphResponse.$propertyName if (-not $resolvedName) { continue } break } if (-not $resolvedName) { $resolvedName = $entry Write-PSFMessage -Level SomewhatVerbose -Message "{0} of type {1} could be found but failed to resolve the {2}." -StringValues $entry, $providerName, ($providerObject.NameProperty -join ", ") -Target $entry -Tag $providerName } Write-Result -Id $entry -Value $resolvedName -Provider $providerName -NameOnly:$NameOnly -NoCache:$NoCache continue main } catch { if ($_.ErrorDetails.Message -match '"code":"Request_ResourceNotFound"') { Write-PSFMessage -Level InternalComment -Message "ID {0} could not found as {1}." -StringValues $entry, $providerName -Target $entry -Tag $providerName -ErrorRecord $_ -OverrideExceptionMessage continue } Write-PSFMessage -Level Error -Message "Error resolving {0}." -StringValues $entry -ErrorRecord $_ -Target $entry -Tag $providerName, "fail" -EnableException $true -PSCmdlet $PSCmdlet } } Write-Result -Id $entry -Value $entry -Provider "Unknown" -NameOnly:$NameOnly -NoCache } } end { } } # ID for Name mapping $script:IdNameMappingTable = @{} # Identity Provider $script:IdentityProvider = @{} Register-PSFTeppScriptblock -Name ResolveEntraID.Provider -ScriptBlock { foreach ($provider in Get-MeidIdentityProvider){ @{ Text = $provider.Name ToolTip = "{0} --> {1}" -f $provider.Name, ($provider.NameProperty -join ", ") } } } $param = @{ Name = "Application" NameProperty = "displayName" Query = "applications/{0}" } Register-MeidIdentityProvider @param $param = @{ Name = "Group" NameProperty = "displayName" Query = "groups/{0}" } Register-MeidIdentityProvider @param $param = @{ Name = "User" NameProperty = "userPrincipalName" Query = "users/{0}" } Register-MeidIdentityProvider @param |