# 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-`, 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.