Der Autopilot-Status im Geräte-Detail (Profil-Zuweisung / letzter Kontakt) braucht DeviceManagementServiceConfig.Read.All. Das Flag -IncludeDeviceActions fügt den Scope jetzt hinzu (Read/Write und Read-Only). Ist ohnehin Offboarding aktiv, wird die ReadWrite-Variante bevorzugt und die Read-Variante entfernt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
239 lines
11 KiB
PowerShell
239 lines
11 KiB
PowerShell
<#
|
|
.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)
|
|
sowie DeviceManagementServiceConfig.Read.All fuer den Autopilot-Status
|
|
im Detail-Panel.
|
|
|
|
.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) {
|
|
# DeviceManagementServiceConfig.Read.All: Autopilot-Status im Geraete-Detail
|
|
# (Profil-Zuweisung / letzter Kontakt) — ansonsten bleibt das Feld leer.
|
|
$perms += if ($ReadOnly) {
|
|
@('DeviceManagementManagedDevices.Read.All',
|
|
'DeviceManagementServiceConfig.Read.All')
|
|
} else {
|
|
@('DeviceManagementManagedDevices.Read.All',
|
|
'DeviceManagementManagedDevices.ReadWrite.All',
|
|
'DeviceManagementManagedDevices.PrivilegedOperations.All',
|
|
'DeviceManagementServiceConfig.Read.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
|
|
# ServiceConfig: ReadWrite (Offboarding) schliesst Read (Autopilot-Anzeige) ein
|
|
# -> die Read-Variante nicht zusaetzlich anfordern.
|
|
if ($perms -contains 'DeviceManagementServiceConfig.ReadWrite.All') {
|
|
$perms = @($perms | Where-Object { $_ -ne 'DeviceManagementServiceConfig.Read.All' })
|
|
}
|
|
|
|
# --- 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 " - $_" }
|