Private/Get-Win32ToolkitTenantInfo.ps1

function Get-Win32ToolkitTenantInfo {
    <#
    .SYNOPSIS
        Resolves a friendly tenant name for the CURRENT Graph session, cached on disk.
 
    .DESCRIPTION
        A tenant GUID is unreadable, and the signed-in ACCOUNT is not identity either: a consultant's
        UPN looks identical in every customer tenant they are a guest in. The organisation's display
        name is the only thing a human reliably recognises, so the connection banner leads with it.
 
        Uses Invoke-MgGraphRequest against /v1.0/organization rather than Get-MgOrganization, so the
        module keeps its single Microsoft.Graph.Authentication dependency instead of pulling in
        Microsoft.Graph.Identity.DirectoryManagement for one call.
 
        NOTE the response shape: /organization returns a COLLECTION ({ value: [ {...} ] }), and
        Invoke-MgGraphRequest returns a HASHTABLE by default, not a PSObject. Both matter for the
        tests as much as the code.
 
        Cached under LOCALAPPDATA keyed by tenant id, so the banner costs one call per tenant ever.
        Fails OPEN: no name simply means the GUID is shown. Never blocks a publish.
 
    .OUTPUTS
        [pscustomobject] TenantId / DisplayName / DefaultDomain. DisplayName/DefaultDomain may be empty.
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param()

    $ctx = try { Get-MgContext } catch { $null }
    if (-not $ctx -or -not $ctx.TenantId) { return $null }

    $cacheDir  = Join-Path $env:LOCALAPPDATA 'CloudFlow\win32-toolkit'
    $cacheFile = Join-Path $cacheDir 'tenants.json'
    $cache = @{}
    if (Test-Path -LiteralPath $cacheFile) {
        try {
            $raw = Get-Content -LiteralPath $cacheFile -Raw -Encoding UTF8 | ConvertFrom-Json
            foreach ($p in $raw.PSObject.Properties) { $cache[$p.Name] = $p.Value }
        } catch { $cache = @{} }
    }

    $key = "$($ctx.TenantId)"
    if ($cache.ContainsKey($key) -and $cache[$key].DisplayName) {
        return [pscustomobject]@{
            TenantId      = $key
            DisplayName   = [string]$cache[$key].DisplayName
            DefaultDomain = [string]$cache[$key].DefaultDomain
        }
    }

    $name = ''; $domain = ''
    try {
        $resp = Invoke-MgGraphRequest -Method GET -Uri 'https://graph.microsoft.com/v1.0/organization' -ErrorAction Stop
        # Collection + HashTable: resp['value'] is an array of hashtables.
        $org = @($resp['value'])[0]
        if ($org) {
            $name = [string]$org['displayName']
            $default = @($org['verifiedDomains']) | Where-Object { $_['isDefault'] } | Select-Object -First 1
            if ($default) { $domain = [string]$default['name'] }
        }
    }
    catch {
        # Reading the org needs a directory-read permission the publish scope does not include. That is
        # fine and expected: degrade to the GUID rather than asking for more privilege than we need.
        Write-Verbose "Could not resolve the tenant name: $($_.Exception.Message)"
    }

    if ($name) {
        try {
            if (-not (Test-Path -LiteralPath $cacheDir)) { New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null }
            $cache[$key] = @{ DisplayName = $name; DefaultDomain = $domain }
            ($cache | ConvertTo-Json -Depth 5) | Set-Content -LiteralPath $cacheFile -Encoding UTF8
        } catch { Write-Verbose "Tenant-name cache write skipped: $($_.Exception.Message)" }
    }

    return [pscustomobject]@{ TenantId = $key; DisplayName = $name; DefaultDomain = $domain }
}