Private/Environment/ConvertTo-MsalEnvironment.ps1

<#
.SYNOPSIS
    Validates one environment and returns it in the module's normalised form.
.DESCRIPTION
    Takes a hashtable or an object with Name, AzureADEndpoint, GraphEndpoint and, optionally,
    DisableInstanceDiscovery and AzureCloudInstance. The name must be letters, digits, hyphens or
    underscores; each endpoint must be an https URL with a host only, and is kept as
    https://host in lower case. Throws on any invalid value.
.EXAMPLE
    ConvertTo-MsalEnvironment @{ Name = 'Contoso'; AzureADEndpoint = 'https://login.contoso.com'; GraphEndpoint = 'https://graph.contoso.com' } -Type UserDefined
    Returns the Contoso environment, ready to add to the module state.
.OUTPUTS
    System.Management.Automation.PSCustomObject
#>

function ConvertTo-MsalEnvironment {
    [CmdletBinding()]
    [OutputType([psobject])]
    param (
        # Hashtable or object with Name, AzureADEndpoint, GraphEndpoint and optionally DisableInstanceDiscovery, AzureCloudInstance.
        [Parameter(Mandatory = $true, Position = 0)]
        [object] $InputObject,
        [Parameter(Mandatory = $true)]
        [ValidateSet('BuiltIn', 'UserDefined')]
        [string] $Type
    )

    ## Validates and normalises one environment. Throws on an invalid name or endpoint.
    $Source = @{}
    if ($InputObject -is [System.Collections.IDictionary]) { foreach ($Key in $InputObject.Keys) { $Source[[string] $Key] = $InputObject[$Key] } }
    else { foreach ($Property in $InputObject.psobject.Properties) { $Source[$Property.Name] = $Property.Value } }
    $Value = { param ($Key) $Source[$Key] }
    $Name = [string] (& $Value 'Name')
    if ($Name -notmatch '^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$') {
        throw ('Invalid environment name "{0}": use letters, digits, "-" and "_" only.' -f $Name)
    }
    $Endpoints = @{}
    foreach ($Property in 'AzureADEndpoint', 'GraphEndpoint') {
        $Uri = $null
        if (-not [uri]::TryCreate([string] (& $Value $Property), [System.UriKind]::Absolute, [ref] $Uri) -or $Uri.Scheme -ne 'https' -or $Uri.AbsolutePath -ne '/' -or $Uri.Query -or $Uri.Fragment) {
            throw ('Invalid {0} "{1}" for environment "{2}": give an https URL with a host only, e.g. https://login.example.com.' -f $Property, (& $Value $Property), $Name)
        }
        $Endpoints[$Property] = 'https://{0}' -f $Uri.Authority.ToLowerInvariant()
    }
    # An older MSAL.NET loaded first by another module (kept with dll.lenientLoading) lacks the newer
    # values, such as GovFr before 4.6x: the environment then has none instead of failing the import.
    $AzureCloudInstance = $null
    $InstanceName = [string] (& $Value 'AzureCloudInstance')
    if ($InstanceName) {
        $Known = @([System.Enum]::GetNames([Microsoft.Identity.Client.AzureCloudInstance]) | Where-Object { $_ -eq $InstanceName })
        if ($Known.Count -gt 0) { $AzureCloudInstance = [Microsoft.Identity.Client.AzureCloudInstance] $Known[0] }
        else { Write-Verbose ('Environment "{0}": the loaded MSAL.NET has no AzureCloudInstance "{1}".' -f $Name, $InstanceName) }
    }

    $Environment = [PSCustomObject] @{
        PSTypeName               = 'PoshMsal.Environment'
        Name                     = $Name
        Type                     = $Type
        AzureADEndpoint          = $Endpoints.AzureADEndpoint
        GraphEndpoint            = $Endpoints.GraphEndpoint
        DisableInstanceDiscovery = [bool] (& $Value 'DisableInstanceDiscovery')
        AzureCloudInstance       = $AzureCloudInstance
    }
    return $Environment
}