# Intune Manager – Web Edition Modernes Web-Frontend fuer den Intune Manager. PowerShell startet einen lokalen HTTP-Server, der das Frontend ausliefert und die Microsoft Graph-Aufrufe ausfuehrt. ## Voraussetzungen - Windows mit PowerShell 5.1 oder PowerShell 7+ - Modul `Microsoft.Graph.Authentication` (wird beim ersten Connect benoetigt): ```powershell Install-Module Microsoft.Graph.Authentication -Scope CurrentUser -Force ``` - Browser (Edge, Chrome, Firefox) ## Start ```powershell .\Start.ps1 ``` Optionen: | Parameter | Standard | Beschreibung | |---------------|----------|-----------------------------------------------------------| | `-Port` | `8077` | Port des lokalen HTTP-Servers | | `-TenantId` | — | Ueberschreibt Tenant-ID aus Settings (nur dieser Start) | | `-ClientId` | — | Ueberschreibt Client-ID aus Settings (nur dieser Start) | | `-NoBrowser` | aus | Browser nicht automatisch oeffnen | Beispiele: ```powershell .\Start.ps1 -Port 8088 .\Start.ps1 -NoBrowser ``` Beenden: `Ctrl+C` im Terminal. --- ## Single- vs. Multi-Tenant Unter **Settings → Verbindung → Verbindungsmodus** wählst du zwischen: | Modus | Verhalten | |---------------|---------------------------------------------------------------------------| | Single-Tenant | Klassisch: ein Mandant (Tenant ID + App-Registrierung). Standard. | | Multi-Tenant | Mehrere Mandanten mit **je eigener App-Registrierung**; Umschalten oben rechts | Im Multi-Tenant-Modus legst du pro Mandant ein Profil an (Bezeichnung, Tenant ID, Client-ID Read/Write, optional Client-ID Read-Only). Der **aktive** Mandant ist markiert. Über das **Dropdown in der Topbar** wechselst du zwischen den Mandanten — der Wechsel trennt die aktuelle Verbindung und verbindet **sofort neu** mit dem gewählten Tenant (Login-Prompt erscheint). Alle Caches werden beim Wechsel geleert. > Die Scopes (RW/RO) gelten **global** für alle Profile. Jeder Mandant benötigt > eine eigene App-Registrierung mit denselben delegierten Berechtigungen und > (Admin-)Consent im jeweiligen Tenant. Bestehende Single-Tenant-Konfigurationen > werden verlustfrei übernommen (Umschalten auf Multi bietet an, das vorhandene > Setup als erstes Profil zu übernehmen). ### Pro-Tenant-Vorgaben Jedes Tenant-Profil kann **eigene Vorgaben** hinterlegen (z. B. Kunde A → `abt-hm-*`, Kunde B → `dept-*`): - **Abteilungs-Präfixe** - **Required-Gruppen-Naming** (Präfix/Suffix) - **Available-Gruppen-Naming** (Präfix/Suffix) Bleibt ein Feld **leer, gilt die globale Vorgabe** aus den entsprechenden Einstellungs-Abschnitten (bei Naming greift der Fallback pro Feld einzeln). Beim Mandantenwechsel werden die Caches geleert, sodass die passenden Vorgaben gegen den aktiven Tenant wirken. --- ## Verbindungsmodus (Read/Write vs. Read-Only) Der Server probiert beim Verbinden **zuerst die Read/Write-App-ID** (`clientId`). Schlaegt diese fehl, wird automatisch auf die **Read-Only-App-ID** (`clientIdRo`) zurueckgefallen. | Modus | Anmelde-Ergebnis | Verhalten im Frontend | |-------------|------------------------------------------|------------------------------------------------| | Read/Write | Normale Verbindung | Alle Funktionen verfuegbar | | Read-Only | Fallback auf `clientIdRo` | Schreib-Buttons/Tabs werden ausgeblendet | ### Was wird im Read-Only-Modus ausgeblendet? - App-Zuweisung (Required / Available hinzufuegen und entfernen) - Gruppen erstellen und loeschen - Tab „Benutzer | Gruppen hinzufuegen" im Group Management - Tab „CSV-Import" im Group Management - Session-Ausfuehren-Button - Alle Rename-/Delete-Buttons ### Konfiguration in den Einstellungen ``` Settings → Verbindung Client-ID (Read/Write): Client-ID (Read-Only): (optional) Scopes RW: DeviceManagementApps.ReadWrite.All ... Scopes RO: DeviceManagementApps.Read.All ... ``` ### Benoetigt Graph-Berechtigungen | Funktion | Benoetigt | |----------------------------------|----------------------------------------------| | Apps anzeigen | `DeviceManagementApps.Read.All` | | Gruppen anzeigen / Mitglieder | `GroupMember.Read.All` | | User suchen | `User.Read.All` | | Zuweisungen aendern | `DeviceManagementApps.ReadWrite.All` | | Gruppen-Mitglieder aendern | `GroupMember.ReadWrite.All` | | App-Installationszaehler (Report)| `DeviceManagementManagedDevices.Read.All` ⚠ | | Geraete-Exportliste pro App | `DeviceManagementManagedDevices.Read.All` ⚠ | > ⚠ Ohne `DeviceManagementManagedDevices.Read.All` zeigt der App-Report nur > 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) | Tab | Beschreibung | |--------------------|-------------------------------------------------------------| | App Management | Haupt-Ansicht: Apps laden, Gruppen zuweisen / entfernen | | Group Management | Mitglieder anzeigen/exportieren, Benutzer **und Gruppen** hinzufuegen, CSV-Import | | App Report | Installationszaehler pro App, Geraete-Export als CSV | | Geräte | Geraete suchen, Filter (auch nach **Gruppe**), CSV-Export, **Bulk-Offboarding**, Detail-Panel, Aktionen (Sync, Wipe, …) | | Policies | Policies exportieren/importieren (Neuanlage) und **zusammenfuehren** (Settings Catalog) | --- ## Group Management ### Mitglieder & Export - Gruppe links suchen und auswaehlen - Mitgliederliste (inkl. verschachtelte Gruppen) wird geladen - Filter zum Einschraenken der Anzeige - **Als CSV exportieren** laedt alle Mitglieder als `members--.csv` ### Benutzer | Gruppen hinzufuegen *(nur Read/Write)* - Umschalter **Benutzer / Gruppen** oben im Tab - Benutzer per Name, UPN oder E-Mail suchen — oder Gruppen per Name (verschachtelte Gruppen) - Mehrere Eintraege auswaehlen und gemeinsam zur gewaehlten Gruppe hinzufuegen ### CSV-Import *(nur Read/Write)* Massenhaftes Hinzufuegen von Benutzern zu einer Gruppe per CSV-Datei. **Ablauf:** 1. Gruppe links auswaehlen 2. CSV hochladen **oder** UPNs/E-Mails direkt einfuegen (eine pro Zeile) 3. **„Benutzer aufloesen"** – Graph-Lookup via UPN/Mail → Vorschau mit ✓/✗ 4. **„Gefundene importieren"** – fuegt alle gefundenen Benutzer zur Gruppe hinzu **CSV-Format:** Die erste Zeile wird als optionaler Header erkannt. Spalte mit dem Namen `UserPrincipalName`, `Mail` oder `Email` wird automatisch ausgewaehlt. Ohne Header: erste Spalte mit `@` wird verwendet. ```csv DisplayName,UserPrincipalName,Mail Max Mustermann,max.mustermann@firma.de,max.mustermann@firma.de Erika Muster,erika.muster@firma.de, ``` Oder einfach eine UPN pro Zeile: ``` max.mustermann@firma.de erika.muster@firma.de ``` **Ergebnis-Anzeige nach Import:** | Chip / Status | Bedeutung | |--------------------|------------------------------------------------| | ✔ Gruen | Erfolgreich hinzugefuegt | | ≈ Gelb | Bereits Mitglied (kein Fehler) | | ✗ Rot | Fehler (Meldung in der Tabelle) | | Grau | UPN nicht in Graph gefunden (nicht importiert) | --- ## App Report - **Neu laden**: laedt Installationszaehler fuer alle Apps (kann 1–2 Min. dauern) - Tabelle sortierbar per Klick auf Spaltenheader - Suche filtert nach App-Name, Publisher, Typ - **Als CSV exportieren**: exportiert die gefilterte Tabelle - **Geraete-Export** (pro Zeile): laedt alle Geraete mit Installationsstatus fuer diese App --- ## Policies (Export / Import) Sichert Intune-Policies als JSON und legt sie in einem (ggf. anderen) Tenant neu an. Unterstuetzte Typen: | Typ | Graph-Collection | |-------------------------|-----------------------------| | Settings Catalog | `configurationPolicies` | | Compliance Policies | `deviceCompliancePolicies` | | Konfigurationsprofile | `deviceConfigurations` | ### Export - Tabelle laedt alle drei Typen; Suche + Typ-Filter grenzen ein - Policies per Checkbox auswaehlen → **Export** - **1 Policy** → eine JSON-Datei - **mehrere** → eine gebündelte JSON-Datei (`intune-policies_.json`) - Zusaetzlich wird jeder Export server-seitig unter `%APPDATA%\IntuneAppManager-Web\policy-exports` archiviert ### Import *(nur Read/Write)* - **Import…** → eine oder mehrere JSON-Dateien waehlen (einzeln oder Bundle) - Import legt die Policies **immer als neue Policy** an — bestehende Policies werden nie ueberschrieben - Ergebnis-Toast meldet Erfolg/Fehler pro Policy > Beim Anlegen entfernt der Import instanzgebundene / schreibgeschuetzte Felder > (`id`, Zeitstempel, `version`, Zuweisungen, Status). Compliance-Policies > bekommen bei Bedarf einen Default-`scheduledActionsForRule`-Block, den Graph > beim Anlegen zwingend verlangt. ### Konsolidieren *(nur Read/Write, nur Settings Catalog)* - Mindestens zwei **Settings-Catalog**-Policies per Checkbox waehlen → **„Konsolidieren"** - Die Einstellungen werden zu **einer neuen** Policy vereint (Neuanlage) — gleiche `settingDefinitionId` mit gleichem Wert wird einmal uebernommen - **Konflikte** (gleiche Einstellung, unterschiedlicher Wert) werden vor dem Anlegen angezeigt; pro Konflikt waehlst du, welche Policy gewinnt - Voraussetzung: gleiche Plattform. Zuweisungen werden nicht uebernommen. ### Benoetigt Graph-Berechtigung `DeviceManagementConfiguration.ReadWrite.All` (Import) bzw. `DeviceManagementConfiguration.Read.All` (nur Export). Der Scope wird beim Start automatisch zur Verbindung ergaenzt — in der Azure-App-Registration muss die Berechtigung aber vorhanden und (Admin-)zugestimmt sein. ### Git-Snapshot (versioniertes Backup) Button **„Git-Snapshot"** im Policies-Tab schreibt **alle** Policies aller Typen als JSON in einen konfigurierten **lokalen Git-Ordner** und committet sie automatisch — ideal als versioniertes Backup mit nachvollziehbarer History. - Konfiguration: **Settings → Policy-Backup (Git)** — lokaler Repo-Pfad (wird bei Bedarf angelegt und `git init`-isiert) + optional **automatischer `git push`** (nutzt den vorhandenen Git-Credential-Helper; **kein Token in der App**). - **Stabile Dateinamen** (`//__.json`) und deterministische, zeitstempel-freie JSON-Ausgabe → saubere Git-Diffs zwischen Snapshots. Entfernte Policies verschwinden aus dem Snapshot. - Multi-Tenant: je Mandant ein eigener Unterordner (nach Profil-Label bzw. Tenant-ID). - Gibt es keine Aenderungen seit dem letzten Snapshot, wird nichts committet. --- ## Architektur ``` Intune Manager/ ├── Start.ps1 Entry Point, Config, State ├── README.md diese Datei ├── src/ │ ├── Models.ps1 Settings-Schema und Defaults │ ├── Graph.ps1 Microsoft Graph API Helpers │ ├── Server.ps1 HTTP-Server (HttpListener) │ ├── Router.ps1 Request-Routing │ ├── Api.ps1 REST-API-Endpoints │ └── PolicyIO.ps1 Policy Export/Import (Compliance, Config, Settings Catalog) └── www/ ├── index.html SPA-Shell ├── styles.css Dark Theme, Read-Only-Regeln └── app.js Frontend-Logik (Vanilla JS) ``` --- ## REST-API (intern) | Methode | Pfad | Rechte | Zweck | |---------|-------------------------------------------|-----------|------------------------------------------| | GET | `/api/status` | — | Verbindungsstatus + ReadOnly-Flag | | POST | `/api/connect` | — | Microsoft Graph verbinden | | POST | `/api/disconnect` | — | Trennen | | GET | `/api/groups` | Read | Abteilungs-Gruppen (`abt-hm-*`) | | GET | `/api/groups/rpa` | Read | 3 vordefinierte RPA-Gruppen | | GET | `/api/groups/search?q=` | Read | Gruppen suchen | | POST | `/api/groups` | **Write** | Neue Required-Gruppe erstellen | | POST | `/api/groups/check` | Read | Gruppen-Name pruefen | | POST | `/api/groups/resolve-upns` | Read | UPN-Liste → UserId + DisplayName | | GET | `/api/groups/{id}/members` | Read | Mitglieder einer Gruppe | | GET | `/api/groups/{id}/members/export` | Read | Mitglieder als CSV | | POST | `/api/groups/{id}/members` | **Write** | Benutzer/Gruppen zur Gruppe hinzufuegen | | DELETE | `/api/groups/{id}/members/{uid}` | **Write** | Benutzer/Gruppe aus Gruppe entfernen | | GET | `/api/groups/{id}/devices/export` | Read+MDM | Geraete der Gruppen-User als CSV | | GET | `/api/users?q=` | Read | Benutzer suchen | | GET | `/api/apps` | Read | Apps laden (cached) | | POST | `/api/apps/refresh` | Read | Apps neu laden | | GET | `/api/apps/report` | Read+MDM | Installationszaehler alle Apps | | GET | `/api/apps/devicestatus?appId=` | Read+MDM | Geraete-Installationsstatus einer App | | GET | `/api/membership?targets=&groupId=` | Read | Mitgliedschafts-Status pruefen | | POST | `/api/membership/bulk` | Read | Mehrere Mitgliedschaften pruefen | | POST | `/api/assignments/apply` | **Write** | Geplante Zuweisungen ausfuehren | | GET | `/api/policies/settingscatalog` | Read+Cfg | Settings-Catalog-Policies auflisten | | GET | `/api/policies/compliance` | Read+Cfg | Compliance-Policies auflisten | | GET | `/api/policies/configuration` | Read+Cfg | Konfigurationsprofile auflisten | | POST | `/api/policies/export` | Read+Cfg | Ausgewaehlte Policies als JSON exportieren | | GET | `/api/policies/exports` | — | Server-Archiv der Exporte auflisten | | POST | `/api/policies/import` | **Write** | Policies als Neuanlage importieren | | POST | `/api/policies/consolidate` | **Write** | Settings-Catalog-Policies zusammenfuehren | > **Read+MDM** = benoetigt zusaetzlich `DeviceManagementManagedDevices.Read.All` > **Read+Cfg** = benoetigt `DeviceManagementConfiguration.Read.All` (Export) bzw. `.ReadWrite.All` (Import) --- ## Hinweise - **Single-User-App** – der lokale HTTP-Server ist fuer einen Benutzer ausgelegt. - **Auth lebt im PowerShell-Prozess** – Disconnect oder Schliessen des Terminals beendet die Session. - **Cache** – Apps werden im Serverprozess gecacht. Refresh-Knopf laedt neu. - **Port-Konflikt** – laeuft bereits eine Instanz, beendet `Start.ps1` sie automatisch (per CommandLine-Matching). Bei PID-4-Konflikt (http.sys) anderen Port waehlen: `.\Start.ps1 -Port 8088`.