v0.1.27 — App-Registrierungs-Doku + Setup-Skript, Version-Bump
Build & Release MSI / build-msi (push) Canceled after 0s
Build & Release MSI / build-msi (push) Canceled after 0s
- docs/App-Registration.md: alle benoetigten Graph-Berechtigungen (Kern, Geraete-Tab, Read-Only) + Auth-Konfiguration - docs/Setup-AppRegistration.ps1: legt die App-Registrierung automatisch an (Rechte, Public-Client-Flow, Redirect-URIs, optional Admin-Consent); Berechtigungs-IDs werden live aufgeloest - README/CHANGELOG verlinkt, ToolVersion + build-local auf 0.1.27 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+3
-2
@@ -5,9 +5,9 @@ Versionierung folgt [Semantic Versioning](https://semver.org/lang/de/) — solan
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [Unreleased]
|
## [0.1.27] - 2026-08-23
|
||||||
|
|
||||||
Policy-Import härter, Administrative Vorlagen, Zuweisung nach Import, Git-Snapshot.
|
Policy-Import härter, Administrative Vorlagen, Zuweisung nach Import, Git-Snapshot, App-Registrierungs-Doku.
|
||||||
|
|
||||||
### Neu
|
### Neu
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ Policy-Import härter, Administrative Vorlagen, Zuweisung nach Import, Git-Snaps
|
|||||||
- **Zuweisung direkt nach Import**: Dialog listet die neu angelegten Policies und lässt **pro Policy Include-/Exclude-Gruppen** im Ziel-Tenant zuweisen (Typeahead über die vorhandene Gruppensuche). Neuer Endpoint `POST /api/policies/assign` (nutzt die typ-spezifische `/assign`-Action für alle vier Typen).
|
- **Zuweisung direkt nach Import**: Dialog listet die neu angelegten Policies und lässt **pro Policy Include-/Exclude-Gruppen** im Ziel-Tenant zuweisen (Typeahead über die vorhandene Gruppensuche). Neuer Endpoint `POST /api/policies/assign` (nutzt die typ-spezifische `/assign`-Action für alle vier Typen).
|
||||||
- **Git-Snapshot (versioniertes Policy-Backup)**: Button „Git-Snapshot" schreibt alle Policies aller Typen als JSON in einen konfigurierten lokalen Git-Ordner und committet sie (optional `git push` über den vorhandenen Credential-Helper). Stabile Dateinamen + deterministische, zeitstempel-freie Ausgabe → saubere Diffs. Neue Settings-Sektion `policyBackup` (`gitRepoPath`, `push`).
|
- **Git-Snapshot (versioniertes Policy-Backup)**: Button „Git-Snapshot" schreibt alle Policies aller Typen als JSON in einen konfigurierten lokalen Git-Ordner und committet sie (optional `git push` über den vorhandenen Credential-Helper). Stabile Dateinamen + deterministische, zeitstempel-freie Ausgabe → saubere Diffs. Neue Settings-Sektion `policyBackup` (`gitRepoPath`, `push`).
|
||||||
- Import akzeptiert jetzt auch **Git-Snapshot-Dateien** (Feld `policyType` zusätzlich zu `exportType`).
|
- Import akzeptiert jetzt auch **Git-Snapshot-Dateien** (Feld `policyType` zusätzlich zu `exportType`).
|
||||||
|
- **Doku + Provisioning-Skript für die App-Registrierung**: [`docs/App-Registration.md`](docs/App-Registration.md) listet alle benötigten delegierten Graph-Berechtigungen (Kern, Geräte-Tab, Read-Only) samt Auth-Konfiguration; [`docs/Setup-AppRegistration.ps1`](docs/Setup-AppRegistration.ps1) legt die Registrierung automatisch an bzw. aktualisiert sie (Rechte, Public-Client-Flow, Redirect-URIs, optional Admin-Consent) — Berechtigungs-IDs werden live aufgelöst, keine fest verdrahteten GUIDs.
|
||||||
|
|
||||||
### Behoben
|
### Behoben
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,11 @@ Settings → Verbindung
|
|||||||
> ⚠ Ohne `DeviceManagementManagedDevices.Read.All` zeigt der App-Report nur
|
> ⚠ Ohne `DeviceManagementManagedDevices.Read.All` zeigt der App-Report nur
|
||||||
> Zaehler = 0 und der Geraete-Export liefert keine Eintraege.
|
> Zaehler = 0 und der Geraete-Export liefert keine Eintraege.
|
||||||
|
|
||||||
|
**Vollstaendige Berechtigungsliste, Redirect-URIs und ein Skript, das die
|
||||||
|
App-Registrierung automatisch anlegt/konfiguriert:**
|
||||||
|
[docs/App-Registration.md](docs/App-Registration.md) — inkl.
|
||||||
|
[`Setup-AppRegistration.ps1`](docs/Setup-AppRegistration.ps1).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Navigation (Tabs)
|
## Navigation (Tabs)
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ $script:State = [pscustomobject]@{
|
|||||||
Session = @() # geplante Zuweisungen
|
Session = @() # geplante Zuweisungen
|
||||||
}
|
}
|
||||||
|
|
||||||
$script:ToolVersion = "0.1.26"
|
$script:ToolVersion = "0.1.27"
|
||||||
$script:BuildStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
$script:BuildStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# App-Registrierung & Berechtigungen
|
||||||
|
|
||||||
|
Der Intune Manager meldet sich als **delegierte** Anwendung an (Device-Code- bzw.
|
||||||
|
WAM-Login) — er handelt also immer **im Namen des angemeldeten Admins**, nie mit
|
||||||
|
Anwendungsrechten. Die App-Registrierung braucht daher **delegierte
|
||||||
|
Microsoft-Graph-Berechtigungen** plus die passende Authentifizierungs-Konfiguration.
|
||||||
|
|
||||||
|
> **Schnellweg:** Statt alles von Hand zu klicken, legt das Skript
|
||||||
|
> [`Setup-AppRegistration.ps1`](Setup-AppRegistration.ps1) die Registrierung
|
||||||
|
> komplett an (Rechte + Public-Client-Flow + Redirect-URIs + optional Consent).
|
||||||
|
> Siehe [Automatische Einrichtung](#automatische-einrichtung).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Benötigte Berechtigungen
|
||||||
|
|
||||||
|
Alle Berechtigungen sind vom Typ **Delegiert** (Microsoft Graph).
|
||||||
|
|
||||||
|
### Kern (immer erforderlich)
|
||||||
|
|
||||||
|
| Berechtigung | Wofür |
|
||||||
|
|---|---|
|
||||||
|
| `Group.ReadWrite.All` | Gruppen anlegen, App-/Policy-Zuweisungen setzen |
|
||||||
|
| `GroupMember.ReadWrite.All` | Gruppenmitglieder lesen/hinzufügen/entfernen, CSV-Import |
|
||||||
|
| `User.Read.All` | Benutzersuche, UPN-Auflösung |
|
||||||
|
| `DeviceManagementApps.ReadWrite.All` | Apps auflisten, zuweisen, umbenennen, löschen, Setup-Datei aktualisieren, App-Report |
|
||||||
|
| `DeviceManagementConfiguration.ReadWrite.All` | Policies (Settings Catalog, Compliance, Konfigurationsprofile, Administrative Vorlagen) lesen, exportieren, importieren, zuweisen |
|
||||||
|
| `offline_access` | Refresh-Token (Device-Code-Flow) |
|
||||||
|
|
||||||
|
> `DeviceManagementConfiguration.ReadWrite.All` wird vom Tool automatisch zur
|
||||||
|
> Verbindung ergänzt — die App-Registrierung muss die Berechtigung aber besitzen
|
||||||
|
> und (Admin-)zugestimmt bekommen haben, sonst schlägt der Policy-Import mit
|
||||||
|
> `403 Forbidden` fehl.
|
||||||
|
|
||||||
|
### Geräte-Tab (optional)
|
||||||
|
|
||||||
|
Nur nötig, wenn der **Geräte-Tab** (Sync/Reboot/Lock/Diagnose/Wipe/Retire) genutzt wird:
|
||||||
|
|
||||||
|
| Berechtigung | Wofür |
|
||||||
|
|---|---|
|
||||||
|
| `DeviceManagementManagedDevices.Read.All` | Geräte auflisten und Details anzeigen |
|
||||||
|
| `DeviceManagementManagedDevices.ReadWrite.All` | Sync, Neustart, Remote-Lock, Diagnose, BitLocker-Key-Rotation |
|
||||||
|
| `DeviceManagementManagedDevices.PrivilegedOperations.All` | Wipe, Retire, Autopilot-Reset |
|
||||||
|
|
||||||
|
### Read-Only-Variante
|
||||||
|
|
||||||
|
Für eine reine Anzeige-App (im Tool als `clientIdRo` hinterlegbar) genügen die
|
||||||
|
`*.Read.All`-Pendants:
|
||||||
|
|
||||||
|
`Group.Read.All`, `GroupMember.Read.All`, `User.Read.All`,
|
||||||
|
`DeviceManagementApps.Read.All`, `DeviceManagementConfiguration.Read.All`,
|
||||||
|
`offline_access` (+ optional `DeviceManagementManagedDevices.Read.All`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Authentifizierungs-Konfiguration
|
||||||
|
|
||||||
|
Zusätzlich zu den Berechtigungen muss die App als **öffentlicher Client** nutzbar
|
||||||
|
sein — sonst scheitert der erste Login (`AADSTS500113` bzw. `AADSTS50011`):
|
||||||
|
|
||||||
|
1. **Öffentliche Clientflows zulassen** → **Ja**
|
||||||
|
(Entra → App-Registrierung → *Authentifizierung* → ganz unten;
|
||||||
|
im Manifest `isFallbackPublicClient = true`).
|
||||||
|
2. **Redirect-URIs** unter *Mobilgerät- und Desktopanwendungen*:
|
||||||
|
```
|
||||||
|
https://login.microsoftonline.com/common/oauth2/nativeclient
|
||||||
|
ms-appx-web://Microsoft.AAD.BrokerPlugin/<CLIENT-ID>
|
||||||
|
```
|
||||||
|
- Zeile 1 → **Device-Code-Flow**
|
||||||
|
- Zeile 2 → **WAM-Broker** (Windows-Anmeldefenster); `<CLIENT-ID>` ist die
|
||||||
|
AppId der Registrierung selbst.
|
||||||
|
3. **Multi-Tenant:** Wird dieselbe App gegen fremde Tenants genutzt, muss
|
||||||
|
`signInAudience` auf *Accounts in any organizational directory*
|
||||||
|
(`AzureADMultipleOrgs`) stehen und im Ziel-Tenant per Admin-Consent
|
||||||
|
bereitgestellt werden. Bei einer eigenen App **pro** Tenant ist Single-Tenant
|
||||||
|
ausreichend.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Automatische Einrichtung
|
||||||
|
|
||||||
|
Das Skript [`Setup-AppRegistration.ps1`](Setup-AppRegistration.ps1) erledigt alles
|
||||||
|
oben Genannte über Microsoft Graph. Es braucht nur das Modul
|
||||||
|
`Microsoft.Graph.Authentication` (dieselbe Abhängigkeit wie das Tool) und löst die
|
||||||
|
Berechtigungs-IDs **live** aus dem Graph-Service-Principal auf — keine fest
|
||||||
|
verdrahteten GUIDs.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Einmalig:
|
||||||
|
Install-Module Microsoft.Graph.Authentication -Scope CurrentUser
|
||||||
|
|
||||||
|
# Neue Single-Tenant-App inkl. Admin-Consent:
|
||||||
|
.\docs\Setup-AppRegistration.ps1 -GrantAdminConsent
|
||||||
|
|
||||||
|
# Mit Geräte-Rechten:
|
||||||
|
.\docs\Setup-AppRegistration.ps1 -IncludeDeviceActions -GrantAdminConsent
|
||||||
|
|
||||||
|
# Multi-Tenant-App:
|
||||||
|
.\docs\Setup-AppRegistration.ps1 -MultiTenant -GrantAdminConsent
|
||||||
|
|
||||||
|
# Bestehende App nur um Rechte/Redirect-URIs ergänzen:
|
||||||
|
.\docs\Setup-AppRegistration.ps1 -ClientId "<APP-ID>" -GrantAdminConsent
|
||||||
|
|
||||||
|
# Nur das Manifest-Snippet ausgeben (ohne etwas anzulegen wäre -WhatIf-artig —
|
||||||
|
# hier zusätzlich zum Anlegen):
|
||||||
|
.\docs\Setup-AppRegistration.ps1 -EmitManifest
|
||||||
|
```
|
||||||
|
|
||||||
|
Am Ende gibt das Skript **Tenant ID** und **Client ID** aus — diese Werte im
|
||||||
|
Intune Manager unter *Einstellungen → Verbindung* (bzw. im Tenant-Profil)
|
||||||
|
eintragen.
|
||||||
|
|
||||||
|
Die zum Anlegen/Ändern nötigen Admin-Rechte (`Application.ReadWrite.All`, für
|
||||||
|
Consent zusätzlich `DelegatedPermissionGrant.ReadWrite.All`) werden beim
|
||||||
|
`Connect-MgGraph`-Aufruf des Skripts einmalig abgefragt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manuelle Einrichtung (Portal)
|
||||||
|
|
||||||
|
1. **Entra** → *App-Registrierungen* → *Neue Registrierung* → Name vergeben,
|
||||||
|
Kontotyp wählen (Single- oder Multi-Tenant) → **Registrieren**.
|
||||||
|
2. *API-Berechtigungen* → *Berechtigung hinzufügen* → **Microsoft Graph** →
|
||||||
|
**Delegierte Berechtigungen** → die [Kern-Berechtigungen](#kern-immer-erforderlich)
|
||||||
|
(und bei Bedarf die [Geräte-Berechtigungen](#geräte-tab-optional)) auswählen →
|
||||||
|
**Administratorzustimmung erteilen**.
|
||||||
|
3. *Authentifizierung* → [Authentifizierungs-Konfiguration](#authentifizierungs-konfiguration)
|
||||||
|
wie oben setzen (Public-Client-Flow + beide Redirect-URIs).
|
||||||
|
4. **Übersichtsseite**: *Anwendungs-(Client-)ID* und *Verzeichnis-(Mandanten-)ID*
|
||||||
|
in den Intune Manager übernehmen.
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
<#
|
||||||
|
.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]$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')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$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 " - $_" }
|
||||||
@@ -12,7 +12,7 @@ if (Test-Path "$x64Dotnet\dotnet.exe") {
|
|||||||
$env:DOTNET_ROOT = "C:\Program Files\dotnet"
|
$env:DOTNET_ROOT = "C:\Program Files\dotnet"
|
||||||
}
|
}
|
||||||
|
|
||||||
$version = "0.1.26"
|
$version = "0.1.27"
|
||||||
$src = "\\Mac\Home\ClaudePRJ\Intune Manager"
|
$src = "\\Mac\Home\ClaudePRJ\Intune Manager"
|
||||||
$stage = "$env:TEMP\IntuneManagerStage"
|
$stage = "$env:TEMP\IntuneManagerStage"
|
||||||
$installerDir = "$src\installer"
|
$installerDir = "$src\installer"
|
||||||
|
|||||||
Reference in New Issue
Block a user