Files
tomas.kracmar 122aa2d4e3 fix(reporting): add Platform column and clean up Windows artifacts
- Export-SettingsReport.py: add Platform column for Settings Catalog
  (platforms field) and legacy policies (platform/platformType or
  @odata.type inference)
- MSGraph.psm1: store GraphMetaData.xml in cross-platform data folder
  (Get-CloudApiDataFolder) instead of literal %LOCALAPPDATA% path
- MSALAuthentication.psm1: skip TokenCacheHelperEx on non-Windows with
  an info log instead of failing on missing ProtectedData.dll
- .gitignore: remove literal %LOCALAPPDATA% patterns
- AGENTS.md, CHANGELOG: document reporting and cross-platform fixes
2026-06-22 11:56:55 +02:00

15 KiB

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

.
├── 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. Includes a Platform column and resolves Settings Catalog names 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

# 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

pwsh -Command "Test-ModuleManifest ./Headless/IntuneManagement.Headless.psd1"
pwsh -Command "Test-ModuleManifest ./Runtime/IntuneManagement.Runtime.psd1"

Python syntax validation

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.