Public/Initialize-TCM.ps1

function Initialize-TCM {
    <#
    .SYNOPSIS
        One-time setup: registers the TCM service principal and grants required permissions.
    .DESCRIPTION
        Automates the TCM onboarding process:
        1. Creates the TCM service principal (AppId 03b07b79-c5bc-4b5e-9bfa-13acf4a99998)
        2. Grants it the required Graph and workload permissions
        3. Validates the setup

        Requires: Connect-MgGraph with Application.ReadWrite.All and AppRoleAssignment.ReadWrite.All
    .PARAMETER TenantId
        The tenant ID to configure. If omitted, uses the current connection's tenant.
    .PARAMETER Workloads
        Which workloads to grant permissions for. Defaults to all — no reason to limit unless
        you have a specific compliance constraint.
    .PARAMETER SkipPermissionGrant
        Only create the service principal without granting permissions.
    .EXAMPLE
        Initialize-TCM
    .EXAMPLE
        # Limit to specific workloads (rare — only if needed for compliance)
        Initialize-TCM -Workloads Entra, Exchange
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [string]$TenantId,

        [ValidateSet('All', 'Entra', 'Exchange', 'Intune', 'Teams')]
        [string[]]$Workloads = @('All'),

        [switch]$SkipPermissionGrant
    )

    # Step 1: Create the TCM service principal
    Write-Host '[ 1/3 ] Registering TCM service principal...' -ForegroundColor Cyan

    $existingSp = $null
    try {
        $existingSp = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/servicePrincipals?`$filter=appId eq '$script:TCM_APP_ID'"
    }
    catch { }

    if ($existingSp.value -and $existingSp.value.Count -gt 0) {
        $tcmSp = $existingSp.value[0]
        Write-Host " TCM service principal already exists (ObjectId: $($tcmSp.id))" -ForegroundColor Green
    }
    else {
        if ($PSCmdlet.ShouldProcess('TCM Service Principal', 'Create')) {
            $body = @{ appId = $script:TCM_APP_ID } | ConvertTo-Json
            $tcmSp = Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/v1.0/servicePrincipals' -Body $body -ContentType 'application/json'
            Write-Host " Created TCM service principal (ObjectId: $($tcmSp.id))" -ForegroundColor Green
        }
    }

    if ($SkipPermissionGrant) {
        Write-Host '[ SKIP ] Permission grant skipped.' -ForegroundColor Yellow
        return [PSCustomObject]@{
            ServicePrincipalId = $tcmSp.id
            AppId              = $script:TCM_APP_ID
            PermissionsGranted = $false
            Status             = 'ServicePrincipalCreated'
        }
    }

    # Step 2: Grant permissions
    Write-Host '[ 2/3 ] Granting permissions to TCM service principal...' -ForegroundColor Cyan

    # Get Microsoft Graph service principal
    $graphSp = (Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/servicePrincipals?`$filter=appId eq '00000003-0000-0000-c000-000000000000'").value[0]

    # Define role mappings per workload
    $permissionsByWorkload = @{
        'Entra'    = @('User.Read.All', 'Policy.Read.All', 'Policy.ReadWrite.ConditionalAccess', 'RoleManagement.Read.Directory', 'Application.Read.All', 'Group.Read.All')
        'Exchange' = @('Organization.Read.All')
        'Intune'   = @('DeviceManagementConfiguration.Read.All')
        'Teams'    = @('Organization.Read.All')
    }

    $targetWorkloads = if ($Workloads -contains 'All') { $permissionsByWorkload.Keys } else { $Workloads }

    $allRoles = $targetWorkloads | ForEach-Object { $permissionsByWorkload[$_] } | Select-Object -Unique

    $grantedCount = 0
    foreach ($roleName in $allRoles) {
        $appRole = $graphSp.appRoles | Where-Object { $_.value -eq $roleName -and $_.allowedMemberTypes -contains 'Application' }
        if (-not $appRole) {
            Write-Warning " App role '$roleName' not found on Microsoft Graph SP — skipping"
            continue
        }

        # Check if already assigned
        $existing = $null
        try {
            $existing = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$($tcmSp.id)/appRoleAssignments?`$filter=appRoleId eq '$($appRole.id)'"
        }
        catch { }

        if ($existing.value -and $existing.value.Count -gt 0) {
            Write-Host " Permission '$roleName' already granted" -ForegroundColor DarkGray
            continue
        }

        if ($PSCmdlet.ShouldProcess($roleName, 'Grant to TCM SP')) {
            $assignBody = @{
                principalId = $tcmSp.id
                resourceId  = $graphSp.id
                appRoleId   = $appRole.id
            } | ConvertTo-Json

            Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$($tcmSp.id)/appRoleAssignments" -Body $assignBody -ContentType 'application/json' | Out-Null
            Write-Host " Granted '$roleName'" -ForegroundColor Green
            $grantedCount++
        }
    }

    # Step 3: Validate
    Write-Host '[ 3/3 ] Validating setup...' -ForegroundColor Cyan
    $validation = Test-TCMConnection -Quiet

    $result = [PSCustomObject]@{
        ServicePrincipalId = $tcmSp.id
        AppId              = $script:TCM_APP_ID
        PermissionsGranted = $true
        NewPermissions     = $grantedCount
        Workloads          = $targetWorkloads
        Validated          = $validation
        Status             = if ($validation) { 'Ready' } else { 'SetupCompleteValidationFailed' }
    }

    if ($validation) {
        Write-Host "`nTCM setup complete! You can now create monitors and snapshots." -ForegroundColor Green
    }
    else {
        Write-Warning "`nTCM service principal created and permissions granted, but API validation failed. Permissions may take a few minutes to propagate."
    }

    $result
}