Modules/M365DSCTenantInfo.psm1

<#
.SYNOPSIS
    Resolves the initial onmicrosoft tenant domain.
 
.DESCRIPTION
    Retrieves the tenant's initial domain through Microsoft Graph when connection parameters support it.
    When a certificate path is provided and the tenant id already contains onmicrosoft, it returns the tenant id directly.
 
.PARAMETER ApplicationId
    Specifies the application id used for app-based authentication.
 
.PARAMETER TenantId
    Specifies the tenant id or tenant domain.
 
.PARAMETER ApplicationSecret
    Specifies the application secret used for app-based authentication.
 
.PARAMETER CertificateThumbprint
    Specifies the certificate thumbprint used for app-based authentication.
 
.PARAMETER CertificatePath
    Specifies the certificate file path used for app-based authentication.
 
.PARAMETER ManagedIdentity
    Indicates that managed identity authentication should be used.
 
.FUNCTIONALITY
    Internal
 
.OUTPUTS
    System.String
#>

function Get-M365DSCTenantDomain
{
    [CmdletBinding(DefaultParameterSetName = 'AppId')]
    [OutputType([System.String])]
    param
    (
        [Parameter(ParameterSetName = 'AppId', Mandatory = $true)]
        [System.String]
        $ApplicationId,

        [Parameter(Mandatory = $true)]
        [System.String]
        $TenantId,

        [Parameter(ParameterSetName = 'AppId')]
        [System.Management.Automation.PSCredential]
        $ApplicationSecret,

        [Parameter(ParameterSetName = 'AppId')]
        [System.String]
        $CertificateThumbprint,

        [Parameter(ParameterSetName = 'AppId')]
        [System.String]
        $CertificatePath,

        [Parameter(ParameterSetName = 'MID')]
        [Switch]
        $ManagedIdentity
    )

    if ([System.String]::IsNullOrEmpty($CertificatePath))
    {
        $null = New-M365DSCConnection -Workload 'MicrosoftGraph' `
            -InboundParameters $PSBoundParameters

        try
        {
            $tenantDetails = (Invoke-MgGraphRequest -Uri '/beta/organization' -Method GET -ErrorAction 'Stop').value
            $defaultDomain = $tenantDetails.verifiedDomains | Where-Object -Property isInitial -EQ $true

            return $defaultDomain.name
        }
        catch
        {
            if ($_.Exception.Message -eq 'Insufficient privileges to complete the operation.')
            {
                New-M365DSCLogEntry `
                    -Message 'Error retrieving Organizational information: Missing Organization.Read.All permission. ' `
                    -Exception $_ `
                    -Source $($MyInvocation.MyCommand.Source) `
                    -TenantId $TenantId `
                    -Credential $Credential

                return [System.String]::Empty
            }

            throw $_
        }
    }

    if ($TenantId.Contains('onmicrosoft'))
    {
        return $TenantId
    }
    else
    {
        throw 'TenantID must be in format contoso.onmicrosoft.com'
    }
}

<#
.SYNOPSIS
    Derives the tenant name from a bound parameter set.
 
.DESCRIPTION
    Returns the tenant identifier directly when TenantId is present.
    Otherwise, it derives the tenant name from the credential user principal name.
 
.PARAMETER ParameterSet
    Specifies the bound parameter hashtable to inspect.
 
.FUNCTIONALITY
    Internal
 
.OUTPUTS
    System.String
#>

function Get-M365DSCTenantNameFromParameterSet
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)]
        [System.Collections.HashTable]
        $ParameterSet
    )

    if ($ParameterSet.ContainsKey('TenantId'))
    {
        return $ParameterSet.TenantId
    }
    elseif ($ParameterSet.ContainsKey('Credential'))
    {
        try
        {
            $tenantName = $ParameterSet.Credential.Username.Split('@')[1]
            return $tenantName
        }
        catch
        {
            return $null
        }
    }
}

<#
.SYNOPSIS
    Resolves the organization domain from credential or tenant id.
 
.DESCRIPTION
    Extracts the DNS domain part from the credential username when available.
    If no credential is provided, it validates and returns the tenant id when it represents a domain value.
 
.PARAMETER Credential
    Specifies the credential containing a user principal name.
 
.PARAMETER TenantId
    Specifies the tenant id or domain value to return.
 
.FUNCTIONALITY
    Internal
#>

function Get-M365DSCOrganization
{
    param
    (
        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credential,

        [Parameter()]
        [System.String]
        $TenantId
    )

    if ($null -ne $Credential -and $Credential.UserName.Contains('@'))
    {
        $organization = $Credential.UserName.Split('@')[1]
        return $organization
    }

    if (-not [System.String]::IsNullOrEmpty($TenantId))
    {
        if ($TenantId.Contains('.'))
        {
            $organization = $TenantId
            return $organization
        }
        else
        {
            throw 'Tenant ID must be name of the tenant, e.g. contoso.onmicrosoft.com'
        }

    }
}

<#
.SYNOPSIS
    Retrieves the tenant short name from initial Microsoft Graph domains.
 
.DESCRIPTION
    Connects to Microsoft Graph and finds the initial onmicrosoft domain.
    Returns the tenant short name portion that is used to build workload-specific endpoints.
 
.PARAMETER UseMFA
    Indicates that interactive multi-factor authentication should be used.
 
.PARAMETER Credential
    Specifies the credential used for delegated authentication.
 
.FUNCTIONALITY
    Internal
#>

function Get-M365TenantName
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $false)]
        [switch]
        $UseMFA,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credential
    )

    $UseMFASwitch = @{}
    if ($UseMFA)
    {
        $UseMFASwitch.Add('UseMFA', $true)
    }

    Write-Verbose -Message 'Connection to Azure AD is required to automatically determine SharePoint Online admin URL...'
    $null = New-M365DSCConnection -Workload 'MicrosoftGraph' `
        -InboundParameters $PSBoundParameters
    Write-Verbose -Message 'Getting SharePoint Online admin URL...'
    $domain = Invoke-MgGraphRequest -Uri 'beta/domains' -Method GET
    [Array]$defaultDomain = $domain | Where-Object { ($_.id -like '*.onmicrosoft.com' -or $_.id -like '*.onmicrosoft.de') -and $_.isInitial -eq $true } # We don't use IsDefault here because the default could be a custom domain

    if ($defaultDomain[0].id -like '*.onmicrosoft.com*')
    {
        $tenantName = $defaultDomain[0].id -replace '.onmicrosoft.com', ''
    }
    elseif ($defaultDomain[0].id -like '*.onmicrosoft.de*')
    {
        $tenantName = $defaultDomain[0].id -replace '.onmicrosoft.de', ''
    }

    Write-Verbose -Message "M365 tenant name is $tenantName"
    return $tenantName
}

<#
.SYNOPSIS
    Resolves API endpoint base URLs for a tenant cloud region.
 
.DESCRIPTION
    Reads the tenant OpenID metadata to detect regional scope and returns endpoint values used by Microsoft365DSC.
    Currently resolves the Azure management endpoint with sovereign cloud handling.
 
.PARAMETER TenantId
    Specifies the tenant id or tenant domain used to query OpenID metadata.
 
.EXAMPLE
    PS> Get-M365DSCAPIEndpoint -TenantId 'contoso.onmicrosoft.com'
 
.FUNCTIONALITY
    Internal
 
.OUTPUTS
    System.Collections.Hashtable
#>

function Get-M365DSCAPIEndpoint
{
    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [System.String]
        $TenantId
    )

    try
    {
        $webrequest = Invoke-WebRequest -Uri "https://login.windows.net/$($TenantId)/.well-known/openid-configuration" -UseBasicParsing
        $response = ConvertFrom-Json $webrequest.Content
        $tenantRegionScope = $response.'tenant_region_scope'

        $endpoints = @{
            AzureManagement = $null
        }

        switch ($tenantRegionScope)
        {
            'USGov'
            {
                if ($null -ne $response.'tenant_region_sub_scope' -and $response.'tenant_region_sub_scope' -eq 'DODCON')
                {
                    $endpoints.AzureManagement = 'https://management.usgovcloudapi.net'
                }
            }
            default
            {
                $endpoints.AzureManagement = 'https://management.azure.com'
            }
        }
        return $endpoints
    }
    catch
    {
        throw $_
    }
}

<#
.SYNOPSIS
    Retrieves the SharePoint Online administration URL.
 
.DESCRIPTION
    Connects to Microsoft Graph, discovers the initial tenant domain, and builds the SharePoint admin URL.
    Returns the URL in the https://tenant-admin.sharepoint.com format.
 
.PARAMETER UseMFA
    Indicates that interactive multi-factor authentication should be used.
 
.PARAMETER Credential
    Specifies the credential used for delegated authentication.
 
.FUNCTIONALITY
    Internal
#>

function Get-SPOAdministrationUrl
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $false)]
        [switch]
        $UseMFA,

        [Parameter()]
        [System.Management.Automation.PSCredential]
        $Credential
    )

    $UseMFASwitch = @{}
    if ($UseMFA)
    {
        $UseMFASwitch.Add('UseMFA', $true)
    }

    Write-Verbose -Message 'Connection to Azure AD is required to automatically determine SharePoint Online admin URL...'
    $null = New-M365DSCConnection -Workload 'MicrosoftGraph' `
        -InboundParameters $PSBoundParameters
    Write-Verbose -Message 'Getting SharePoint Online admin URL...'
    $domain = Invoke-MgGraphRequest -Uri 'beta/domains' -Method GET
    [Array]$defaultDomain = $domain | Where-Object { ($_.id -like '*.onmicrosoft.com' -or $_.id -like '*.onmicrosoft.de' -or $_.id -like '*.onmicrosoft.us') -and $_.isInitial -eq $true } # We don't use IsDefault here because the default could be a custom domain

    if ($defaultDomain[0].id -like '*.onmicrosoft.com*')
    {
        $global:tenantName = $defaultDomain[0].id -replace '.onmicrosoft.com', ''
    }
    elseif ($defaultDomain[0].id -like '*.onmicrosoft.de*')
    {
        $global:tenantName = $defaultDomain[0].id -replace '.onmicrosoft.de', ''
    }
    $global:AdminUrl = "https://$global:tenantName-admin.sharepoint.com"
    Write-Verbose -Message "SharePoint Online admin URL is $global:AdminUrl"
    return $global:AdminUrl
}

Export-ModuleMember -Function @(
    'Get-M365DSCTenantDomain',
    'Get-M365DSCTenantNameFromParameterSet',
    'Get-M365DSCOrganization',
    'Get-M365TenantName',
    'Get-M365DSCAPIEndpoint',
    'Get-SPOAdministrationUrl'
)