Public/Initialize-KritTcmTenant.ps1

function Initialize-KritTcmTenant {
    <#
    .SYNOPSIS
        One-shot end-to-end TCM bootstrap for a tenant: Connect + SP provision +
        permission grant. Idempotent.

    .DESCRIPTION
        Orchestrates the documented 4-step setup from
        https://learn.microsoft.com/en-us/graph/utcm-authentication-setup into one
        function with operator-overridable defaults and structured receipt.

        Steps:
          1. Connect-MgGraph with Application.ReadWrite.All + AppRoleAssignment.ReadWrite.All
             (unless already connected with sufficient scopes)
          2. Initialize-KritTcmServicePrincipal — provision TCM + M365 Admin Services SPs
          3. Grant-KritTcmGraphPermission — grant the requested Graph perms to TCM SP
          4. Emit consolidated receipt JSON (optional -OutDir)

    .PARAMETER Permissions
        Graph application permissions to grant to TCM. Defaults to the conservative
        set in the MS Learn example ('User.ReadWrite.All','Policy.Read.All'). Operator
        should expand based on which workloads they want to manage.

    .PARAMETER OutDir
        Where to write the receipt JSON. If unset, no file is written.

    .PARAMETER SkipConnect
        Skip Connect-MgGraph (assume already connected with sufficient scopes).

    .EXAMPLE
        # Default conservative permissions, no receipt write
        Initialize-KritTcmTenant -WhatIf
        Initialize-KritTcmTenant

    .EXAMPLE
        # Customer-grade — wider permission set, receipt to scripts/state/tcm-bootstrap/
        Initialize-KritTcmTenant `
            -Permissions @('User.ReadWrite.All','Policy.Read.All','Directory.Read.All','RoleManagement.Read.Directory','SecurityActions.Read.All') `
            -OutDir 'C:/some/repo/scripts/state/tcm-bootstrap'

    .NOTES
        Tenant ID auto-resolved from Get-MgContext. No hardcoded kritical.net / kritical
        defaults — this function ships safe for any customer tenant.
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Medium')]
    param(
        [string[]]$Permissions = @('User.ReadWrite.All','Policy.Read.All'),
        [string]$OutDir,
        [switch]$SkipConnect
    )

    $receipt = [ordered]@{
        Action='Initialize-KritTcmTenant'; Timestamp=(Get-Date).ToUniversalTime().ToString('o')
        Steps=[ordered]@{}; Errors=@{}; PermissionsRequested=$Permissions
    }

    # Step 1 — Connect (if needed)
    if (-not $SkipConnect) {
        $existing = Get-MgContext -ErrorAction SilentlyContinue
        $needed = @('Application.ReadWrite.All','AppRoleAssignment.ReadWrite.All')
        $haveAll = $existing -and ($needed | ForEach-Object { $_ -in $existing.Scopes }).Count -eq $needed.Count
        if (-not $haveAll) {
            if ($PSCmdlet.ShouldProcess('Microsoft Graph', "Connect with scopes $($needed -join ',')")) {
                Connect-MgGraph -Scopes $needed -NoWelcome -ErrorAction Stop
                $receipt.Steps['1-Connect'] = @{ Scopes=$needed; Connected=$true }
            }
        } else {
            $receipt.Steps['1-Connect'] = @{ Reused=$true; ExistingScopes=$existing.Scopes }
        }
    }
    $ctx = Get-MgContext -ErrorAction Stop
    $receipt.TenantId = $ctx.TenantId
    $receipt.Account  = $ctx.Account

    # Step 2 — Provision SPs
    try {
        $receipt.Steps['2-ServicePrincipals'] = Initialize-KritTcmServicePrincipal
    } catch { $receipt.Errors['2-ServicePrincipals'] = $_.Exception.Message }

    # Step 3 — Grant Graph permissions
    try {
        $receipt.Steps['3-Permissions'] = @($Permissions | Grant-KritTcmGraphPermission)
    } catch { $receipt.Errors['3-Permissions'] = $_.Exception.Message }

    # Step 4 — Receipt
    if ($OutDir) {
        if (-not (Test-Path $OutDir)) { New-Item -ItemType Directory -Force -Path $OutDir | Out-Null }
        $path = Join-Path $OutDir ("{0}-tcm-bootstrap.json" -f (Get-Date -F yyyyMMddHHmmss))
        $receipt | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $path -Encoding UTF8
        $receipt.ReceiptPath = $path
    }
    [PSCustomObject]$receipt
}