Public/Connect-Msec.ps1

function Connect-Msec {
    <#
    .SYNOPSIS
        Establishes a Microsoft Security session bound to a certificate in Azure Key Vault.

    .DESCRIPTION
        Use this once per PowerShell session before calling any Get-Msec* function. It:
          1. Requires you to be signed into Azure (Connect-AzAccount) - that's your *own*
             identity and is what Key Vault sees for RBAC and audit.
          2. Reads the certificate's metadata from Key Vault. New-MsecApp stamped the cert
             with two tags - AppId and TenantId - so the caller normally only needs the
             Key Vault and certificate names.
          3. Stores tenant + client IDs, vault name, key name, and thumbprint in a
             module-scoped session.
          4. Acquires the Graph and Defender tokens up front - each acquisition signs a
             fresh JWT client assertion inside Key Vault via Invoke-AzKeyVaultKeyOperation.
             The private key never leaves the vault.

        Identity resolution order:
          ClientId -> -ClientId param -> AppId tag on the cert -> error
          TenantId -> -TenantId param -> TenantId tag on the cert -> Get-AzContext tenant

        Required Azure RBAC for the calling user (or group):
          - 'Key Vault Certificate User' on the vault (to read cert metadata + tags).
          - 'Key Vault Crypto User' on the vault (to sign with the key).

    .PARAMETER KeyVaultName
        The Azure Key Vault containing the certificate.
    .PARAMETER CertificateName
        The name of the certificate in Key Vault. Defaults to 'msec-app' (matches the
        default used by New-MsecApp).
    .PARAMETER ClientId
        Optional override. App registration (client) ID. Only needed if the cert was not
        tagged by New-MsecApp.
    .PARAMETER TenantId
        Optional override. Entra ID tenant ID. Only needed if the cert was not tagged by
        New-MsecApp AND the current Az context tenant is wrong.

    .EXAMPLE
        Connect-AzAccount
        Connect-Msec -KeyVaultName 'kv-mysec'
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string] $KeyVaultName,
        [Parameter()][string] $CertificateName = 'msec-app',
        [Parameter()][string] $ClientId,
        [Parameter()][string] $TenantId
    )

    $azCtx = Get-AzContext -ErrorAction SilentlyContinue
    if (-not $azCtx) {
        throw 'No Azure context. Run Connect-AzAccount before Connect-Msec.'
    }

    $meta = Get-MsecCertificateMetadata -VaultName $KeyVaultName -CertificateName $CertificateName

    # Resolve ClientId: explicit param wins, else cert tag.
    if (-not $ClientId) { $ClientId = $meta.AppId }
    if (-not $ClientId) {
        throw "Could not resolve ClientId. The certificate '$CertificateName' has no 'AppId' tag and -ClientId was not provided. If the cert was created by New-MsecApp, the tag should be present; otherwise pass -ClientId explicitly."
    }

    # Resolve TenantId: explicit param > cert tag > Az context.
    if (-not $TenantId) { $TenantId = $meta.TenantId }
    if (-not $TenantId) { $TenantId = $azCtx.Tenant.Id }

    # Resolve the cloud's endpoints from the Az context (commercial, China, US Gov).
    # Pins the whole session to one cloud; switching clouds means a fresh Connect-AzAccount
    # + Connect-Msec, because a single Az session is one cloud at a time.
    $endpoints = Get-MsecEnvironment

    $script:MsecSession = @{
        TenantId        = $TenantId
        ClientId        = $ClientId
        KeyVaultName    = $KeyVaultName
        KeyName         = $meta.KeyName
        ThumbprintBytes = $meta.ThumbprintBytes
        Endpoints       = $endpoints
        Tokens          = @{}
    }

    # Prime tokens so configuration errors surface here, not deep in a Get-Msec* call.
    # Graph exists in every cloud; Defender (securitycenter) is commercial-only, so prime
    # it only where it has an endpoint (e.g. it is retired in Azure China).
    [void](Get-MsecAccessToken -Resource $endpoints.GraphResource)
    if ($endpoints.DefenderResource) {
        [void](Get-MsecAccessToken -Resource $endpoints.DefenderResource)
    }
    else {
        Write-Verbose "Defender (securitycenter) has no endpoint in $($endpoints.EnvironmentName); skipping Defender token. Defender functions will be unavailable."
    }

    Write-Verbose "Connected to tenant $TenantId as app $ClientId in $($endpoints.EnvironmentName) (cert: $($meta.Thumbprint))"
}