Files
macOS_IntuneManagement/AGENTS.md
T
tomas.kracmar d3e0769799 release: v4.1.0 — restructure entry points, add CIS baselines, reporting tools and fzf hints
- Restructure launchers: Start-IntuneToolkit.ps1 moves to repo root;
  Start-HeadlessIntune.ps1 moves to Scripts/; TUI helper moves to Scripts/Private/
- Add AGENTS.md with project architecture, entry points, and security notes
- Add CIS M365 baseline assets (CISM365-v7, M365-CIS-Rapid) and reporting scripts
- Add Python reporting utilities (Export-SettingsReport, Export-AssignmentReport,
  Export-ObjectInventoryReport) and CA wizard helpers
- Update Deploy-IntuneBaseline.ps1 with Merge conflict resolution, ReportPath,
  and optimized group loading
- Update Initialize-IntuneAuth.ps1 with -RotateSecret and configurable secret expiry
- Update Extensions for Settings Catalog definition auto-export
- Update README with v4.1.0, new entry points and script catalog
- Bump VERSION to 4.1.0
- Harden .gitignore against .DS_Store, __pycache__, .venv-pdf/, local exports,
  Settings.json and IntuneManagement.log
2026-06-14 15:24:42 +02:00

233 lines
15 KiB
Markdown

# Agent Guide: macOS Intune Management Toolkit
> This file is written for AI coding agents. It assumes no prior knowledge of the project. Refer to `README.md`, `CHANGELOG_macOS_IntuneToolkit.md`, and `ReleaseNotes.md` for human-facing documentation.
## Project overview
This repository is **macOS Intune Management** (also referred to as the IntuneManagement toolkit). It is a cross-platform, headless, PowerShell-first CLI for exporting, importing, migrating, and managing Microsoft Intune policies across tenants.
- **Current version:** `4.1.0` (see `VERSION`).
- **Primary workflow:** export policies from a source tenant as JSON, optionally capture assignments, then import them into a target tenant with app-only, browser, or device-code authentication.
- **Additional capabilities:** declarative baseline deployment from YAML, bulk assignment management, backup/restore of assignments, bulk device operations, policy rename, CSV/Markdown reporting from local backups, and a CIS M365 rapid baseline for tenant-level workloads.
- **Important historical note:** the old WPF UI surface has been removed. The repo is intentionally CLI-first and headless.
There is no compiled application, no `pyproject.toml`/`package.json`/`Cargo.toml`/`Makefile`, and no CI/CD pipeline. The project is a collection of interpreted PowerShell modules/scripts with supporting Python utilities.
## Technology stack
- **PowerShell 7+ (`pwsh`)** — required runtime. Some launcher files declare `#requires -Version 5.1`, but headless scripts and the runtime target PowerShell 7 behaviors.
- **Microsoft Graph** — all tenant operations call the Graph `beta` (or optionally `v1.0`) endpoints.
- **MSAL.NET** — authentication is handled via `Microsoft.Identity.Client.dll` (shipped in `Bin/`). C# helpers in `CS/` are compiled at runtime with `Add-Type` when proxy or token-cache support is needed.
- **Python 3** — optional reporting utilities and CIS PDF conversion (`Scripts/*.py`). A local virtual environment `.venv-pdf` exists for PDF-specific dependencies (`pypdf`, `markdown-it`).
- **YAML** — baseline manifests are authored in YAML and consumed by `powershell-yaml`.
- **Optional `fzf`** — used by the TUI launchers for interactive menus; falls back to numbered prompts if missing.
### Required / optional dependencies
| Dependency | Where used | How to install |
|---|---|---|
| PowerShell 7+ | Everything | `brew install powershell` / `winget install Microsoft.PowerShell` |
| `Microsoft.Graph.Authentication` & `Microsoft.Graph.Applications` | `Initialize-IntuneAuth.ps1` | `Install-Module Microsoft.Graph -Scope CurrentUser -Force` |
| `powershell-yaml` | `Deploy-IntuneBaseline.ps1` | Script prompts to install; or `Install-Module powershell-yaml -Scope CurrentUser -Force` |
| `fzf` | TUI menus | `brew install fzf`, `apt install fzf`, `winget install junegunn.fzf` |
| Python 3 + `pypdf`/`markdown-it` | CIS PDF conversion (`_ConvertFrom-CISPDF.py`) | Recreate `.venv-pdf`: `python3 -m venv .venv-pdf && .venv-pdf/bin/pip install pypdf markdown-it` |
## Architecture and module organization
```text
.
├── Start-IntuneToolkit.ps1 # Unified terminal UI launcher (recommended entry point)
├── Core.psm1 # Headless runtime helpers, settings, logging, module loader
├── Runtime/
│ ├── IntuneManagement.Runtime.psd1 # Module manifest
│ └── IntuneManagement.Runtime.psm1 # Thin bootstrap: sets globals and starts Core app
├── Headless/
│ ├── IntuneManagement.Headless.psd1
│ └── IntuneManagement.Headless.psm1 # Adapter: default object types, Export/Import/Action wrappers
├── Extensions/ # Loaded automatically by Core.psm1
│ ├── MSGraph.psm1 # Graph API layer, object-type registry, import/export logic
│ ├── MSALAuthentication.psm1 # MSAL auth (app-only, browser, device code)
│ └── EndpointManager.psm1 # Intune view definitions and per-type lifecycle hooks
├── Scripts/ # User-facing entry scripts and private helpers
│ ├── Start-HeadlessIntune.ps1 # Single-action wrapper with optional TUI
│ ├── Export-Policies.ps1
│ ├── Import-Policies.ps1
│ ├── Initialize-IntuneAuth.ps1 # One-time Entra app + secret setup
│ ├── Deploy-IntuneBaseline.ps1 # YAML-driven baseline deployer
│ ├── ConvertTo-IntuneBaseline.ps1 # Export folder → baseline skeleton
│ ├── *_Bulk*.ps1 # Assignments, rename, device operations
│ ├── *.py # Reporting and PDF conversion utilities
│ └── Private/
│ └── Start-IntuneManagementTui.ps1
├── Baselines/ # Example and generated baseline manifests
│ ├── OpenIntuneBaseline.example.yaml
│ ├── CISM365-v7.example.yaml
│ ├── CISM365-v7-Generated.yaml
│ └── M365-CIS-Rapid/ # Config-driven tenant-level baseline
├── Bin/ # MSAL DLLs
├── CS/ # C# helper source compiled at runtime
└── .venv-pdf/ # Isolated Python venv for PDF processing
```
### How the pieces fit together
1. An entry script (`Start-IntuneToolkit.ps1`, `Export-Policies.ps1`, etc.) imports `Headless/IntuneManagement.Headless.psd1`.
2. The Headless module builds a JSON batch configuration describing the requested export/import action and writes it to a temporary file.
3. It imports `Runtime/IntuneManagement.Runtime.psd1` and calls `Initialize-IntuneManagementRuntime`, which sets global authentication variables and invokes `Start-CoreApp` from `Core.psm1`.
4. `Core.psm1` loads every `*.psm1` in `Extensions/` and dispatches module lifecycle hooks (`Invoke-InitializeModule`, `Invoke-SilentBatchJob`).
5. `Extensions/MSGraph.psm1` and `Extensions/EndpointManager.psm1` do the actual Graph calls and policy handling.
### Object-type registry
Intune object types are declared as `Add-ViewItem` entries in `Extensions/EndpointManager.psm1`. The headless default list (returned by `Get-DefaultIntunePolicyObjectTypes`) contains roughly 45 types, including:
- `DeviceConfiguration`, `SettingsCatalog`, `AdministrativeTemplates`
- `CompliancePolicies`, `CompliancePoliciesV2`, `EndpointSecurity`, `DeviceManagementIntents`
- `PolicySets`, `Applications`, `AppProtection`, `AppConfigurationManagedDevice`
- `ConditionalAccess`, `NamedLocations`, `TermsOfUse`
- Many more (scripts, branding, enrollment, update policies, etc.)
You can override the list per-run with the `-ObjectTypes` parameter.
## Configuration and data storage
The toolkit stores persistent configuration in a platform-specific JSON settings file:
- **macOS:** `~/Library/Application Support/macOS_IntuneManagement/Settings.json`
- **Windows:** `%LOCALAPPDATA%\macOS_IntuneManagement\Settings.json`
- **Linux:** `~/.local/share/macOS_IntuneManagement/Settings.json`
Per-tenant settings are stored under a key matching the tenant GUID. On macOS, the client secret is stored in the macOS Keychain (service `IntuneMgmt-<AppId>`, account `IntuneManagement`) instead of inside the JSON file. On non-macOS platforms the secret is written to the JSON file — treat that file as sensitive.
Operational logs are written to `IntuneManagement.log` in the same data folder by default.
## Authentication
Three authentication modes are supported, selected via `-AuthMode`:
- `AppOnly` (default) — uses an Entra app registration with client secret or certificate.
- `Browser` — interactive delegated auth using a public client. If `-AppId` is omitted, the Microsoft Graph PowerShell public client (`14d82eec-204b-4c2f-b7e8-296a70dab67e`) is used.
- `DeviceCode` — delegated auth via device code; may be blocked by Conditional Access.
First-time setup is performed by `Scripts/Initialize-IntuneAuth.ps1`, which:
1. Connects to Microsoft Graph with admin credentials.
2. Creates (or reuses) an Entra app registration named after the authenticated Entra user for audit traceability.
3. Ensures a broad set of Microsoft Graph application permissions are configured and grants admin consent.
4. Creates a client secret and stores it securely.
The launcher caches tenant display names in `Settings.json` so the TUI can show friendly names.
## Main entry points
| Script | Purpose |
|---|---|
| `Start-IntuneToolkit.ps1` | Unified reverse-numbered `fzf`/numbered menu; remembers tenants; launches all other tools. |
| `Scripts/Start-HeadlessIntune.ps1` | Single-action wrapper (`Export`, `Import`, `DeployCISBaseline`, `GenerateReports`) with optional interactive TUI. |
| `Scripts/Export-SettingsReport.py` | Generate a flat CSV of policy settings/values. Settings Catalog names are resolved from `configurationSettings.json` (auto-exported with Settings Catalog). |
| `Scripts/Export-Policies.ps1` | Export policies to JSON. |
| `Scripts/Import-Policies.ps1` | Import policies from JSON. |
| `Scripts/Initialize-IntuneAuth.ps1` | One-time Entra app setup; also supports `-RotateSecret`, `-Delete`, `-DeleteApp`. |
| `Scripts/Deploy-IntuneBaseline.ps1` | Declarative YAML baseline deployment with `-WhatIf` support. |
| `Scripts/ConvertTo-IntuneBaseline.ps1` | Convert an existing export into a baseline skeleton. |
| `Scripts/Bulk-AssignmentManager.ps1` | Bulk add/remove policy assignments. |
| `Scripts/Bulk-AppAssignment.ps1` | Bulk add/remove application assignments. |
| `Scripts/Backup-Restore-Assignments.ps1` | Backup and restore assignments across tenants. |
| `Scripts/Bulk-RenamePolicies.ps1` | Search/replace or prefix policy names and descriptions. |
| `Scripts/Bulk-DeviceOperations.ps1` | Delete, retire, wipe, lock, sync devices with `-WhatIf`. |
| `Scripts/Export-AssignmentsToCsv.ps1` | Export assignments to CSV/Markdown. |
## Baselines
- `Baselines/OpenIntuneBaseline.example.yaml` — example manifest for the declarative deployer.
- `Baselines/CISM365-v7.example.yaml` / `CISM365-v7-Generated.yaml` — CIS M365 v7 baseline manifests, generated from PDF.
- `Baselines/M365-CIS-Rapid/` — a separate config-driven baseline (`CISM365-RapidBaseline.psd1`) for tenant-level workloads (Entra ID, Conditional Access guidance, Exchange, SharePoint, Teams, Defender, Purview). See its `README.md` for prerequisites and workflow.
The YAML deployer supports groups, global/per-policy name mutations (`search`/`replace` or `prefix`), assignment targets, and conflict resolution (`Skip`, `Update`, `Error`).
## Build and test commands
There is no build step. Use the commands below to validate changes.
### PowerShell syntax validation
```powershell
# Validate one file
pwsh -Command "Get-Command ./Scripts/Export-Policies.ps1"
# Or parse tokens explicitly
pwsh -Command "
$err = $null
[System.Management.Automation.PSParser]::Tokenize(
(Get-Content -Raw ./Scripts/Deploy-IntuneBaseline.ps1), [ref]$err)
if ($err) { throw $err }
Write-Host 'Syntax OK'
"
```
### Module manifest validation
```powershell
pwsh -Command "Test-ModuleManifest ./Headless/IntuneManagement.Headless.psd1"
pwsh -Command "Test-ModuleManifest ./Runtime/IntuneManagement.Runtime.psd1"
```
### Python syntax validation
```bash
python3 -m py_compile Scripts/Export-SettingsReport.py
python3 -m py_compile Scripts/Export-AssignmentReport.py
python3 -m py_compile Scripts/Export-ObjectInventoryReport.py
python3 -m py_compile Scripts/_ConvertFrom-CISPDF.py
```
### What there is *not*
- No Pester test suite.
- No GitHub Actions, GitLab CI, or other continuous-integration configuration.
- No package-manager manifest (npm/pip/cargo/etc.).
## Testing instructions
1. **Syntax-check** modified PowerShell and Python files before committing.
2. **Use a non-production tenant** for functional testing.
3. **Prefer dry-run modes:**
- `Deploy-IntuneBaseline.ps1 -WhatIf`
- `Bulk-DeviceOperations.ps1 -WhatIf`
- `Start-HeadlessIntune.ps1 -Action GenerateReports` reads local backups only, making no Graph calls.
4. **Test auth setup end-to-end** with `Initialize-IntuneAuth.ps1`, then run `Export-Policies.ps1` without passing `-AppId`/`-Secret` to verify Keychain/settings resolution.
5. For the CIS rapid baseline, run `./Baselines/M365-CIS-Rapid/Deploy-CISM365RapidBaseline.ps1` in default assess mode first.
## Code style guidelines
- **PowerShell**
- Use `[CmdletBinding()]` on public functions/scripts.
- Name functions `Verb-Noun`; parameters are `PascalCase`.
- Entry scripts set `$ErrorActionPreference = "Stop"`.
- Use `[ValidateSet(...)]` for enum-like parameters.
- Use `Write-Host` with `-ForegroundColor` for user-facing menus; use `Write-Log` / `Write-LogError` for operational logging.
- JSON serialization uses `-Depth 20` or `-Depth 30` to avoid truncating nested objects.
- Do not introduce WPF/XAML dependencies — the UI layer has been removed.
- **YAML manifests**
- Follow the structure in `Baselines/OpenIntuneBaseline.example.yaml`.
- Use two-space indentation.
- **Python**
- Use `argparse`, type hints, and `pathlib.Path` in new scripts.
- Keep report scripts using only the standard library where possible.
## Security considerations
- **App-only auth creates a service principal with broad standing permissions.** Audit logs show the app's display name, not the individual admin's UPN. The initializer names the app after the authenticated Entra user to improve traceability.
- **PIM is not enforced for app-only secrets.** If strict Privileged Identity Management compliance is required, use delegated auth (`-AuthMode Browser` or `-AuthMode DeviceCode`) so actions run in the signed-in user's context.
- **Secret storage:** on macOS, secrets live in the Keychain. On other platforms, `GraphAzureAppSecret` is stored in `Settings.json` as plaintext. Never commit `Settings.json`, `IntuneManagement.log`, exported CSVs/ZIPs, or tenant backups.
- **Tenant lockout risk:** Conditional Access policies can lock you out. The toolkit and the CIS rapid baseline intentionally do not auto-create CA policies; create them manually in the Entra portal.
- **Destructive operations:** scripts support device wipe/retire/delete, app registration deletion, and bulk deletes. Always use `-WhatIf` first and confirm in a sandbox tenant.
- **Proxy support:** MSAL authentication can use a proxy URI configured via the `ProxyURI` setting. The C# helper `HttpFactoryWithProxy.cs` is compiled at runtime when needed.
## Deployment / release process
- The project is deployed manually: clone the repository and run `pwsh ./Start-IntuneToolkit.ps1` or the relevant script.
- Version is tracked in the `VERSION` file and referenced by `README.md` and `CHANGELOG_macOS_IntuneToolkit.md`.
- There is no installer, no signed module package, and no automated release pipeline.
- When adding a new workload or object type, register it in both `Extensions/EndpointManager.psm1` (as an `Add-ViewItem`) and `Headless/IntuneManagement.Headless.psm1` (in `Get-DefaultIntunePolicyObjectTypes`) if it should be available headlessly.