# Claude-MSP-Access - Automated Tenant Onboarding # Onboards a customer tenant with full Claude + CIPP permissions # No manual intervention required after initial admin consent # # Usage: .\claude-msp-onboard-tenant.ps1 -TenantDomain "sonorangreenllc.com" # # Prerequisites: Admin consent URL must be clicked first by customer/sysadmin: # https://login.microsoftonline.com/common/adminconsent?client_id=fabb3421-8b34-484b-bc17-e46de9703418&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient # # What this script does after consent: # 1. Finds the Claude-MSP-Access service principal in the customer tenant # 2. Activates Exchange Administrator directory role (if not active) # 3. Assigns Exchange Administrator to Claude's SP (via CIPP Graph proxy) # 4. Verifies all access: Graph, Exchange, Mail, Security, Intune param( [Parameter(Mandatory=$true)] [string]$TenantDomain ) $ErrorActionPreference = "Stop" # --- Credentials --- $cippUrl = "https://cippcanvb.azurewebsites.net" $cippTenantId = "ce61461e-81a0-4c84-bb4a-7b354a9a356d" $cippClientId = "420cb849-542d-4374-9cb2-3d8ae0e1835b" $cippSecret = "MOn8Q~otmxJPLvmL~_aCVTV8Va4t4~SrYrukGbJT" $claudeAppId = "fabb3421-8b34-484b-bc17-e46de9703418" $claudeSecret = "~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO" Write-Output "=========================================" Write-Output " Claude-MSP-Access - Tenant Onboarding" Write-Output " Tenant: $TenantDomain" Write-Output " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" Write-Output "=========================================" # --- STEP 1: Get CIPP API token --- Write-Output "`n[STEP 1] Getting CIPP API token..." $tokenBody = @{ client_id = $cippClientId client_secret = $cippSecret scope = "api://$cippClientId/.default" grant_type = "client_credentials" } $cippToken = (Invoke-RestMethod -Uri "https://login.microsoftonline.com/$cippTenantId/oauth2/v2.0/token" -Method POST -Body $tokenBody).access_token $cippHeaders = @{ "Authorization" = "Bearer $cippToken" } Write-Output "[OK] CIPP token acquired" # --- STEP 2: Find Claude SP in customer tenant via CIPP --- Write-Output "`n[STEP 2] Finding Claude-MSP-Access service principal..." $spFilter = [uri]::EscapeDataString("appId eq '$claudeAppId'") $spResult = Invoke-RestMethod -Uri "$cippUrl/api/ListGraphRequest?TenantFilter=$TenantDomain&Endpoint=servicePrincipals&`$filter=$spFilter" -Headers $cippHeaders $sp = $spResult.Results | Select-Object -First 1 if (-not $sp) { Write-Output "[ERROR] Claude-MSP-Access SP not found in $TenantDomain" Write-Output "[INFO] Has admin consent been completed? Use this URL:" Write-Output " https://login.microsoftonline.com/common/adminconsent?client_id=$claudeAppId&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient" exit 1 } $spId = $sp.id Write-Output "[OK] Found SP: $($sp.displayName) (ID: $spId)" # --- STEP 3: Get Exchange Administrator role ID --- Write-Output "`n[STEP 3] Finding Exchange Administrator role..." $rolesResult = Invoke-RestMethod -Uri "$cippUrl/api/ListGraphRequest?TenantFilter=$TenantDomain&Endpoint=directoryRoles" -Headers $cippHeaders $exoRole = $rolesResult.Results | Where-Object { $_.displayName -eq "Exchange Administrator" } if (-not $exoRole) { Write-Output "[INFO] Exchange Admin role not activated, activating from template..." # Exchange Administrator role template ID is always 29232cdf-9323-42fd-ade2-1d097af3e4de $activateBody = [uri]::EscapeDataString((@{ roleTemplateId = "29232cdf-9323-42fd-ade2-1d097af3e4de" } | ConvertTo-Json -Compress)) $activateResult = Invoke-RestMethod -Uri "$cippUrl/api/ListGraphRequest?TenantFilter=$TenantDomain&Endpoint=directoryRoles&type=POST&body=$activateBody" -Headers $cippHeaders # Re-fetch roles $rolesResult = Invoke-RestMethod -Uri "$cippUrl/api/ListGraphRequest?TenantFilter=$TenantDomain&Endpoint=directoryRoles" -Headers $cippHeaders $exoRole = $rolesResult.Results | Where-Object { $_.displayName -eq "Exchange Administrator" } } if (-not $exoRole) { Write-Output "[ERROR] Could not find or activate Exchange Administrator role" exit 1 } $exoRoleId = $exoRole.id Write-Output "[OK] Exchange Admin role: $exoRoleId" # --- STEP 4: Assign Exchange Administrator to Claude SP --- Write-Output "`n[STEP 4] Assigning Exchange Administrator role..." $assignEndpoint = [uri]::EscapeDataString("directoryRoles/$exoRoleId/members/`$ref") $assignBody = [uri]::EscapeDataString((@{ "@odata.id" = "https://graph.microsoft.com/v1.0/servicePrincipals/$spId" } | ConvertTo-Json -Compress)) try { $assignResult = Invoke-RestMethod -Uri "$cippUrl/api/ListGraphRequest?TenantFilter=$TenantDomain&Endpoint=$assignEndpoint&type=POST&body=$assignBody" -Headers $cippHeaders if ($assignResult.Results.CippStatus -eq "Good") { Write-Output "[OK] Exchange Administrator assigned to Claude-MSP-Access" } else { Write-Output "[INFO] Assignment result: $($assignResult.Results | ConvertTo-Json -Compress)" } } catch { $errMsg = $_.Exception.Message if ($errMsg -match "already exist") { Write-Output "[OK] Exchange Administrator already assigned" } else { Write-Output "[WARNING] Role assignment: $errMsg" } } # --- STEP 5: Verify Claude API access --- Write-Output "`n[STEP 5] Verifying Claude-MSP-Access API connectivity..." # Get tenant ID from CIPP $selectFields = [uri]::EscapeDataString("id,displayName") $orgResult = Invoke-RestMethod -Uri "$cippUrl/api/ListGraphRequest?TenantFilter=$TenantDomain&Endpoint=organization&`$select=$selectFields" -Headers $cippHeaders $customerTenantId = $orgResult.Results[0].id Write-Output "[INFO] Tenant ID: $customerTenantId" # Get Claude token for this tenant $claudeTokenBody = @{ client_id = $claudeAppId client_secret = $claudeSecret scope = "https://graph.microsoft.com/.default" grant_type = "client_credentials" } try { $claudeToken = (Invoke-RestMethod -Uri "https://login.microsoftonline.com/$customerTenantId/oauth2/v2.0/token" -Method POST -Body $claudeTokenBody).access_token Write-Output "[OK] Claude Graph token acquired" } catch { Write-Output "[ERROR] Could not get Claude token - admin consent may not be complete" Write-Output " $($_.Exception.Message)" exit 1 } $claudeHeaders = @{ "Authorization" = "Bearer $claudeToken"; "Content-Type" = "application/json" } # Test endpoints $tests = @( @{ Name = "Users"; Uri = "https://graph.microsoft.com/v1.0/users?`$top=1&`$select=displayName" }, @{ Name = "Security"; Uri = "https://graph.microsoft.com/v1.0/security/alerts?`$top=1" }, @{ Name = "AuditLogs"; Uri = "https://graph.microsoft.com/v1.0/auditLogs/signIns?`$top=1" }, @{ Name = "Policies"; Uri = "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" }, @{ Name = "Devices"; Uri = "https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?`$top=1" } ) foreach ($test in $tests) { try { $r = Invoke-RestMethod -Uri $test.Uri -Headers $claudeHeaders -ErrorAction Stop Write-Output " [OK] $($test.Name)" } catch { $code = $_.Exception.Response.StatusCode.value__ Write-Output " [FAIL] $($test.Name): HTTP $code" } } # Test Exchange Online REST Write-Output "`n Testing Exchange Online REST API..." try { $exoTokenBody = @{ client_id = $claudeAppId client_secret = $claudeSecret scope = "https://outlook.office365.com/.default" grant_type = "client_credentials" } $exoToken = (Invoke-RestMethod -Uri "https://login.microsoftonline.com/$customerTenantId/oauth2/v2.0/token" -Method POST -Body $exoTokenBody).access_token $exoHeaders = @{ "Authorization" = "Bearer $exoToken"; "Content-Type" = "application/json" } $invokeUrl = "https://outlook.office365.com/adminapi/beta/$customerTenantId/InvokeCommand" $getMailbox = @{ CmdletInput = @{ CmdletName = "Get-Mailbox" Parameters = @{ ResultSize = "1" } } } | ConvertTo-Json -Depth 5 $r = Invoke-RestMethod -Uri $invokeUrl -Headers $exoHeaders -Method POST -Body $getMailbox -ErrorAction Stop Write-Output " [OK] Exchange Online (Get-Mailbox)" } catch { Write-Output " [FAIL] Exchange Online: $($_.Exception.Message)" } # --- DONE --- Write-Output "`n=========================================" Write-Output " ONBOARDING COMPLETE: $TenantDomain" Write-Output " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" Write-Output "=========================================" Write-Output "" Write-Output "Claude-MSP-Access is fully operational for this tenant." Write-Output "Capabilities: User mgmt, mail access, security alerts," Write-Output "audit logs, conditional access, Intune, Exchange admin," Write-Output "litigation hold, and all CIPP SAM operations."