ResolveEntraID.psm1

function ConvertTo-REntraGUID {
    <#
    .SYNOPSIS
    Convert a string to a GUID
 
    .DESCRIPTION
    Convert a string to a GUID. If the string is already a GUID, it will be returned as is.
 
    .PARAMETER Identity
    The string to convert to a GUID.
 
    .PARAMETER Provider
    The provider to use for the conversion.
 
    .PARAMETER NoCache
    Option that no Cache will be created. ID(s) with the mapping property will not be cached.
 
    .PARAMETER IdOnly
    Option that only show the resolved ID.
 
    .EXAMPLE
    PS C:\> ConvertTo-REntraGUID -Identity "<userPrincipalName>" -Provider UserUPN
 
    Will convert the string "<userPrincipalName>" to a GUID using the provider "UserUPN".
 
    .EXAMPLE
    PS C:\> ConvertTo-REntraGUID -Identity "<userPrincipalName>" -Provider UserUPN -NoCache
 
    Will convert the string "<userPrincipalName>" to a GUID using the provider "UserUPN" and not cache the result.
 
    .EXAMPLE
    PS C:\> ConvertTo-REntraGUID -Identity "<userPrincipalName>" -Provider UserUPN -IdOnly
 
    Will convert the string "<userPrincipalName>" to a GUID using the provider "UserUPN" and only return the GUID.
 
    .EXAMPLE
    PS C:\> "<userPrincipalName>" | ConvertTo-REntraGUID -Provider UserUPN
 
    Will convert the string "<userPrincipalName>" to a GUID using the provider "UserUPN".
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [AllowEmptyCollection()]
        [AllowNull()]
        [string[]]
        $Identity,

        [Parameter(Mandatory)]
        [PSFArgumentCompleter("ResolveEntraID.Provider")]
        [PSFValidateSet(TabCompletion = "ResolveEntraID.Provider")]
        [string[]]
        $Provider,

        [switch]
        $NoCache,

        [switch]
        $IdOnly
    )
    begin {
        function Write-Result {
            <#
            .SYNOPSIS
            Write the result
 
            .DESCRIPTION
            Write the result in several variants:
            ID, Name (resolved Property), Provider
 
            or with IdOnly
            Name
 
            The function will also write the result in the cache (NameIDMappingTable), if NoCache isn't set.
            #>

            [OutputType([string])]
            [CmdletBinding()]
            param (
                [string]
                $Id,

                [string]
                $Provider,

                [string]
                $Name,

                [switch]
                $IdOnly,

                [switch]
                $NoCache
            )
            $result = [PSCustomObject]@{
                ID       = $Id
                Name     = $Name
                Provider = $Provider
            }
            if ($IdOnly) { $Id }
            else { $result }

            if ($NoCache) { return }

            if (-not $script:NameIdMappingTable[$Provider]) {
                $script:NameIdMappingTable[$Provider] = @{}
            }
            $script:NameIdMappingTable[$Provider][$Name] = $result
        }
    }
    process {
        :main foreach ($entry in $Identity) {
            if ($entry -match '^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$') {
                $entry
                continue
            }

            # Cache check for entry
            foreach ($providerName in $Provider) {
                if ($NoCache) { break }
                if ($script:NameIdMappingTable[$providerName].$entry) {
                    Write-Result -Id $script:NameIdMappingTable[$providerName].$entry.ID -Name $entry -NoCache -Provider $providerName
                    continue main
                }
            }
            # Resolve against Entra
            :providers foreach ($providerName in $Provider) {

                # Resolve provider to use
                $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 $providerObject.Name, ((Get-MeidIdentityProvider).Name -join ", ") -Target $providerObject.Name
                    continue
                }

                # Resolve identities
                foreach ($queryPath in $providerObject.QueryByName) {
                    try {
                        $graphResponse = Invoke-EntraRequest -Path ($queryPath -f $entry) -ErrorAction Stop
                    }
                    catch {
                        if ($_.ErrorDetails.Message -match '"code":\s*"Request_ResourceNotFound"') {
                            Write-PSFMessage -Level InternalComment -Message "ID {0} could not found as {1}." -StringValues $entry, $providerObject.Name -Target $entry -Tag $providerObject.Name -ErrorRecord $_ -OverrideExceptionMessage
                            continue
                        }
                        Write-PSFMessage -Level Error -Message "Error resolving {0}." -StringValues $entry -ErrorRecord $_ -Target $entry -Tag $providerObject.Name, "fail" -EnableException $true -PSCmdlet $PSCmdlet
                        continue
                    }
                    if ($graphResponse) { break }
                }
                if (-not $graphResponse ) { continue providers }

                # Multi value handling of identities
                foreach ($propertyName in $providerObject.NameProperty) {
                    $resolvedPrincipal = $graphResponse | Where-Object $propertyName -EQ $entry
                    if ($resolvedPrincipal) { break }
                }
                if ($resolvedPrincipal.Count -gt 1) {
                    Write-PSFMessage -Level Error -Message "Unable to uniquely identify {0} as {1}. Found {2} matches for property {3}." -StringValues $entry, $providerObject.Name, $resolvedPrincipal.Count, $propertyName
                    continue providers
                }

                # Resolve identity to ID
                foreach ($propertyID in $providerObject.IDProperty) {
                    $resolvedID = $resolvedPrincipal.$propertyID
                    if (-not $resolvedID) { continue }
                    break
                }
                if (-not $resolvedID) {
                    $resolvedID = $entry
                    Write-PSFMessage -Level SomewhatVerbose -Message "{0} of type {1} could be found but failed to resolve the {2}." -StringValues $entry, $providerObject.Name, ($providerObject.IdProperty -join ", ") -Target $entry -Tag $providerObject.Name
                }
                Write-Result -Id $resolvedID -Name $entry -Provider $providerObject.Name -IdOnly:$IdOnly -NoCache:$NoCache
                continue main
            }
            Write-Result -Id $entry -Name $entry -Provider "Unknown" -IdOnly:$IdOnly -NoCache
        }
    }
    end {

    }
}

function ConvertTo-REntraName {
    <#
    .SYNOPSIS
    Convert a GUID to a user defined property.
 
    .DESCRIPTION
    Convert a GUID to a user defined property. If the GUID is already a user defined property, it will be returned as is.
 
    .PARAMETER Identity
    The GUID to convert to a user defined property.
 
    .PARAMETER Provider
    The provider to use for the conversion.
 
    .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:\> ConvertTo-REntraName -Identity "<GUID>" -Provider UserUPN
 
    Will convert the GUID "<GUID>" to a user defined property using the provider "UserUPN".
 
    .EXAMPLE
    PS C:\> ConvertTo-REntraName -Identity "<GUID>" -Provider UserUPN -NoCache
 
    Will convert the GUID "<GUID>" to a user defined property using the provider "UserUPN" and not cache the result.
 
    .EXAMPLE
    PS C:\> "<GUID>" | ConvertTo-REntraName -Provider UserUPN
 
    Will convert the GUID "<GUID>" to a user defined property using the provider "UserUPN".
 
    .EXAMPLE
    PS C:\> ConvertTo-REntraName -Identity "<GUID>" -Provider UserUPN -NameOnly
 
    Will convert the GUID "<GUID>" to a user defined property using the provider "UserUPN" and only return the user defined property.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [AllowEmptyCollection()]
        [AllowNull()]
        [string[]]
        $Identity,

        [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]
                $Name,

                [switch]
                $NameOnly,

                [switch]
                $NoCache
            )
            $result = [PSCustomObject]@{
                ID       = $Id
                Name     = $Name
                Provider = $Provider
            }
            if ($NameOnly) { $Name }
            else { $result }

            if ($NoCache) { return }

            if (-not $script:IdNameMappingTable[$Provider]) {
                $script:IdNameMappingTable[$Provider] = @{}
            }
            $script:IdNameMappingTable[$Provider][$Id] = $result
        }
    }
    process {
        :main foreach ($entry in $Identity) {
            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 -Name $script:IdNameMappingTable[$providerName].$entry.Name -NoCache -Provider $providerName -NameOnly:$NameOnly
                    continue main
                }
            }
            #region iterate over providers
            :providers 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-REntraIdentityProvider).Name -join ", ") -Target $providerName
                    continue
                }

                foreach ($queryPath in $providerObject.QueryByGuid) {
                    try {
                        $graphResponse = Invoke-EntraRequest -Path ($queryPath -f $entry) -ErrorAction Stop
                    }
                    catch {
                        if ($_.ErrorDetails.Message -match '"code":\s*"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
                        continue
                    }
                    if ($graphResponse) { break }
                }
                if (-not $graphResponse ) { continue providers }

                foreach ($propertyName in $providerObject.NameProperty) {
                    $resolvedName = $graphResponse.$propertyName
                    if ($resolvedName) { 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 -Name $resolvedName -Provider $providerName -NameOnly:$NameOnly -NoCache:$NoCache
                continue main
            }
            #endregion iterate over providers
            Write-Result -Id $entry -Name $entry -Provider "Unknown" -NameOnly:$NameOnly -NoCache
        }
    }
    end {

    }
}


function Clear-REntraIdentityCache {
    <#
    .SYNOPSIS
    Clear Entra ID identiy cache.
 
    .DESCRIPTION
    Clear Microsoft Entra ID identiy cache.
    This function will clear the cache of the identity.
 
    .PARAMETER Provider
    Provider(s) that should be used to clear the cache.
 
    .EXAMPLE
    PS C:\> Clear-REntraIdentityCache
 
    Will clear all cached identities.
 
    .EXAMPLE
    PS C:\> Clear-REntraIdentityCache -Provider "User"
 
    Will clear the cached identities for the provider "User".
 
    .EXAMPLE
    PS C:\> Clear-REntraIdentityCache -Provider "User","Group"
 
    Will clear the cached identities for the providers "User" and "Group".
    #>

    [CmdletBinding()]
    param (
        [string[]]
        $Provider
    )
    if (-not $Provider) {
        $script:IdNameMappingTable = @{}
        $script:NameIdMappingTable = @{}
    }
    else {
        foreach ($providerName in $Provider) {
            $script:IdNameMappingTable.Remove($providerName)
            $script:NameIdMappingTable.Remove($providerName)
        }
    }
}

function Get-REntraIdentityCache {
    <#
    .SYNOPSIS
    Get Entra ID identiy cache.
 
    .DESCRIPTION
    Get Microsoft Entra ID identiy cache.
    This function will get all cached identities.
 
    .PARAMETER Provider
    Provider(s) that should be used to get the cache.
 
    .EXAMPLE
    PS C:\> Get-REntraIdentityCache
 
    Will get all cached identities.
 
    .EXAMPLE
    PS C:\> Get-REntraIdentityCache -Provider "User"
 
    Will get the cached identities for the provider "User".
 
    .EXAMPLE
    PS C:\> Get-REntraIdentityCache -Provider "User","Group"
 
    Will get the cached identities for the providers "User" and "Group".
    #>

    [OutputType([hashtable])]
    [CmdletBinding()]
    param (
        [string[]]
        $Provider
    )
    if (-not $Provider) {
        $script:IdNameMappingTable
        $script:NameIdMappingTable
    }
    else {
        foreach ($providerName in $Provider) {
            $script:IdNameMappingTable[$providerName]
            $script:NameIdMappingTable[$providerName]
        }
    }
}

function Get-REntraIdentityProvider {
    <#
    .SYNOPSIS
    Get Entra ID identity provider.
 
    .DESCRIPTION
    Get Microsoft Entra ID identity provider.
    This function will get all registered providers.
 
    .PARAMETER ProviderName
    Name(s) of the Entra ID identity provider.
 
    .EXAMPLE
    PS C:\> Get-REntraIdentityProvider
 
    Will get all providers.
 
    .EXAMPLE
    PS C:\> Get-REntraIdentityProvider -ProviderName "*"
 
    Will get all providers.
 
    .EXAMPLE
    PS C:\> Get-REntraIdentityProvider -ProviderName "User"
 
    Will get the provider with name "User".
 
    .EXAMPLE
    PS C:\> Get-REntraIdentityProvider -ProviderName "User*"
 
    Will get all providers with name starting with "User".
 
    .EXAMPLE
    PS C:\> Get-REntraIdentityProvider -ProviderName "*User"
 
    Will get all providers with name ending with "User".
 
    .EXAMPLE
    PS C:\> Get-REntraIdentityProvider -ProviderName "*User*"
 
    Will get all providers with name containing "User".
    #>

    [CmdletBinding()]
    param (
        [PSFArgumentCompleter("ResolveEntraID.Provider")]
        [string]
        $ProviderName = '*'
    )
    process {
        $script:IdentityProvider.Values | Where-Object Name -Like $ProviderName
    }
}

function Register-REntraIdentityProvider {
    <#
    .SYNOPSIS
    Register Entra ID identity provider.
 
    .DESCRIPTION
    Register Microsoft Entra ID identity provider.
    This function will register a provider with the defined properties.
    Per default the provider are "User", "Group" and "Application" for more information, check the examples of this function.
 
    .PARAMETER ProviderName
    Name(s) of the Entra ID identity provider.
 
    .PARAMETER NameProperty
    Property(s) to search for. E.g.: userPrincipalName
 
    .PARAMETER IDProperty
    Property(s) to return. E.g.: id
 
    .PARAMETER QueryByName
    Query(s) to search for the property. E.g.: users?`$filter=userPrincipalName eq '{0}'
 
    .PARAMETER QueryByGUID
    Query(s) to search for the GUID. E.g.: users/{0}
 
    .EXAMPLE
    PS C:\> Register-REntraIdentityProvider -ProviderName "User" -NameProperty "userPrincipalName", "mail" -IDProperty "id" -QueryByName "users?`$filter=userPrincipalName eq '{0}' or mail eq '{0}'", "users?`$filter=Name eq '{0}'" -QueryByGUID "users/{0}"
 
    Will register a provider with name "User", the property to search for "userPrincipalName" and "mail" with the query to get the GUID "users?`$filter=userPrincipalName eq '{0}' or mail eq '{0}'" or "users?`$filter=Name eq '{0}'" or to get the userPrincipalName "users/{0}".
 
    .EXAMPLE
    PS C:\> Register-REntraIdentityProvider -ProviderName "Group" -NameProperty "displayName" -IDProperty "id" -QueryByName "groups?`$filter=displayName eq '{0}'" -QueryByGUID "groups/{0}"
 
    Will register a provider with name "Group", the property to search for "displayName" with the query to get the GUID "groups?`$filter=displayName eq '{0}'" or to get the displayName "groups/{0}".
 
    .EXAMPLE
    PS C:\> Register-REntraIdentityProvider -ProviderName "Application" -NameProperty "displayName" -IDProperty "appId", "id" -QueryByName "applications?`$filter=displayName eq '{0}'", "servicePrincipals?`$filter=displayName eq '{0}'" -QueryByGUID "applications/{0}", "servicePrincipals(appId='{0}')", "servicePrincipals/{0}"
 
    Will register a provider with name "Application", the property to search for "displayName" with the query to get the GUID "applications?`$filter=displayName eq '{0}'" or to get the displayName "applications/{0}".
    Will register a provider with name "Application", the property to search for "displayName" with the query to get the GUID "servicePrincipals?`$filter=displayName eq '{0}'" or to get the displayName "servicePrincipals(appId='{0}')".
    Will register a provider with name "Application", the property to search for "displayName" with the query to get the GUID "servicePrincipals?`$filter=displayName eq '{0}'" or to get the displayName "servicePrincipals/{0}".
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string[]]
        $ProviderName,

        [Parameter(Mandatory)]
        [string[]]
        $NameProperty,

        [Parameter(Mandatory)]
        [string[]]
        $IDProperty,

        [Parameter(Mandatory)]
        [string[]]
        $QueryByName,

        [Parameter(Mandatory)]
        [string[]]
        $QueryByGUID
    )
    process {
        $queriesName = foreach ($item in $QueryByName) {
            if ($item -match "\{0\}") { $item; continue }
            $item.TrimEnd("/").Replace("{", "{{").Replace("}", "}}"), "{0}" -join "/"
        }
        $queriesGUID = foreach ($item in $QueryByGUID) {
            if ($item -match "\{0\}") { $item; continue }
            $item.TrimEnd("/").Replace("{", "{{").Replace("}", "}}"), "{0}" -join "/"
        }
        foreach ($entry in $ProviderName) {
            $script:IdentityProvider[$entry] = [PSCustomObject]@{
                Name         = $entry
                NameProperty = $NameProperty
                IDProperty   = $IDProperty
                QueryByName  = $queriesName
                QueryByGUID  = $queriesGUID
                PSTypeName   = "ResolveEntraID.Provider"
            }
        }
    }
}

function Resolve-REntraIdentity {
    <#
    .SYNOPSIS
    Resolve Entra ID identity.
 
    .DESCRIPTION
    Resolve Microsoft Entra ID identity.
    This function will resolve an ID to a property or resolve a property to a ID.
    Requires an active connection to Azure with EntraAuth.
    Default service for EntraAuth is "ResolveEntraGraph".
 
    .PARAMETER Identity
    ID(s) that should be resolved by the function.
 
    .PARAMETER Provider
    Provider(s) that should be used to resolve the ID(s).
 
    .PARAMETER ResultType
    Option to define the result type. Default is "Name".
 
    .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-REntraIdentity -Identity "<ID>" -Provider UserUPN -ResultType Name
 
    Will resolve the ID "<ID>" 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-REntraIdentity -Identity "<Property>" -Provider UserUPN -ResultType ID
 
    Will resolve the Name (Property) "<Property>" 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-REntraIdentity -Identity "<ID>","<ID2>" -Provider UserUPN,Group
 
    Will resolve the IDs "<ID>" and "<ID2>" 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-REntraIdentity -Identity "<ID>","<ID2>" -Provider UserUPN,Group -NoCache
 
    Will resolve the IDs "<ID>" and "<ID2>" 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-REntraIdentity -Identity "<ID>","<ID2>" -Provider UserUPN,Group -NoCache -NameOnly
 
    Will resolve the IDs "<ID>" and "<ID2>" 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.
 
    .EXAMPLE
     PS C:\> Resolve-REntraIdentity -Identity "<Property>","<Property2>" -Provider UserUPN,Group -NoCache -ResultType ID -IdOnly
 
    Will resolve the Name (Property) "<Property>" and "<Property2>" with defined property in the providers "UserUPN" and "Group".
    The written output is only the ID and the result will NOT be written in the cache.
 
    .EXAMPLE
    PS C:\> "<ID>" | Resolve-REntraIdentity -Provider UserUPN
 
    Will resolve the ID "<ID>" with defined property in the provider "UserUPN".
    The written output is ID, Name (Property), Provider and the result will be written in the cache.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', ('Provider','NoCache', 'NameOnly') , Justification = 'Provider, NoCache and NameOnly are used in the function, in a steppable pipeline and should not be suppressed.')]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [AllowEmptyCollection()]
        [AllowNull()]
        [string[]]
        $Identity,

        [Parameter(Mandatory)]
        [PSFArgumentCompleter("ResolveEntraID.Provider")]
        [PSFValidateSet(TabCompletion = "ResolveEntraID.Provider")]
        [string[]]
        $Provider,

        [ValidateSet("Name", "ID")]
        [string]
        $ResultType = "Name",

        [switch]
        $NoCache,

        [Alias("IdOnly")]
        [switch]
        $NameOnly
    )
    begin {
        Assert-EntraConnection -Cmdlet $PSCmdLet -Service $Script:PSDefaultParameterValues["Invoke-EntraRequest:Service"]
        # GetSteppablePipeline is used to create a portable pipeline, that allows pausing and resuming the enclosed command as needed.
        # Effectively this means, the wrapped command will only be run once, no matter the numbers of input ids.
        # This significantly improves performance.
        $command = switch ($ResultType) {
            "Name" { { ConvertTo-REntraName -Provider $Provider -NoCache:$NoCache -NameOnly:$NameOnly }.GetSteppablePipeline() }
            "ID" { { ConvertTo-REntraGUID -Provider $Provider -NoCache:$NoCache -IdOnly:$NameOnly }.GetSteppablePipeline() }
        }
        $command.begin($true)
    }
    process {
        foreach ($entry in $Identity) { $command.Process($entry) }
    }
    end {
        $command.End()
    }
}

function Set-REntraConnection {
    <#
    .SYNOPSIS
    Set the default service for ResolveEntraID.
 
    .DESCRIPTION
    Set the default service for for ResolveEntraID.
 
    .PARAMETER Name
    The name of the service. Default is "ResolveEntraGraph".
 
    .EXAMPLE
    PS C:\> Set-REntraConnection -Name "ResolveEntraGraph"
 
    Will set the default service for Invoke-EntraRequest to "ResolveEntraGraph"
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [PSFValidateSet(TabCompletion = "ResolveEntraID.EntraService")]
        [PsfArgumentCompleter("ResolveEntraID.EntraService")]
        [string]
        $Name
    )
    process {
        $Script:PSDefaultParameterValues["Invoke-EntraRequest:Service"] = $Name
    }
}

function Unregister-REntraIdentityProvider {
    <#
    .SYNOPSIS
    Unregister Entra ID identity provider.
 
    .DESCRIPTION
    Unregister Microsoft Entra ID identity provider.
    This function will unregister a provider.
 
    .PARAMETER ProviderName
    Name(s) of the Entra ID identity provider.
 
    .EXAMPLE
    PS C:\> Get-REntraIdentityProvider -ProviderName "*"
 
    Will unregister all providers.
 
    .EXAMPLE
    PS C:\> Unregister-REntraIdentityProvider -ProviderName "User"
 
    Will unregister the provider with name "User".
 
    .EXAMPLE
    PS C:\> Unregister-REntraIdentityProvider -ProviderName "User","Group"
 
    Will unregister the providers with name "User" and "Group".
 
    .EXAMPLE
    PS C:\> Get-REntraIdentityProvider -ProviderName "User*"
 
    Will unregister all providers with name starting with "User".
 
    .EXAMPLE
    PS C:\> "User" | Unregister-REntraIdentityProvider
 
    Will unregister the provider with name "User".
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [PSFArgumentCompleter("ResolveEntraID.Provider")]
        [PSFValidateSet(TabCompletion = "ResolveEntraID.Provider")]
        [string[]]
        $ProviderName
    )
    process {
        foreach ($entry in $ProviderName ) {
            Clear-REntraIdentityCache -Provider $entry
            $script:IdentityProvider.Remove($entry)
        }
    }
}

# ID for Name mapping
$script:IdNameMappingTable = @{}

# Name for ID mapping
$script:NameIdMappingTable = @{}

# Identity Provider
$script:IdentityProvider = @{}

Register-PSFTeppScriptblock -Name ResolveEntraID.Provider -ScriptBlock {
    foreach ($provider in Get-MeidIdentityProvider){
        @{
            Text = $provider.Name
            ToolTip = "{0} --> Property: {1}" -f $provider.Name, ($provider.NameProperty -join ", ")
        }
    }
}

Register-PSFTeppScriptblock -Name ResolveEntraID.EntraService -ScriptBlock {
    foreach ($service in Get-EntraService){
        @{
            Text = $service.Name
            ToolTip = "{0} --> ServiceUrl: {1}" -f $service.Name, $service.ServiceUrl
        }
    }
}

$param = @{
    ProviderName = "Application"
    NameProperty = "displayName"
    IdProperty   = "appId", "id"
    QueryByName  = "applications?`$filter=displayName eq '{0}'", "servicePrincipals?`$filter=displayName eq '{0}'"
    QueryByGUID  = "applications/{0}", "servicePrincipals(appId='{0}')", "servicePrincipals/{0}"
}
Register-REntraIdentityProvider @param

$param = @{
    Name          = 'ResolveEntraGraph'
    ServiceUrl    = 'https://graph.microsoft.com/beta'
    Resource      = 'https://graph.microsoft.com'
    DefaultScopes = @()
    HelpUrl       = 'https://developer.microsoft.com/en-us/graph/quick-start'
}
Register-EntraService @param

$Script:PSDefaultParameterValues["Invoke-EntraRequest:Service"] = "ResolveEntraGraph"

$param = @{
    ProviderName = "Group"
    NameProperty = "displayName"
    IdProperty   = "id"
    QueryByName  = "groups?`$filter=displayName eq '{0}'"
    QueryByGUID  = "groups/{0}"
}
Register-REntraIdentityProvider @param

$param = @{
    ProviderName = "User"
    NameProperty = "userPrincipalName", "mail"
    IdProperty   = "id"
    QueryByName  = "users?`$filter=userPrincipalName eq '{0}' or mail eq '{0}'", "users?`$filter=Name eq '{0}'"
    QueryByGUID  = "users/{0}"
}
Register-REntraIdentityProvider @param