<# .SYNOPSIS Legt die App-Registrierung fuer den Intune Manager an (oder aktualisiert sie) inkl. Graph-Berechtigungen, Public-Client-Flow und Redirect-URIs. .DESCRIPTION Deckt genau die Punkte ab, an denen der erste Login sonst mit AADSTS500113 / AADSTS50011 scheitert: * Delegierte Microsoft-Graph-Berechtigungen (nach Bedarf: RW, RO, Geraete) * "Oeffentliche Clientflows zulassen" (isFallbackPublicClient = true) * Redirect-URIs fuer Device-Code (nativeclient) und WAM-Broker * optional Admin-Consent Nutzt nur Microsoft.Graph.Authentication (Invoke-MgGraphRequest) — dieselbe Abhaengigkeit wie das Tool selbst. Berechtigungs-IDs werden LIVE aus dem Graph-Service-Principal aufgeloest, es sind also keine fest verdrahteten GUIDs noetig (die sonst leicht veralten). .PARAMETER DisplayName Anzeigename der App-Registrierung. Default: "Intune Manager". .PARAMETER ClientId AppId einer BESTEHENDEN Registrierung, die aktualisiert werden soll. Ohne diesen Parameter wird eine NEUE App angelegt. .PARAMETER MultiTenant App fuer mehrere Tenants (signInAudience = AzureADMultipleOrgs). Default: nur der aktuelle Tenant (AzureADMyOrg). .PARAMETER ReadOnly Verwendet die Read-Only-Berechtigungen (fuer eine reine Anzeige-/RO-App, passend zu clientIdRo im Tool). .PARAMETER IncludeDeviceActions Nimmt zusaetzlich die Berechtigungen fuer den Geraete-Tab auf (Read + ReadWrite + PrivilegedOperations = Sync/Reboot/Lock/Wipe/Retire). .PARAMETER GrantAdminConsent Erteilt direkt tenantweiten Admin-Consent fuer die gesetzten Scopes. Benoetigt entsprechend privilegierte Anmeldung. .PARAMETER EmitManifest Gibt zusaetzlich den requiredResourceAccess-Block als JSON aus (zum manuellen Einfuegen in das App-Manifest im Portal). .EXAMPLE # Neue Single-Tenant-App inkl. Consent: .\Setup-AppRegistration.ps1 -GrantAdminConsent .EXAMPLE # Bestehende App um Geraete-Rechte erweitern: .\Setup-AppRegistration.ps1 -ClientId "51477347-...." -IncludeDeviceActions -GrantAdminConsent .NOTES Vorher einmalig: Install-Module Microsoft.Graph.Authentication -Scope CurrentUser #> [CmdletBinding()] param( [string]$DisplayName = 'Intune Manager', [string]$ClientId, [switch]$MultiTenant, [switch]$ReadOnly, [switch]$IncludeDeviceActions, [switch]$IncludeOffboarding, [switch]$GrantAdminConsent, [switch]$EmitManifest ) $ErrorActionPreference = 'Stop' $GraphAppId = '00000003-0000-0000-c000-000000000000' # Microsoft Graph # --- Benoetigte delegierte Berechtigungen zusammenstellen --------------------- $perms = if ($ReadOnly) { @('Group.Read.All','GroupMember.Read.All','User.Read.All', 'DeviceManagementApps.Read.All','DeviceManagementConfiguration.Read.All','offline_access') } else { @('Group.ReadWrite.All','GroupMember.ReadWrite.All','User.Read.All', 'DeviceManagementApps.ReadWrite.All','DeviceManagementConfiguration.ReadWrite.All','offline_access') } if ($IncludeDeviceActions) { $perms += if ($ReadOnly) { @('DeviceManagementManagedDevices.Read.All') } else { @('DeviceManagementManagedDevices.Read.All', 'DeviceManagementManagedDevices.ReadWrite.All', 'DeviceManagementManagedDevices.PrivilegedOperations.All') } } if ($IncludeOffboarding) { # Geraete ueber Intune/Autopilot/Entra entfernen + Recovery-Keys lesen. # ACHTUNG: Loeschen braucht zusaetzlich Verzeichnis-/Intune-ROLLEN (Cloud # Device Administrator / Intune Administrator) — nicht nur diese Scopes. $perms += @( 'Device.ReadWrite.All', 'DeviceManagementManagedDevices.ReadWrite.All', 'DeviceManagementServiceConfig.ReadWrite.All', 'BitlockerKey.Read.All', 'DeviceLocalCredential.Read.All' ) } $perms = $perms | Select-Object -Unique # --- Verbinden ---------------------------------------------------------------- Write-Host "[1/6] Mit Microsoft Graph verbinden (Admin noetig)..." -ForegroundColor Cyan $connectScopes = @('Application.ReadWrite.All') if ($GrantAdminConsent) { $connectScopes += 'DelegatedPermissionGrant.ReadWrite.All' } Import-Module Microsoft.Graph.Authentication -ErrorAction Stop Connect-MgGraph -Scopes $connectScopes -NoWelcome $ctx = Get-MgContext if (-not $ctx) { throw 'Keine Graph-Verbindung.' } Write-Host " verbunden mit Tenant $($ctx.TenantId) als $($ctx.Account)" -ForegroundColor DarkGray # --- Graph-Service-Principal + Berechtigungs-IDs aufloesen -------------------- Write-Host "[2/6] Graph-Berechtigungen aufloesen..." -ForegroundColor Cyan $graphSp = (Invoke-MgGraphRequest -Method GET ` -Uri "https://graph.microsoft.com/v1.0/servicePrincipals?`$filter=appId eq '$GraphAppId'&`$select=id,oauth2PermissionScopes").value | Select-Object -First 1 if (-not $graphSp) { throw 'Graph-Service-Principal nicht gefunden.' } $graphSpId = $graphSp.id $scopeMap = @{} foreach ($s in $graphSp.oauth2PermissionScopes) { $scopeMap[$s.value] = $s.id } $resourceAccess = @() foreach ($p in $perms) { if (-not $scopeMap.ContainsKey($p)) { throw "Delegierte Berechtigung '$p' nicht im Graph-SP gefunden." } $resourceAccess += @{ id = $scopeMap[$p]; type = 'Scope' } } $requiredResourceAccess = @(@{ resourceAppId = $GraphAppId; resourceAccess = $resourceAccess }) if ($EmitManifest) { Write-Host "`n--- requiredResourceAccess (Manifest) ---" -ForegroundColor Yellow ($requiredResourceAccess | ConvertTo-Json -Depth 6) Write-Host "-----------------------------------------`n" -ForegroundColor Yellow } # --- App anlegen oder aktualisieren ------------------------------------------- $signInAudience = if ($MultiTenant) { 'AzureADMultipleOrgs' } else { 'AzureADMyOrg' } if ($ClientId) { Write-Host "[3/6] Bestehende App $ClientId laden..." -ForegroundColor Cyan $app = (Invoke-MgGraphRequest -Method GET ` -Uri "https://graph.microsoft.com/v1.0/applications?`$filter=appId eq '$ClientId'&`$select=id,appId").value | Select-Object -First 1 if (-not $app) { throw "App mit appId $ClientId nicht gefunden." } $objId = $app.id $appId = $ClientId $patch = @{ signInAudience = $signInAudience isFallbackPublicClient = $true requiredResourceAccess = $requiredResourceAccess publicClient = @{ redirectUris = @( 'https://login.microsoftonline.com/common/oauth2/nativeclient' "ms-appx-web://Microsoft.AAD.BrokerPlugin/$appId" ) } } Invoke-MgGraphRequest -Method PATCH -Uri "https://graph.microsoft.com/v1.0/applications/$objId" ` -Body ($patch | ConvertTo-Json -Depth 8) -ContentType 'application/json' | Out-Null Write-Host " App aktualisiert." -ForegroundColor Green } else { Write-Host "[3/6] Neue App '$DisplayName' anlegen..." -ForegroundColor Cyan # Broker-Redirect-URI braucht die appId -> erst mit nativeclient anlegen, # danach die Broker-URI per PATCH ergaenzen. $create = @{ displayName = $DisplayName signInAudience = $signInAudience isFallbackPublicClient = $true requiredResourceAccess = $requiredResourceAccess publicClient = @{ redirectUris = @('https://login.microsoftonline.com/common/oauth2/nativeclient') } } $app = Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/v1.0/applications' ` -Body ($create | ConvertTo-Json -Depth 8) -ContentType 'application/json' $objId = $app.id $appId = $app.appId $patch = @{ publicClient = @{ redirectUris = @( 'https://login.microsoftonline.com/common/oauth2/nativeclient' "ms-appx-web://Microsoft.AAD.BrokerPlugin/$appId" ) } } Invoke-MgGraphRequest -Method PATCH -Uri "https://graph.microsoft.com/v1.0/applications/$objId" ` -Body ($patch | ConvertTo-Json -Depth 6) -ContentType 'application/json' | Out-Null Write-Host " App angelegt: appId $appId" -ForegroundColor Green } # --- Service-Principal (Enterprise-App) sicherstellen ------------------------- Write-Host "[4/6] Service-Principal sicherstellen..." -ForegroundColor Cyan $sp = (Invoke-MgGraphRequest -Method GET ` -Uri "https://graph.microsoft.com/v1.0/servicePrincipals?`$filter=appId eq '$appId'&`$select=id").value | Select-Object -First 1 if (-not $sp) { $sp = Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/v1.0/servicePrincipals' ` -Body (@{ appId = $appId } | ConvertTo-Json) -ContentType 'application/json' } $spId = $sp.id # --- Optional: Admin-Consent -------------------------------------------------- Write-Host "[5/6] Admin-Consent..." -ForegroundColor Cyan if ($GrantAdminConsent) { $scopeString = ($perms -join ' ') # Bestehenden Grant fuer (Client -> Graph) suchen und ersetzen, sonst neu. $existing = (Invoke-MgGraphRequest -Method GET ` -Uri "https://graph.microsoft.com/v1.0/oauth2PermissionGrants?`$filter=clientId eq '$spId' and resourceId eq '$graphSpId'").value | Select-Object -First 1 $grantBody = @{ clientId = $spId consentType = 'AllPrincipals' resourceId = $graphSpId scope = $scopeString } if ($existing) { Invoke-MgGraphRequest -Method PATCH -Uri "https://graph.microsoft.com/v1.0/oauth2PermissionGrants/$($existing.id)" ` -Body (@{ scope = $scopeString } | ConvertTo-Json) -ContentType 'application/json' | Out-Null } else { Invoke-MgGraphRequest -Method POST -Uri 'https://graph.microsoft.com/v1.0/oauth2PermissionGrants' ` -Body ($grantBody | ConvertTo-Json) -ContentType 'application/json' | Out-Null } Write-Host " Admin-Consent erteilt fuer: $scopeString" -ForegroundColor Green } else { Write-Host " uebersprungen (-GrantAdminConsent nicht gesetzt)." -ForegroundColor DarkGray Write-Host " Consent im Portal: Entra -> App-Registrierungen -> $DisplayName -> API-Berechtigungen -> Administratorzustimmung erteilen" -ForegroundColor DarkGray } # --- Ergebnis ----------------------------------------------------------------- Write-Host "[6/6] Fertig." -ForegroundColor Cyan Write-Host "" Write-Host " Im Intune Manager eintragen:" -ForegroundColor White Write-Host " Tenant ID : $($ctx.TenantId)" Write-Host " Client ID : $appId" Write-Host "" Write-Host " Gesetzte delegierte Berechtigungen:" -ForegroundColor White $perms | ForEach-Object { Write-Host " - $_" }