Public/Connect-TenantLens.ps1

function Connect-TenantLens {
    <#
    .SYNOPSIS
        Connects to Microsoft Graph with the read-only scopes TenantLens needs.
    .DESCRIPTION
        Wraps Connect-MgGraph. Interactive (browser or device code) for ad-hoc
        assessments, app-only with a certificate for scheduled runs across many
        tenants. Interactive connections request the union of the read-only
        scopes required by the shipped collectors; any scope containing 'Write'
        is refused - TenantLens is read-only by design. For app-only, grant the
        app registration only the documented read application permissions.
    .EXAMPLE
        Connect-TenantLens
    .EXAMPLE
        Connect-TenantLens -TenantId contoso.onmicrosoft.com -UseDeviceCode
    .EXAMPLE
        Connect-TenantLens -TenantId $tenantId -ClientId $appId -CertificateThumbprint $thumbprint
    #>

    [CmdletBinding(DefaultParameterSetName = 'Interactive')]
    param(
        [Parameter(ParameterSetName = 'Interactive')]
        [Parameter(ParameterSetName = 'AppThumbprint', Mandatory)]
        [Parameter(ParameterSetName = 'AppCertificate', Mandatory)]
        [string]$TenantId,

        # App registration (client) id for app-only auth.
        [Parameter(ParameterSetName = 'AppThumbprint', Mandatory)]
        [Parameter(ParameterSetName = 'AppCertificate', Mandatory)]
        [string]$ClientId,

        [Parameter(ParameterSetName = 'AppThumbprint', Mandatory)]
        [string]$CertificateThumbprint,

        [Parameter(ParameterSetName = 'AppCertificate', Mandatory)]
        [System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,

        # Override the requested scopes (read-only scopes only, interactive auth).
        [Parameter(ParameterSetName = 'Interactive')]
        [string[]]$Scopes,

        [Parameter(ParameterSetName = 'Interactive')]
        [switch]$UseDeviceCode
    )

    $connectParams = @{ NoWelcome = $true; ErrorAction = 'Stop' }

    if ($PSCmdlet.ParameterSetName -eq 'Interactive') {
        if (-not $Scopes -or @($Scopes).Count -eq 0) { $Scopes = Get-TLDefaultScope }
        $writeScopes = @($Scopes | Where-Object { $_ -match '(?i)write' })
        if ($writeScopes.Count -gt 0) {
            throw ("TenantLens is read-only by design and refuses to request write scopes: {0}" -f ($writeScopes -join ', '))
        }
        $connectParams['Scopes'] = @($Scopes)
        if ($TenantId) { $connectParams['TenantId'] = $TenantId }
        if ($UseDeviceCode) { $connectParams['UseDeviceCode'] = $true }
    }
    else {
        $connectParams['TenantId'] = $TenantId
        $connectParams['ClientId'] = $ClientId
        if ($PSCmdlet.ParameterSetName -eq 'AppThumbprint') {
            $connectParams['CertificateThumbprint'] = $CertificateThumbprint
        }
        else {
            $connectParams['Certificate'] = $Certificate
        }
    }

    Connect-MgGraph @connectParams | Out-Null

    $context = Get-MgContext
    if (-not $context) { throw 'Connect-MgGraph did not establish a Graph context.' }

    [pscustomobject]@{
        TenantId = $context.TenantId
        Account  = $context.Account
        AuthType = $context.AuthType
        Scopes   = @($context.Scopes)
    }
}