mirror of
https://github.com/Micke-K/IntuneManagement.git
synced 2026-09-28 10:55:38 +02:00
IntuneManagement 4.0.0-beta1
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
# Bulk Assignments
|
||||
|
||||
## Purpose
|
||||
|
||||
`Set-GraphBulkAssignments` adds, replaces, or removes assignment targets across many policies. It is public and UI-independent; the WPF bulk assignment form builds an `IntuneManagerAssignmentSettings` object and calls it.
|
||||
|
||||
## Main Files
|
||||
|
||||
| File | Responsibility |
|
||||
| --- | --- |
|
||||
| `Public/Set-GraphBulkAssignments.ps1` | assignment orchestration and body generation. |
|
||||
| `Classes/IntuneBaseClasses.ps1` | `IntuneManagerAssignmentSettings` and policy type assignment metadata. |
|
||||
| `Public/Get-GraphPolicies.ps1` | list policies and include current assignments. |
|
||||
| `UI/Extensions/IntuneManagerUI.ps1` | bulk assignment UI, group picker, app settings dialog. |
|
||||
| `UI/XAML/BulkAssignments*.xaml` | forms for assignment targets and settings. |
|
||||
|
||||
## Supported Actions
|
||||
|
||||
| Action | Behavior |
|
||||
| --- | --- |
|
||||
| `Add` | union current assignments with selected assignment rows. |
|
||||
| `Replace` | replace all current assignments with selected rows. |
|
||||
| `Remove` | remove selected rows from current assignments. |
|
||||
|
||||
Graph `/assign` endpoints replace the full assignment collection, so `Add` and `Remove` must first load current assignments and compute the final collection.
|
||||
|
||||
## Target Types
|
||||
|
||||
Supported target descriptors:
|
||||
|
||||
| Target type | Required fields |
|
||||
| --- | --- |
|
||||
| `groupAssignmentTarget` | `GroupId` |
|
||||
| `exclusionGroupAssignmentTarget` | `GroupId` |
|
||||
| `allDevicesAssignmentTarget` | none |
|
||||
| `allLicensedUsersAssignmentTarget` | none |
|
||||
|
||||
Group targets can include assignment filters:
|
||||
|
||||
| Field | Meaning |
|
||||
| --- | --- |
|
||||
| `FilterId` | assignment filter ID. |
|
||||
| `FilterType` | `include` or `exclude`. |
|
||||
|
||||
## Assignment Shapes
|
||||
|
||||
Different Graph APIs use different assignment body shapes.
|
||||
|
||||
| Shape | Detection | Body |
|
||||
| --- | --- | --- |
|
||||
| simple | default | `{ target }` |
|
||||
| app | `AssignmentsType = mobileAppAssignments` | `{ target, intent, settings? }` |
|
||||
| script | `AssignmentsType = deviceHealthScriptAssignments` | `{ target, runRemediationScript?, runSchedule? }` |
|
||||
|
||||
The function rejects policy types that cannot be represented by one of these shapes.
|
||||
|
||||
## Policy Type Gating
|
||||
|
||||
`Test-BulkAssignmentSupported` checks:
|
||||
|
||||
1. `SupportsAssignments = true`;
|
||||
2. `AssignmentsType` exists;
|
||||
3. the type is not a known non-policy assignment API;
|
||||
4. `Get-BulkAssignmentShape` returns a supported shape.
|
||||
|
||||
`Get-BulkAssignmentObjectType` determines assignment entry `@odata.type`. It first checks `PolicyType.AssignmentObjectType`, then uses built-in heuristics for known types.
|
||||
|
||||
## No-Op Detection
|
||||
|
||||
The command builds tuple signatures for assignment comparison.
|
||||
|
||||
| Signature | Used for | Fields |
|
||||
| --- | --- | --- |
|
||||
| target signature | Add/Remove identity and dedupe | target type, group ID, filter ID/type, app intent |
|
||||
| full signature | no-op detection | target signature plus app settings, health-script schedule, remediation flag |
|
||||
|
||||
This distinction matters because `Add` should not create duplicates, but `Replace` must still update settings for an existing target.
|
||||
|
||||
## App Settings
|
||||
|
||||
The UI stores app settings by settings type name, for example `win32LobAppAssignmentSettings`. At execution time:
|
||||
|
||||
1. the policy object's `@odata.type` is mapped to the assignment settings type;
|
||||
2. the selected row's matching settings hashtable is converted to Graph-shaped objects;
|
||||
3. nested hashtables and arrays are converted recursively for JSON serialization;
|
||||
4. settings are omitted when not configured so Graph defaults apply.
|
||||
|
||||
## Health Script Schedule
|
||||
|
||||
Health script settings are stored under the synthetic key `deviceHealthScriptAssignment`. The command turns those values into:
|
||||
|
||||
| UI value | Graph type |
|
||||
| --- | --- |
|
||||
| `Hourly` | `deviceHealthScriptHourlySchedule` |
|
||||
| `Daily` | `deviceHealthScriptDailySchedule` |
|
||||
| `Once` | `deviceHealthScriptRunOnceSchedule` |
|
||||
|
||||
## Summary Output
|
||||
|
||||
The command returns:
|
||||
|
||||
| Field | Meaning |
|
||||
| --- | --- |
|
||||
| `Types` | eligible policy types. |
|
||||
| `PoliciesScanned` | policies loaded. |
|
||||
| `PoliciesMatched` | policies matching name filter. |
|
||||
| `PoliciesUpdated` | successful assignment POSTs. |
|
||||
| `PoliciesSkipped` | no changes required. |
|
||||
| `PoliciesFailed` | failed POSTs or missing batch responses. |
|
||||
| `UnsupportedTypes` | selected types skipped by support gate. |
|
||||
| `Duration` | elapsed time. |
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# Bulk Export
|
||||
|
||||
## Purpose
|
||||
|
||||
`Start-GraphBulkExport` is the UI-independent bulk export driver. It can be called from the WPF UI, a scheduled task, or automation. It wraps listing, hydration, extra-data synchronization, file writing, and migration-table generation.
|
||||
|
||||
## Main Files
|
||||
|
||||
| File | Responsibility |
|
||||
| --- | --- |
|
||||
| `Public/Start-GraphBulkExport.ps1` | bulk export orchestration and helper functions. |
|
||||
| `Public/Export-GraphPolicy.ps1` | per-policy file writing and migration hooks. |
|
||||
| `Public/Get-GraphPolicies.ps1` | listing and assignment loading. |
|
||||
| `Internal/MSGraph.ps1` | batch execution, migration table functions, navigation properties. |
|
||||
| `UI/Extensions/IntuneManagerUI.ps1` | bulk export form and settings save UI. |
|
||||
|
||||
## Settings Resolution
|
||||
|
||||
Parameter precedence:
|
||||
|
||||
```text
|
||||
IntuneManagerExportSettings defaults
|
||||
<- SettingsFile JSON
|
||||
<- explicit parameters
|
||||
```
|
||||
|
||||
This allows saved scheduled-task settings while still making command-line overrides possible.
|
||||
|
||||
`ExportFullMembershipPrefixes` ("Get full membership") was removed: every group
|
||||
path is batched now, and its only output was `#DirectMembers` /
|
||||
`#DirectMemberCount` on the group sidecars, which nothing in the module read.
|
||||
`Clear-BulkExportLegacyMembershipSetting` (`Internal/BulkExport.ps1`) runs at the
|
||||
start of every export so a value that outlived the removal is never dropped in
|
||||
silence: a non-empty value in a settings file is logged as a warning each run
|
||||
(the file belongs to the caller and is not rewritten), and a value in the
|
||||
settings store is warned about once and then cleared. The store sweep follows the
|
||||
same precedence `Get-SettingValue` used, so it covers the per-tenant paths
|
||||
(`<tenantId>\IntuneManager`, for the connected organization and for the export
|
||||
token's tenant) as well as the global `IntuneManager` one.
|
||||
|
||||
## Target Resolution
|
||||
|
||||
Targets are resolved from:
|
||||
|
||||
| Input | Behavior |
|
||||
| --- | --- |
|
||||
| `-PolicyType` | exact type IDs. |
|
||||
| `-PolicyGroup` | group IDs expanded to member policy types. |
|
||||
| neither | all exportable groups and types. |
|
||||
|
||||
Unknown types/groups are logged and skipped.
|
||||
|
||||
## Parallel Pipeline
|
||||
|
||||
When `UseParallelBatchAPI` is enabled and PowerShell 7+ is running, bulk export uses a three-phase pipeline:
|
||||
|
||||
```text
|
||||
Phase 1: list all selected types through Get-GraphPolicies
|
||||
Phase 2: batch-fetch full policy bodies via Invoke-PolicyHydrate
|
||||
Phase 2.5: sync extra data and prefetch assignment groups
|
||||
Phase 3: write policy files type by type
|
||||
```
|
||||
|
||||
When parallel mode is disabled, it processes each type sequentially:
|
||||
|
||||
```text
|
||||
list type -> hydrate full objects -> sync extra data -> write type
|
||||
```
|
||||
|
||||
## Extra Data Synchronization
|
||||
|
||||
Some Graph list/detail endpoints do not include all export data. `Invoke-PolicyExtraData` (a Phase-A wrapper that will shrink to nothing as helpers migrate to the per-class `_HasSubResourceBatch` contract) fills the remaining gaps before file writing.
|
||||
|
||||
| Helper | Data added | Status |
|
||||
| --- | --- | --- |
|
||||
| `Sync-BulkExportReusableSettings` | reusable setting instances. | Phase-A wrapper; migrates to class contract in Phase B. |
|
||||
| `Sync-BulkExportBrandingImages` | branding image payloads. | Phase-A wrapper; migrates to `IntuneBrandingObject` contract. |
|
||||
| `Sync-BulkExportAppConfigurationTargetApps` | targeted mobile app reference info. | Phase-A wrapper; migrates to AppConfig object contracts. |
|
||||
| `Sync-BulkExportRoleAssignmentDetails` | role assignment expanded details. | Phase-A wrapper; migrates to `RoleDefinitionObject` Phase 2. |
|
||||
| `Sync-BulkExportTermsOfUseFiles` | terms of use file data. | Phase-A wrapper; migrates to `TermsOfUseObject` contract. |
|
||||
| `Sync-BulkExportMigrationGroups` | migration-table group resolution. | Cross-cutting; will be renamed `Invoke-MigrationGroupResolution`. |
|
||||
| `Sync-BulkExportNestedGroupHierarchy` | nested-group hierarchy expansion. | Cross-cutting; will be renamed `Invoke-NestedGroupResolution`. |
|
||||
|
||||
ADMX definition values, presentation values, app dependencies, supersedence, and Win32 scripts are no longer handled by `Sync-Bulk*` helpers — those moved to the per-class `GetSubResourceBatchRequests` / `ApplySubResourceBatchResult` contract on `AdminTemplateObject` and `ApplicationObject`.
|
||||
|
||||
## Migration Performance
|
||||
|
||||
Bulk export resets migration caches at the start:
|
||||
|
||||
| Cache | Purpose |
|
||||
| --- | --- |
|
||||
| `_migFileCache` | in-memory `MigrationTable.json` objects. |
|
||||
| `_migFileObjectsIndex` | duplicate prevention. |
|
||||
| `_migFileDirty` | list of migration files to flush once. |
|
||||
| `_migFilePathCache` | avoid repeated path resolution. |
|
||||
| `_appConfigTargetAppCache` | avoid repeated app target lookups. |
|
||||
|
||||
`Sync-BulkExportMigrationGroups` prefetches assigned groups in one batch. `Add-GraphMigrationObject` can still fetch non-prefetched references such as Conditional Access users/groups and nested groups.
|
||||
|
||||
## Output
|
||||
|
||||
Returns a summary object:
|
||||
|
||||
| Field | Meaning |
|
||||
| --- | --- |
|
||||
| `Types` | number of policy types processed. |
|
||||
| `Policies` | number of policies exported. |
|
||||
| `Failed` | number of failed type operations. |
|
||||
| `Duration` | elapsed time. |
|
||||
|
||||
## Extension Points
|
||||
|
||||
To make a policy type export fully:
|
||||
|
||||
1. ensure the list endpoint returns enough data or full hydration works;
|
||||
2. add full-object URL handling when a type uses polymorphic endpoints;
|
||||
3. add extra-data sync if the data is not part of the normal object body;
|
||||
4. add `PostExportCommand` for references that must be included in migration data;
|
||||
5. set properties-to-remove for import/update round trips.
|
||||
|
||||
@@ -0,0 +1,714 @@
|
||||
# Command reference
|
||||
|
||||
Every command the module exports, with parameters, examples and what it returns.
|
||||
Recipes that combine them are in [Examples.md](Examples.md).
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Prefix.** The module exports with `DefaultCommandPrefix = 'IM'`, so `Get-GraphPolicies`
|
||||
is called as `Get-IMGraphPolicies`. The prefix goes after the verb: `Invoke-MSGraphAPI`
|
||||
becomes **`Invoke-IMMSGraphAPI`** (double M). Override with `Import-Module -Prefix`.
|
||||
- **`-TokenId`.** Several tenants can be signed in at once. Commands that talk to Graph take
|
||||
`-TokenId` (from `Get-IMAuthToken`) to say which; omitted, they use the default token -
|
||||
the first sign-in, or the one connected with `-DefaultToken`.
|
||||
- **Policy type and group ids** (`-PolicyType`, `-PolicyGroup`) are tab-completed, not
|
||||
validated: an unknown id is reported, not rejected at bind time. The ids are the ones in
|
||||
the policy table in [README.md](../README.md#supported-policy-types) - `DeviceConfiguration`,
|
||||
`SettingsCatalog`, `CompliancePolicies`, `DeviceEnrollments`, and so on.
|
||||
- **`-WhatIf` / `-Confirm`** are supported on `Remove-IMGraphPolicy`, `Set-IMSetting`,
|
||||
`Remove-IMSetting`, `Use-IMSettingsStore`, `Export-IMSettingsStore` and
|
||||
`Import-IMSettingsStore`. **`Start-IMGraphBulkDelete` has neither** - the caller owns the
|
||||
confirmation.
|
||||
- **Read-only** commands, safe for reporting and CI: `Get-IMAuthToken`, `Get-IMAccessibleTenant`,
|
||||
`Get-IMGraphEffectivePermissions`, `Get-IMGraphPolicies`, `Get-IMGraphPolicyFromFile`,
|
||||
`Compare-IMGraphPolicy`, `Get-IMGraphDocumentation`, `Get-IMDocumentationOutput`,
|
||||
`Get-IMSetting`, `Get-IMSettingDefinition`, `Get-IMSettingsStore`.
|
||||
|
||||
## Contents
|
||||
|
||||
| Area | Commands |
|
||||
|---|---|
|
||||
| [Signing in and tokens](#signing-in-and-tokens) | `Connect-IMIntuneManagement`, `Get-IMAuthToken`, `Get-IMAccessibleTenant`, `Get-IMGraphEffectivePermissions`, `Invoke-IMMSGraphAPI` |
|
||||
| [Working with policies](#working-with-policies) | `Get-IMGraphPolicies`, `Get-IMGraphPolicyFromFile`, `Import-IMGraphPolicy`, `Export-IMGraphPolicy`, `Copy-IMGraphPolicy`, `Remove-IMGraphPolicy`, `Compare-IMGraphPolicy` |
|
||||
| [Bulk operations](#bulk-operations) | `Start-IMGraphBulkExport`, `Start-IMGraphBulkImport`, `Start-IMGraphBulkCopy`, `Start-IMGraphBulkDelete`, `Set-IMGraphBulkAssignments`, `Set-IMGraphBulkScopeTags`, `Save-IMGraphBulkExportSettings` |
|
||||
| [Documentation](#documentation) | `Get-IMGraphDocumentation`, `Start-IMGraphBulkDocumentation`, `Get-IMDocumentationOutput` |
|
||||
| [Settings](#settings) | `Get-IMSetting`, `Set-IMSetting`, `Remove-IMSetting`, `Get-IMSettingDefinition`, `Use-IMSettingsStore`, `Get-IMSettingsStore`, `Export-IMSettingsStore`, `Import-IMSettingsStore` |
|
||||
| [UI](#ui) | `Show-IMMainWindow` |
|
||||
|
||||
---
|
||||
|
||||
## Signing in and tokens
|
||||
|
||||
### Connect-IMIntuneManagement
|
||||
|
||||
Authenticate to Microsoft Graph. Each call registers a token; several tenants can be
|
||||
live at once. Returns the token as a `PSCustomObject` (`TokenId`, `TenantId`,
|
||||
`TenantName`, `Provider`, `Account`, `ExpiresOn`).
|
||||
|
||||
The parameter set chooses the credential. `-Provider` chooses the implementation:
|
||||
`MSAL` (default) or `OAuth`; the saved *Active authentication provider* setting is the
|
||||
fallback.
|
||||
|
||||
| Set | Parameters | Notes |
|
||||
|---|---|---|
|
||||
| Interactive (default) | `-Interactive` `[-TenantId]` `[-User]` `[-ForceInteractive]` `[-AuthenticationBroker]` `[-Browser]` | Silent from cache first, then browser. `-AuthenticationBroker` = WAM, Windows + PS 7 only. With `-Provider OAuth` this routes to device code. |
|
||||
| DeviceCode | `-DeviceCode` `[-TenantId]` `[-AppId]` | Code shown here, browser step on any device. MFA / FIDO2 capable. |
|
||||
| Secret | `-TenantId` `-AppId` `-Secret` | App registration with client secret. |
|
||||
| Certificate | `-TenantId` `-AppId` `-Certificate <thumbprint or X509Certificate2>` | Looked up in `Cert:\CurrentUser\My`, then `Cert:\LocalMachine\My`. |
|
||||
| CertificatePath | `-TenantId` `-AppId` `-CertificatePath` `[-CertificatePassword <SecureString>]` | `.pfx` file. |
|
||||
| Token | `-Token` | Bring your own Graph bearer token. Cannot be refreshed. |
|
||||
| ManagedIdentity | `-ManagedIdentity` `[-AppId]` | System-assigned, or user-assigned via `-AppId`. `-Provider OAuth`. |
|
||||
| OAuthFederated | `-TenantId` `-AppId` `-FederatedTokenFile` or `-FederatedToken` | Workload identity federation (AKS, GitHub Actions OIDC). `-Provider OAuth`. |
|
||||
| OAuthCredential | `-TenantId` `-AppId` `-Credential <PSCredential>` | ROPC. Non-MFA accounts only. `-Provider OAuth`. |
|
||||
|
||||
Common to every set: `-Cloud Public|USGov|USGovDOD|China` (default from the
|
||||
*DefaultCloud* setting), `-DefaultToken` (make this the default token), `-Provider`.
|
||||
|
||||
```powershell
|
||||
# Interactive, resumes silently from the cache when it can
|
||||
Connect-IMIntuneManagement -Interactive
|
||||
|
||||
# A specific tenant, and force a fresh prompt
|
||||
Connect-IMIntuneManagement -Interactive -TenantId contoso.onmicrosoft.com -ForceInteractive
|
||||
|
||||
# Unattended: client secret
|
||||
Connect-IMIntuneManagement -TenantId contoso.onmicrosoft.com -AppId $appId -Secret $secret
|
||||
|
||||
# Unattended: certificate thumbprint
|
||||
Connect-IMIntuneManagement -TenantId contoso.onmicrosoft.com -AppId $appId -Certificate 'A1B2C3...'
|
||||
|
||||
# Unattended: .pfx
|
||||
Connect-IMIntuneManagement -TenantId $t -AppId $a -CertificatePath C:\certs\app.pfx `
|
||||
-CertificatePassword (ConvertTo-SecureString $pw -AsPlainText -Force)
|
||||
|
||||
# Device code - headless box, sign in from a phone
|
||||
Connect-IMIntuneManagement -DeviceCode
|
||||
|
||||
# OAuth provider: managed identity on an Azure VM / Function / Automation account
|
||||
Connect-IMIntuneManagement -Provider OAuth -ManagedIdentity
|
||||
|
||||
# OAuth provider: GitHub Actions OIDC / AKS workload identity
|
||||
Connect-IMIntuneManagement -Provider OAuth -TenantId $t -AppId $a -FederatedTokenFile $env:AZURE_FEDERATED_TOKEN_FILE
|
||||
|
||||
# Bring your own token (the only way to reach APIs closed to public clients)
|
||||
Connect-IMIntuneManagement -Token $bearer
|
||||
|
||||
# Sovereign cloud
|
||||
Connect-IMIntuneManagement -Interactive -Cloud USGov
|
||||
```
|
||||
|
||||
### Get-IMAuthToken
|
||||
|
||||
List the tokens currently held, across providers. Returns `IMAuthToken[]` -
|
||||
`TokenId`, `TenantId`, `TenantName`, `Provider`, `Account`/`UPN`, `AppId`, `ExpiresOn`,
|
||||
`IsDefault`.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-TokenId` | int | One token. |
|
||||
| `-Provider` | string | Filter by provider. |
|
||||
| `-TenantId` | string | Filter by tenant. |
|
||||
|
||||
```powershell
|
||||
Get-IMAuthToken
|
||||
|
||||
# Pick a tenant's token for a later command
|
||||
$prod = Get-IMAuthToken | Where-Object TenantName -eq 'Contoso Prod'
|
||||
Get-IMGraphPolicies -PolicyType CompliancePolicies -TokenId $prod.TokenId
|
||||
```
|
||||
|
||||
### Get-IMAccessibleTenant
|
||||
|
||||
List the tenants the signed-in account can reach - its home tenant and every tenant it
|
||||
is a guest in. Graph cannot answer this; the list comes from Azure Resource Manager, so
|
||||
the app registration needs the delegated permission *Azure Service Management /
|
||||
user_impersonation*, and only the MSAL provider implements it. Without either, the
|
||||
command warns and returns nothing.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-TokenId` | int | Ask the provider that owns this token. |
|
||||
|
||||
```powershell
|
||||
Get-IMAccessibleTenant
|
||||
|
||||
# Confirm a guest tenant is reachable, then connect to it silently
|
||||
$guest = Get-IMAccessibleTenant | Where-Object displayName -eq 'Fabrikam'
|
||||
Connect-IMIntuneManagement -Interactive -TenantId $guest.tenantId
|
||||
```
|
||||
|
||||
### Get-IMGraphEffectivePermissions
|
||||
|
||||
What the signed-in identity can actually do, per policy type: the app's token scopes
|
||||
combined with the user's Intune RBAC or Entra directory roles. Lets a script learn it is
|
||||
read-only for a type *before* a bulk import, instead of collecting 403s halfway through.
|
||||
One row per policy type: `Id`, `Required`, `TokenAccess`, `RoleAccess`,
|
||||
`EffectiveAccess`, `Result` (Match / Read-only / No access), plus the raw
|
||||
`TokenLevel` / `RbacLevel` / `EffectiveLevel` (Full / Limited / None) and `Reason`.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-TokenId` | int | Evaluate this token. |
|
||||
| `-PolicyType` | string[] | Only these type ids. |
|
||||
| `-Raw` | switch | Return the RBAC context itself (allowed resource actions, the catalog, the raw response) instead of the table. |
|
||||
|
||||
App-only tokens bypass Intune RBAC, so `RbacLevel` is `$null` and the token level is the
|
||||
answer. Scope tags are not modelled. The answer is cached per token and refreshed when
|
||||
the token is re-issued.
|
||||
|
||||
```powershell
|
||||
# What can this user not change, and why?
|
||||
Get-IMGraphEffectivePermissions | Where-Object EffectiveLevel -ne Full |
|
||||
Format-Table Id, TokenLevel, RbacLevel, EffectiveLevel, Reason
|
||||
|
||||
# Gate a bulk import on write access to the types it touches
|
||||
$blocked = Get-IMGraphEffectivePermissions -PolicyType DeviceConfiguration, SettingsCatalog |
|
||||
Where-Object EffectiveLevel -ne Full
|
||||
if ($blocked) { throw "Read-only for: $($blocked.Id -join ', ')" }
|
||||
```
|
||||
|
||||
### Invoke-IMMSGraphAPI
|
||||
|
||||
The low-level Graph call every other command uses: resolves the token, adds headers,
|
||||
handles throttling, paging, batching and claims challenges. Use it for anything the
|
||||
policy commands do not cover.
|
||||
|
||||
| Parameter | Type | Default | Notes |
|
||||
|---|---|---|---|
|
||||
| `-Url` | string | required | Relative (`deviceManagement/deviceCategories`) or absolute. |
|
||||
| `-HttpMethod` (`-Method`) | string | `GET` | `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, `OPTIONS`. |
|
||||
| `-Content` (`-Body`) | string | | Request body, JSON. |
|
||||
| `-Headers`, `-AdditionalHeaders` | hashtable | | |
|
||||
| `-GraphVersion` | string | `beta` | `beta` or `v1.0`. The *UseGraphV1* setting flips the default. |
|
||||
| `-ODataMetadata` | string | `full` | `full`, `minimal`, `none`, `skip`. |
|
||||
| `-AllPages` | switch | | Follow `@odata.nextLink` to the end. |
|
||||
| `-PageSize` | int | | `$top` for the first page. |
|
||||
| `-Batch` | switch | | Queue into the current `$batch` instead of sending. |
|
||||
| `-Outfile` | string | | Save the response body to a file. |
|
||||
| `-FullResponseObject` | switch | | Return status code and headers as well as the body. |
|
||||
| `-NoError` | switch | | Return `$null` on failure instead of logging an error. |
|
||||
| `-SkipAuthentication` | switch | | |
|
||||
| `-TokenId` | int | default token | |
|
||||
|
||||
Returns the parsed body (collections under `.value`). A Multi Admin Approval 412 comes
|
||||
back with `ApprovalPending = $true` and the `ApprovalCode` on the result.
|
||||
|
||||
```powershell
|
||||
# List with paging
|
||||
(Invoke-IMMSGraphAPI -Url 'deviceManagement/deviceCategories' -AllPages).value
|
||||
|
||||
# Create
|
||||
Invoke-IMMSGraphAPI -Url 'deviceManagement/deviceCategories' -HttpMethod POST `
|
||||
-Content '{"displayName":"Kiosks","description":"Shared kiosk devices"}'
|
||||
|
||||
# Status code as well as body
|
||||
$r = Invoke-IMMSGraphAPI -Url "deviceManagement/deviceCategories/$id" -HttpMethod DELETE -FullResponseObject
|
||||
$r.StatusCode
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Working with policies
|
||||
|
||||
The policy commands pass **policy objects** (`IntunePolicyBase`) down the pipeline. Get
|
||||
them from a tenant with `Get-IMGraphPolicies` or from files with
|
||||
`Get-IMGraphPolicyFromFile`; `Import`, `Export`, `Copy`, `Remove` and `Compare` consume
|
||||
them.
|
||||
|
||||
### Get-IMGraphPolicies
|
||||
|
||||
List policies of one or more types or groups. Shared list URLs are coalesced and
|
||||
batched. Returns `IntunePolicyBase[]` - each with `Name`, `Id`, `PolicyType`, `Object`
|
||||
(the Graph JSON) and, with `-IncludeAssignments`, `Object.assignments`.
|
||||
|
||||
| Set | Parameters | Notes |
|
||||
|---|---|---|
|
||||
| PolicyType | `-PolicyType <string[]>` (position 0, pipeline) | |
|
||||
| PolicyGroup | `-PolicyGroup <string[]>` (position 0, pipeline) | Every type in the group. |
|
||||
| Paging | `-Paging NextPage\|AllRemainingPages` | Continue a paged listing. |
|
||||
|
||||
Common: `-NameFilter <string>` (server-side where the endpoint allows, always re-checked
|
||||
client-side), `-IncludeAssignments`, `-SinglePage`, `-TokenId`.
|
||||
|
||||
```powershell
|
||||
Get-IMGraphPolicies -PolicyType CompliancePolicies
|
||||
Get-IMGraphPolicies -PolicyGroup DeviceConfiguration -IncludeAssignments
|
||||
Get-IMGraphPolicies -PolicyType SettingsCatalog -NameFilter 'Baseline'
|
||||
'CompliancePolicies', 'ConditionalAccess' | Get-IMGraphPolicies
|
||||
```
|
||||
|
||||
### Get-IMGraphPolicyFromFile
|
||||
|
||||
Load exported json files as policy objects, resolving each file's policy type from its
|
||||
`@odata.type` and folder. The result is what `Import-IMGraphPolicy` and
|
||||
`Compare-IMGraphPolicy` accept.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-InputObject` (`-FileInfo`) | `IO.FileInfo[]`, pipeline | The files. |
|
||||
| `-FromPolicyTypes` | `IntunePolicyTypeBase[]` | Narrow the candidate types (e.g. when the folder name does not match). |
|
||||
| `-TenantId` | string | Record the source tenant on the objects. |
|
||||
|
||||
```powershell
|
||||
Get-ChildItem C:\IntuneExport\CompliancePolicies\*.json | Get-IMGraphPolicyFromFile
|
||||
```
|
||||
|
||||
### Import-IMGraphPolicy
|
||||
|
||||
Create the piped policies in the tenant. Types are imported in dependency order (scope
|
||||
tags and filters first, policy sets last), references are translated where the export
|
||||
carries the information to do so - assignment groups, scope tags, targeted apps, ADMX
|
||||
setting ids - and per-type hooks run (Win32 content upload, ADMX/ADML files, Terms of
|
||||
Use PDF). Returns one result per policy with `ImportedObject` (the created policy),
|
||||
`SourceObject` and `Success`.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-InputObject` | `IntunePolicyBase[]`, pipeline | From `Get-IMGraphPolicyFromFile` or another tenant's `Get-IMGraphPolicies`. |
|
||||
| `-TokenId` | int | Destination tenant. |
|
||||
|
||||
```powershell
|
||||
# Everything under a folder
|
||||
Get-ChildItem C:\IntuneExport -Recurse -Filter *.json | Get-IMGraphPolicyFromFile | Import-IMGraphPolicy
|
||||
|
||||
# Tenant to tenant without touching disk
|
||||
$src = (Get-IMAuthToken | Where-Object TenantName -eq 'Lab').TokenId
|
||||
$dst = (Get-IMAuthToken | Where-Object TenantName -eq 'Prod').TokenId
|
||||
Get-IMGraphPolicies -PolicyType CompliancePolicies -TokenId $src | Import-IMGraphPolicy -TokenId $dst
|
||||
```
|
||||
|
||||
### Export-IMGraphPolicy
|
||||
|
||||
Write the piped policies to json under the export folder, one file per policy, in the
|
||||
type's subfolder. Assignments, scope-tag names and organization tokens follow the
|
||||
`IntuneManagerExportSettings` passed in.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-InputObject` | `IntunePolicyBase[]`, pipeline | |
|
||||
| `-ExportSettings` | `IntuneManagerExportSettings` | Required. `[IntuneManagerExportSettings]::new()` starts from the saved settings. |
|
||||
| `-PassThru` | switch | Emit the full path of each written file. |
|
||||
|
||||
```powershell
|
||||
$s = [IntuneManagerExportSettings]::new()
|
||||
$s.ExportFolder = 'C:\IntuneExport'
|
||||
$s.ExportAssignments = $true
|
||||
Get-IMGraphPolicies -PolicyType CompliancePolicies | Export-IMGraphPolicy -ExportSettings $s -PassThru
|
||||
```
|
||||
|
||||
### Copy-IMGraphPolicy
|
||||
|
||||
Create a copy of each piped policy - in the same tenant, or in another with `-TokenId`.
|
||||
Returns the new policies.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-InputObject` | `IntunePolicyBase[]`, pipeline | |
|
||||
| `-Name` | string | Required. Name of the copy. With several inputs, use the patterns instead. |
|
||||
| `-Description` | string | |
|
||||
| `-CopyFromPatternName` / `-CopyFromPatternDescription` | string | Substring in the source name/description replaced by `-Name` / `-Description` - for copying many at once. |
|
||||
| `-ScopeTagIds` | string[] | Scope tags for the copy. Omitted, the copy inherits the source's; an empty array clears them. |
|
||||
| `-TokenId` | int | Destination tenant. |
|
||||
|
||||
The Copy dialog in the UI pre-fills the name from the type's `CopyDefaultName` template
|
||||
where one is set (`%Name% Copy`); from a script the name is always what you pass.
|
||||
|
||||
```powershell
|
||||
Get-IMGraphPolicies -PolicyType CompliancePolicies -NameFilter 'Pilot - W11' |
|
||||
Copy-IMGraphPolicy -Name 'Prod - W11'
|
||||
|
||||
# Many at once: "Pilot - X" -> "Prod - X"
|
||||
Get-IMGraphPolicies -PolicyGroup DeviceConfiguration -NameFilter 'Pilot - ' |
|
||||
Copy-IMGraphPolicy -CopyFromPatternName 'Pilot - ' -Name 'Prod - '
|
||||
```
|
||||
|
||||
### Remove-IMGraphPolicy
|
||||
|
||||
Delete the piped policies. Supports `-WhatIf` and `-Confirm` (ConfirmImpact Medium).
|
||||
Batched when batching is on. Returns the deleted policies.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-InputObject` | `IntunePolicyBase[]`, pipeline | |
|
||||
| `-TokenId` | int | |
|
||||
|
||||
```powershell
|
||||
Get-IMGraphPolicies -PolicyType DeviceCategories -NameFilter '[Test]' | Remove-IMGraphPolicy -WhatIf
|
||||
Get-IMGraphPolicies -PolicyType DeviceCategories -NameFilter '[Test]' | Remove-IMGraphPolicy -Confirm:$false
|
||||
```
|
||||
|
||||
### Compare-IMGraphPolicy
|
||||
|
||||
Compare two or more policies property by property, or run one of the compare providers
|
||||
over pairs. Returns rows of `Property`, `Value1`, `Value2`, `Match`.
|
||||
|
||||
| Set | Parameters | Notes |
|
||||
|---|---|---|
|
||||
| Direct | `-Policies <object[]>` (position 0, pipeline) | Two or more policy objects. |
|
||||
| ExportFiles | `-ExportFiles <CompareExportFilesProvider>` | Each tenant policy vs its exported file. |
|
||||
| IntuneWithExport | `-IntuneWithExport <CompareIntuneWithExportProvider>` | |
|
||||
| NamedObjects | `-NamedObjects <CompareNamedObjectsProvider>` | Pairs matched by name pattern. |
|
||||
| ExportedFolders | `-ExportedFolders <CompareExportedFoldersProvider>` | Two export folders. |
|
||||
|
||||
The provider sets take `-PolicyGroupIds <string[]>` to limit the groups compared.
|
||||
Provider classes are in `Classes/CompareClasses.ps1`; see [Compare.md](Compare.md).
|
||||
|
||||
```powershell
|
||||
$a, $b = Get-IMGraphPolicies -PolicyType CompliancePolicies -NameFilter 'W11' | Select-Object -First 2
|
||||
Compare-IMGraphPolicy -Policies @($a, $b) | Where-Object Match -eq $false
|
||||
|
||||
# A tenant against last night's export
|
||||
$p = [CompareIntuneWithExportProvider]::new()
|
||||
$p.ExportPath = 'C:\IntuneExport'
|
||||
Compare-IMGraphPolicy -IntuneWithExport $p -PolicyGroupIds DeviceConfiguration
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bulk operations
|
||||
|
||||
The bulk commands are the headless form of the Bulk menu. Each returns a summary object
|
||||
with counts and `Duration`. `-PolicyType` and `-PolicyGroup` select what to operate on;
|
||||
`-Filter` is a name filter: a literal, case-insensitive substring of the policy name, the
|
||||
same rule as 3.x. `-Filter '[Test]'` selects names containing the text `[Test]`; there is no
|
||||
regex or wildcard syntax.
|
||||
|
||||
### Start-IMGraphBulkExport
|
||||
|
||||
Export whole policy groups or types to disk. Precedence: `-SettingsFile` < `-ExportSettings`
|
||||
< explicit parameters. Returns `Types`, `Policies`, `Failed`, `Duration`.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-ExportFolder` | string | Root folder. Required unless a settings source provides it. |
|
||||
| `-PolicyType` / `-PolicyGroup` | string[] | Default: every type whose group allows export. |
|
||||
| `-Filter` | string | Name filter. |
|
||||
| `-ExportAssignments` | bool | |
|
||||
| `-AddCompanyName` | bool | Add a tenant-name folder level. |
|
||||
| `-ExportSettings` | `IntuneManagerExportSettings` | A settings instance. |
|
||||
| `-SettingsFile` | string | A file written by `Save-IMGraphBulkExportSettings`. |
|
||||
| `-TokenId` | int | |
|
||||
|
||||
```powershell
|
||||
Start-IMGraphBulkExport -ExportFolder C:\IntuneExport -PolicyGroup DeviceConfiguration
|
||||
Start-IMGraphBulkExport -ExportFolder C:\IntuneExport -ExportAssignments $true -Filter 'Baseline'
|
||||
Start-IMGraphBulkExport -SettingsFile .\nightly-export.json
|
||||
```
|
||||
|
||||
### Start-IMGraphBulkImport
|
||||
|
||||
Import an export folder. Groups are processed in dependency order. Returns `Groups`,
|
||||
`Imported`, `Duration`.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-ImportFolder` | string | Required. |
|
||||
| `-PolicyGroup` | string[] | Default: every group that allows import. |
|
||||
| `-Filter` | string | |
|
||||
| `-ImportType` | string | `alwaysImport` (default), `skipIfExist`, `update`, `replace`, `replace_with_assignments`. |
|
||||
| `-ImportAssignments`, `-ImportScopeTags`, `-ReplaceDependencyIDs` | bool | Persisted as the like-named settings for the session. |
|
||||
| `-TokenId` | int | |
|
||||
|
||||
```powershell
|
||||
Start-IMGraphBulkImport -ImportFolder C:\IntuneExport
|
||||
Start-IMGraphBulkImport -ImportFolder C:\IntuneExport -PolicyGroup DeviceConfiguration -Filter 'Baseline' -ImportType update
|
||||
```
|
||||
|
||||
### Start-IMGraphBulkCopy
|
||||
|
||||
Copy every policy whose name contains a pattern, to the same name with the pattern
|
||||
replaced. Returns `Types`, `Copied`, `Skipped`, `FailedTypes`, `UnknownSelectors`, `Duration`.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-CopyFromPattern` | string | Required. |
|
||||
| `-CopyToPattern` | string | Required. |
|
||||
| `-PolicyType` / `-PolicyGroup` | string[] | Default: every type whose group allows copy. |
|
||||
| `-TokenId` | int | |
|
||||
|
||||
```powershell
|
||||
Start-IMGraphBulkCopy -CopyFromPattern 'Pilot - ' -CopyToPattern 'Prod - ' -PolicyGroup DeviceConfiguration
|
||||
```
|
||||
|
||||
### Start-IMGraphBulkDelete
|
||||
|
||||
Delete every policy in the selected groups that matches the filter. **No `-WhatIf`, no
|
||||
confirmation** - list first with `Get-IMGraphPolicies` using the same filter. Groups are
|
||||
deleted in reverse dependency order. Returns `Groups`, `Deleted`, `Duration`.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-PolicyGroup` | string[] | Required. |
|
||||
| `-Filter` | string | Empty means every object in the group. |
|
||||
| `-TokenId` | int | |
|
||||
|
||||
```powershell
|
||||
Get-IMGraphPolicies -PolicyGroup DeviceConfiguration -NameFilter '[Test]' | Select-Object Name # look first
|
||||
Start-IMGraphBulkDelete -PolicyGroup DeviceConfiguration -Filter '[Test]'
|
||||
```
|
||||
|
||||
### Set-IMGraphBulkAssignments
|
||||
|
||||
Add, replace or remove assignments across types or groups. Handles the three assignment
|
||||
shapes - simple targets, app assignments with intent and per-platform settings, health
|
||||
scripts with schedules. App types that cannot take a filter (web apps) are assigned
|
||||
without it, with a log line. Returns `Types`, `PoliciesScanned`, `PoliciesMatched`,
|
||||
`PoliciesUpdated`, `PoliciesSkipped`, `PoliciesFailed`, `PoliciesUnsupported`, `Duration`.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-Action` | string | `Add`, `Replace`, `Remove`. |
|
||||
| `-Assignments` | `PSCustomObject[]` | Descriptors: `TargetType` (`groupAssignmentTarget`, `exclusionGroupAssignmentTarget`, `allDevicesAssignmentTarget`, `allLicensedUsersAssignmentTarget`), `GroupId`, `GroupName`, `FilterId`, `FilterType` (`include`/`exclude`), `Intent` (apps), `Settings` (hashtable per platform). |
|
||||
| `-Filter` | string | Name filter. |
|
||||
| `-PolicyType` / `-PolicyGroup` | string[] | |
|
||||
| `-AssignmentSettings` | `IntuneManagerAssignmentSettings` | Alternative to the three above. |
|
||||
| `-TokenId` | int | |
|
||||
|
||||
```powershell
|
||||
$a = [PSCustomObject]@{
|
||||
TargetType = 'groupAssignmentTarget'
|
||||
GroupId = '<entra-group-id>'
|
||||
GroupName = 'All Helpdesk Devices'
|
||||
FilterId = '<assignment-filter-id>'
|
||||
FilterType = 'include'
|
||||
}
|
||||
Set-IMGraphBulkAssignments -Action Add -Assignments @($a) -PolicyGroup DeviceConfiguration -Filter 'Baseline'
|
||||
|
||||
# Apps: required install to a group
|
||||
$app = [PSCustomObject]@{ TargetType = 'groupAssignmentTarget'; GroupId = $gid; GroupName = 'Pilot'; Intent = 'required' }
|
||||
Set-IMGraphBulkAssignments -Action Add -Assignments @($app) -PolicyType Applications -Filter 'Office'
|
||||
|
||||
Set-IMGraphBulkAssignments -Action Remove -Assignments @($a) -PolicyGroup DeviceConfiguration
|
||||
```
|
||||
|
||||
See [BulkAssignments.md](BulkAssignments.md) for the settings hashtables.
|
||||
|
||||
### Set-IMGraphBulkScopeTags
|
||||
|
||||
Add, replace or remove scope tags across types or groups; `-CleanupOrphans` removes
|
||||
references to tags that no longer exist. Returns `Types`, `PoliciesScanned`,
|
||||
`PoliciesMatched`, `PoliciesUpdated`, `PoliciesSkipped`, `PoliciesFailed`, `Duration`.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-Action` | string | `Add`, `Replace`, `Remove`. |
|
||||
| `-ScopeTagIds` | string[] | Tag ids (`0` is the default tag). |
|
||||
| `-Filter` | string | |
|
||||
| `-CleanupOrphans` | bool | |
|
||||
| `-PolicyType` / `-PolicyGroup` | string[] | |
|
||||
| `-ScopeTagSettings` | `IntuneManagerScopeTagSettings` | Alternative to the above. |
|
||||
| `-TokenId` | int | |
|
||||
|
||||
```powershell
|
||||
Set-IMGraphBulkScopeTags -Action Add -ScopeTagIds 3, 4 -PolicyGroup DeviceConfiguration
|
||||
Set-IMGraphBulkScopeTags -CleanupOrphans $true
|
||||
```
|
||||
|
||||
### Save-IMGraphBulkExportSettings
|
||||
|
||||
Write an export configuration to a json file that `Start-IMGraphBulkExport -SettingsFile`
|
||||
reads - the way to schedule the same export nightly.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-Path` | string | Required. |
|
||||
| `-ExportSettings` | `IntuneManagerExportSettings` | Required. |
|
||||
| `-PolicyGroup` / `-PolicyType` | string[] | What the file selects. |
|
||||
|
||||
```powershell
|
||||
$s = [IntuneManagerExportSettings]::new()
|
||||
$s.ExportFolder = '\\server\intune\exports'; $s.ExportAssignments = $true
|
||||
Save-IMGraphBulkExportSettings -Path .\nightly-export.json -ExportSettings $s -PolicyGroup DeviceConfiguration, Compliance
|
||||
Start-IMGraphBulkExport -SettingsFile .\nightly-export.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
### Get-IMGraphDocumentation
|
||||
|
||||
Document one policy and return the result object - `BasicInfo`, `FilteredSettings`,
|
||||
`Assignments`, `Scripts`, `CustomTables` and the rest - without writing a file. Useful
|
||||
for building your own report.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-PolicyObject` | pipeline | A policy from `Get-IMGraphPolicies` or `Get-IMGraphPolicyFromFile`. |
|
||||
| `-Language` | string | `en` default; any language the strings ship in. |
|
||||
| `-Options` | hashtable | See [Documentation.md](Documentation.md#options) for every key. |
|
||||
|
||||
```powershell
|
||||
$p = Get-IMGraphPolicies -PolicyType ConditionalAccess | Select-Object -First 1
|
||||
$doc = Get-IMGraphDocumentation -PolicyObject $p
|
||||
$doc.FilteredSettings | Format-Table Name, Value
|
||||
```
|
||||
|
||||
### Start-IMGraphBulkDocumentation
|
||||
|
||||
Document many policies through one or more output providers. Selects by object, type,
|
||||
group, or an export folder. Returns the run summary; files land where each output's
|
||||
options say.
|
||||
|
||||
| Set | Parameters |
|
||||
|---|---|
|
||||
| PolicyObject | `-PolicyObject` (pipeline) |
|
||||
| PolicyType | `-PolicyType <string[]>` |
|
||||
| PolicyGroup | `-PolicyGroup <string[]>` |
|
||||
| Folder | `-SourceFolder <string>` - an export folder. Still needs a signed-in tenant (any tenant) for setting definitions and templates; source-tenant names come from the export's migration table or stay as ids. |
|
||||
|
||||
Common: `-OutputFormat <string>` (required, position 0; comma-separated: `html`, `md`,
|
||||
`word`, `json`, `csv`, `atlassian`), `-Language`, `-Options`.
|
||||
|
||||
```powershell
|
||||
Start-IMGraphBulkDocumentation -OutputFormat html -PolicyGroup Compliance
|
||||
Start-IMGraphBulkDocumentation -OutputFormat 'md,json' -SourceFolder C:\IntuneExport
|
||||
Get-IMGraphPolicies -PolicyType SettingsCatalog -NameFilter 'Baseline' | Start-IMGraphBulkDocumentation -OutputFormat word
|
||||
```
|
||||
|
||||
### Get-IMDocumentationOutput
|
||||
|
||||
List the registered output providers - `Name` and `Value` (what `-OutputFormat` matches).
|
||||
No parameters.
|
||||
|
||||
```powershell
|
||||
Get-IMDocumentationOutput | Format-Table Name, Value
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Settings
|
||||
|
||||
Settings are read by key. Three stores: the registry (Windows default), a json file, or
|
||||
memory. Effective value = tenant override, else global, else the registered default.
|
||||
See [Settings.md](Settings.md).
|
||||
|
||||
### Get-IMSetting
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-Key` | string, position 0, pipeline | Omit for every registered setting (implies `-Detailed`). |
|
||||
| `-Scope` | string | `Effective` (default), `Global`, `Tenant`. |
|
||||
| `-TenantID` | string | Default: the connected tenant. |
|
||||
| `-SubPath` | string | For keys stored under a sub-path (none for most). |
|
||||
| `-Detailed` | switch | Include `Source` - Tenant, Global or Default. |
|
||||
|
||||
```powershell
|
||||
Get-IMSetting ExportFolder
|
||||
Get-IMSetting UseBatchAPI -Detailed
|
||||
Get-IMSetting | Where-Object Source -ne 'Default' # everything actually configured
|
||||
```
|
||||
|
||||
### Set-IMSetting
|
||||
|
||||
Supports `-WhatIf`.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-Key` | string, position 0 | Required. |
|
||||
| `-Value` | position 1 | Required; `$null` removes the value. |
|
||||
| `-Scope` | string | `Global` (default) or `Tenant`. |
|
||||
| `-TenantID`, `-SubPath`, `-PassThru` | | |
|
||||
|
||||
```powershell
|
||||
Set-IMSetting ExportFolder 'C:\Intune\Export'
|
||||
Set-IMSetting ExportFolder '\\server\intune\contoso' -Scope Tenant
|
||||
Set-IMSetting UseParallelBatchAPI $true
|
||||
```
|
||||
|
||||
### Remove-IMSetting
|
||||
|
||||
Remove a stored value so the next level applies. Supports `-WhatIf`.
|
||||
|
||||
| Parameter | Type | Notes |
|
||||
|---|---|---|
|
||||
| `-Key` | string, position 0 | Required. |
|
||||
| `-Scope` | string | `Global` (default) or `Tenant`. |
|
||||
| `-TenantID`, `-SubPath` | | |
|
||||
|
||||
```powershell
|
||||
Remove-IMSetting ExportFolder -Scope Tenant
|
||||
```
|
||||
|
||||
### Get-IMSettingDefinition
|
||||
|
||||
What the module knows about its settings: `Key`, `Title`, `Section`, `Type`,
|
||||
`DefaultValue`, `Description`. Wildcards on both parameters.
|
||||
|
||||
| Parameter | Type |
|
||||
|---|---|
|
||||
| `-Key` | string, position 0 |
|
||||
| `-Section` | string |
|
||||
|
||||
```powershell
|
||||
Get-IMSettingDefinition | Format-Table Key, Section, Type, DefaultValue
|
||||
Get-IMSettingDefinition -Key *Export*
|
||||
```
|
||||
|
||||
### Use-IMSettingsStore
|
||||
|
||||
Choose the store for the rest of the session. Supports `-WhatIf`.
|
||||
|
||||
| Set | Parameters | Notes |
|
||||
|---|---|---|
|
||||
| Memory (default) | `-Memory` | Nothing is read from or written to the machine. |
|
||||
| Json | `-Path <file>` (position 0) | Created if missing. |
|
||||
| Registry | `-Registry` | Windows only. |
|
||||
|
||||
`-Seed` copies the persisted settings into the new store; `-PassThru` returns the store.
|
||||
|
||||
```powershell
|
||||
# The runbook pattern: an empty store, then the run's configuration from source control
|
||||
Use-IMSettingsStore -Memory
|
||||
Import-IMSettingsStore -Path .\runbook-settings.json
|
||||
|
||||
Use-IMSettingsStore -Path 'D:\shared\IntuneManagement.json'
|
||||
```
|
||||
|
||||
### Get-IMSettingsStore
|
||||
|
||||
Which store is active and whether it persists: `Mode`, `Persisted`, `Path`.
|
||||
`-IncludeValues` adds every value.
|
||||
|
||||
```powershell
|
||||
Get-IMSettingsStore
|
||||
(Get-IMSettingsStore -IncludeValues).Values | Format-Table
|
||||
```
|
||||
|
||||
### Export-IMSettingsStore
|
||||
|
||||
Write the whole active store to a json file - capture a working configuration once and
|
||||
commit it. Supports `-WhatIf`. Takes `-Path` (position 0, required); the folder is
|
||||
created and an existing file replaced.
|
||||
|
||||
```powershell
|
||||
Export-IMSettingsStore -Path .\intune-settings.json
|
||||
```
|
||||
|
||||
### Import-IMSettingsStore
|
||||
|
||||
Merge a json settings file into the active store (existing keys are overwritten, others
|
||||
kept). Supports `-WhatIf`. Takes `-Path` (position 0, required). A missing file is
|
||||
reported, not silently ignored.
|
||||
|
||||
```powershell
|
||||
Use-IMSettingsStore -Memory
|
||||
Import-IMSettingsStore -Path .\runbook-settings.json
|
||||
Import-IMSettingsStore -Path .\baseline.json -WhatIf # which store would this land in?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UI
|
||||
|
||||
### Show-IMMainWindow
|
||||
|
||||
Show the application window for the active UI backend (WPF on Windows, Avalonia
|
||||
elsewhere). Takes an optional `-View` to open on. Not available when the `UI` folder has
|
||||
been removed from the deployment, and not for use from an ordinary `pwsh` session on
|
||||
macOS - launch with `Start-Avalonia.command` there.
|
||||
|
||||
```powershell
|
||||
Import-Module .\IntuneManagement.psd1
|
||||
Show-IMMainWindow
|
||||
```
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
# Compare
|
||||
|
||||
## Purpose
|
||||
|
||||
The compare feature compares policies from Intune, export folders, selected objects, or named providers. It is used by the UI to show differences and by bulk compare workflows to generate structured output.
|
||||
|
||||
## Main Files
|
||||
|
||||
| File | Responsibility |
|
||||
| --- | --- |
|
||||
| `Public/Compare-GraphPolicy.ps1` | public entry point into compare UI. |
|
||||
| `Internal/Compare.ps1` | compare engine and output providers. |
|
||||
| `Classes/CompareClasses.ps1` | compare provider classes. |
|
||||
| `UI/Extensions/CompareUI.ps1` | compare forms and UI orchestration. |
|
||||
|
||||
## Compare Providers
|
||||
|
||||
Compare providers abstract the source of objects:
|
||||
|
||||
| Provider | Source |
|
||||
| --- | --- |
|
||||
| `CompareExportFilesProvider` | exported JSON files. |
|
||||
| `CompareIntuneWithExportProvider` | live Intune versus export folder. |
|
||||
| `CompareNamedObjectsProvider` | named live objects. |
|
||||
| `CompareExportedFoldersProvider` | two export folders. |
|
||||
|
||||
## Object Normalization
|
||||
|
||||
Before comparing, policies are converted to a compare-friendly JSON/object form:
|
||||
|
||||
1. full policy object is resolved when needed;
|
||||
2. ignored core properties are removed;
|
||||
3. settings catalog values can be flattened into readable keys;
|
||||
4. documentation plugin output can be used when available;
|
||||
5. output rows are generated with property paths and differences.
|
||||
|
||||
## Compare Providers - options
|
||||
|
||||
Each provider is a class whose properties are the options. Construct it, set the
|
||||
properties, pass it to `Compare-IMGraphPolicy` through the matching parameter (or select
|
||||
it in the Bulk > Compare form, which shows the same fields).
|
||||
|
||||
| Provider | Parameter | Properties |
|
||||
|---|---|---|
|
||||
| `CompareExportFilesProvider` | `-ExportFiles` | `ExportPath` - the export root; `NameFilter` - only policies whose name contains this. |
|
||||
| `CompareIntuneWithExportProvider` | `-IntuneWithExport` | `ExportPath`, `NameFilter` - as above; each live policy is compared with its exported file. |
|
||||
| `CompareNamedObjectsProvider` | `-NamedObjects` | `SourcePattern`, `ComparePattern` - name patterns that pair objects (`Pilot - X` with `Prod - X`); `SavePath` - where the result is written; `RemoveProperties` - properties dropped before comparing, default `@('Id')`. |
|
||||
| `CompareExportedFoldersProvider` | `-ExportedFolders` | `SourcePath`, `ComparePath` - two export roots; `NameFilter`. |
|
||||
|
||||
All four take `-PolicyGroupIds <string[]>` on the command to limit the groups compared.
|
||||
|
||||
```powershell
|
||||
$p = [CompareNamedObjectsProvider]::new()
|
||||
$p.SourcePattern = 'Pilot - '
|
||||
$p.ComparePattern = 'Prod - '
|
||||
$p.SavePath = 'C:\Reports\pilot-vs-prod.csv'
|
||||
Compare-IMGraphPolicy -NamedObjects $p -PolicyGroupIds DeviceConfiguration, Compliance
|
||||
```
|
||||
|
||||
## Run options
|
||||
|
||||
Set once per run with `Set-CompareRuntimeOptions` (the Bulk > Compare form does this for
|
||||
you); the compare functions read them.
|
||||
|
||||
| Option | Values | Effect |
|
||||
|---|---|---|
|
||||
| `CompareType` | `Property` (default), `Documentation` | Which comparison type - see the strategies below. `Documentation` compares what the documentation engine renders, so two policies that document the same are equal even if raw json differs. |
|
||||
| `IgnoreCoreProperties` | bool | Skip id, timestamps, version and the other server-side properties. |
|
||||
| `SaveType` | `objectType`, `all` | One output file per object type, or one file for everything. |
|
||||
| `OutputProvider` | CSV or Json provider instance | Chosen by the output file's extension when saving from the form (`.csv` / `.json`). |
|
||||
| `CsvDelimiter` | string | CSV delimiter; default is the culture's list separator. |
|
||||
| `ObjectSeparator` | string | Separator between items of a multi-value property in the output. |
|
||||
| `SkipAssignments` | bool | Leave assignments out of the comparison. |
|
||||
|
||||
The single-object form adds a result filter (All / Mismatch / Match) that only affects the
|
||||
grid, not the saved file.
|
||||
|
||||
## Compare Strategies
|
||||
|
||||
| Strategy | Function | Notes |
|
||||
| --- | --- | --- |
|
||||
| property compare | `Compare-ObjectsBasedonProperty` | compares raw property paths. |
|
||||
| settings compare | `Compare-ObjectsBasedonSettings` | extracts settings catalog and intent settings. |
|
||||
| documentation compare | `Compare-ObjectsBasedonDocumentation` | uses `Invoke-ObjectDocumentation` when present. |
|
||||
|
||||
## Output
|
||||
|
||||
Bulk compare writes through an output provider. Two ship: **CSV** (`CompareCSVOutputProvider`,
|
||||
honours `CsvDelimiter`) and **JSON** (`CompareJsonOutputProvider`). The provider is picked
|
||||
from the output file's extension, or selected in the form.
|
||||
|
||||
Typical output fields include:
|
||||
|
||||
| Field | Meaning |
|
||||
| --- | --- |
|
||||
| object name/type | source policy identity. |
|
||||
| property path | location of difference. |
|
||||
| source value | value in left/source object. |
|
||||
| target value | value in right/target object. |
|
||||
| result type | same, different, missing, extra, etc. |
|
||||
|
||||
## Extension Notes
|
||||
|
||||
Add new compare behavior in `Internal/Compare.ps1` when the comparison semantics change. Add new source behavior by creating a provider class in `Classes/CompareClasses.ps1`. Keep UI code limited to collecting options and displaying results.
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
# Running on macOS and Linux (experimental)
|
||||
|
||||
IntuneManagement.Next runs outside Windows through the **Avalonia** UI backend. The
|
||||
engine is the same one the Windows build uses; only the presentation layer differs.
|
||||
|
||||
> **Experimental.** Linux has been exercised during development. macOS has been
|
||||
> started end to end (module import, sign-in, policy browsing) on Apple Silicon with
|
||||
> PowerShell 7.6.6, but the native GUI paths have had far less mileage than Windows.
|
||||
> Treat a macOS run as a bug hunt.
|
||||
|
||||
The app says so itself: on the first non-Windows launch it shows a notice with a
|
||||
**Do not show this message again** checkbox. See [Turning the notice back on](#turning-the-notice-back-on).
|
||||
|
||||
## Requirements
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| PowerShell | **7.4 or newer** (`pwsh`) on Linux and macOS - 7.6 / 7.7 included; nothing is pinned to a particular 7.x. pwsh bundles its own .NET runtime, so **no separate .NET install is needed** on either platform. Windows PowerShell 5.1 remains supported by WPF, not Avalonia. |
|
||||
| Display | A desktop session. X11 or Wayland on Linux, Aqua on macOS. |
|
||||
|
||||
## Launching
|
||||
|
||||
```sh
|
||||
./Start-Avalonia.command # macOS (also double-clickable in Finder) and Linux
|
||||
```
|
||||
|
||||
or directly:
|
||||
|
||||
```sh
|
||||
pwsh -NoProfile -File ./UI/Avalonia/Start-Avalonia.ps1
|
||||
```
|
||||
|
||||
`-ThemeVariant Dark` and `-Provider OAuth` (or `MSAL`, `MgGraph`: the authentication
|
||||
provider for this session only, without touching the saved setting) are supported by
|
||||
both entry points.
|
||||
|
||||
On macOS both entry points run the script through the **main-thread hook**
|
||||
(`Bin/MainThreadHook/IntuneManagement.MainThreadHook.dll`, ~10 KB). Cocoa only allows
|
||||
the GUI on the process's first thread, and a normal `pwsh` pipeline runs on a worker
|
||||
thread - even when single-threaded. The hook is a .NET *startup hook*: `pwsh` loads it
|
||||
before its own `Main` runs, on the main thread, and it opens a `UseCurrentThread`
|
||||
runspace there and runs `Start-Avalonia.ps1` inside the very same `pwsh`. No second
|
||||
engine, no second runtime: whatever pwsh you have brings its engine, its .NET and its
|
||||
`$PSHOME/ref` compile references, all consistent with each other. Direct script
|
||||
invocation re-launches itself through the hook when it notices it is not on the main
|
||||
thread. See [The macOS main-thread hook](#the-macos-main-thread-hook).
|
||||
|
||||
`Start-Avalonia.ps1` sets `IM_UI_BACKEND=Avalonia` for you. On **Linux or Windows**,
|
||||
setting that variable and importing the module by hand works too (on Windows use
|
||||
`pwsh -STA`):
|
||||
|
||||
```powershell
|
||||
$env:IM_UI_BACKEND = 'Avalonia'
|
||||
Import-Module ./IntuneManagement.psd1 -Force
|
||||
Show-IMMainWindow -View 'IntuneManagement'
|
||||
```
|
||||
|
||||
On macOS do not import the GUI manually in an ordinary `pwsh` session: the native
|
||||
host rejects initialization off the main thread. Use one of the entry points above.
|
||||
Headless imports with the `None` backend are unaffected.
|
||||
|
||||
Without `IM_UI_BACKEND`, the module defaults to the headless `None` backend off
|
||||
Windows - useful for automation, and the reason `Connect-IMIntuneManagement` and the
|
||||
bulk cmdlets work fine on a Mac or a Linux box with no display at all.
|
||||
|
||||
## What does not work off Windows
|
||||
|
||||
These degrade with a log message rather than an error:
|
||||
|
||||
| Feature | Why |
|
||||
|---|---|
|
||||
| **Word documentation output** | Needs `Microsoft.Office.Interop.Word` COM automation. HTML, Markdown, CSV and JSON output are unaffected. |
|
||||
| **MSI property extraction** on app import | Needs the `WindowsInstaller.Installer` COM object. Other app types import normally. |
|
||||
| **WAM / broker sign-in** | Windows-only. Authentication falls back to the system browser. |
|
||||
|
||||
The `Default` theme follows the OS on all three platforms: the Windows app theme, the
|
||||
macOS appearance setting, and the GNOME colour scheme on Linux. Linux desktops that are
|
||||
not GNOME have no common way to report a preference and resolve to Light; pick Light or
|
||||
Dark explicitly in Settings there.
|
||||
|
||||
Token cache persistence *is* implemented on both platforms - macOS uses the Keychain
|
||||
and Linux uses libsecret (gnome-keyring / KWallet), via `MsalCacheHelper`. On a Linux
|
||||
box with no keyring daemon, expect to sign in every session.
|
||||
|
||||
## Turning the notice back on
|
||||
|
||||
The startup notice is a normal setting. Clear **Settings -> General -> Hide
|
||||
experimental platform notice** to see it again.
|
||||
|
||||
To preview it on Windows, where it never appears on its own:
|
||||
|
||||
```powershell
|
||||
$env:IM_EXPERIMENTAL_NOTICE = '1'
|
||||
./UI/Avalonia/Start-Avalonia.ps1
|
||||
```
|
||||
|
||||
## Rebuilding the Avalonia binaries
|
||||
|
||||
`Bin/Avalonia` is committed and holds the natives for all three platforms side by
|
||||
side - `.dll` for Windows, `.so` for Linux, `.dylib` for macOS - plus the managed
|
||||
Avalonia assemblies, which are platform-neutral.
|
||||
|
||||
To rebuild for the machine you are on:
|
||||
|
||||
```powershell
|
||||
./UI/Avalonia/Bootstrap/Restore-AvaloniaBinaries.ps1
|
||||
```
|
||||
|
||||
To build another platform's natives **without that platform** - how the committed
|
||||
macOS binaries were produced, from Windows:
|
||||
|
||||
```powershell
|
||||
./UI/Avalonia/Bootstrap/Restore-AvaloniaBinaries.ps1 -RuntimeIdentifier osx-arm64
|
||||
```
|
||||
|
||||
NuGet serves the RID-specific native packages regardless of the host OS. The macOS
|
||||
dylibs are **universal binaries** (x86_64 + arm64 slices), so `osx-arm64` and
|
||||
`osx-x64` produce identical files and either one covers every Mac.
|
||||
|
||||
`dotnet publish` does not clean its output directory, which is what lets one
|
||||
`Bin/Avalonia` hold all three platforms at once. It does overwrite
|
||||
`AvaloniaPayload.deps.json` with the last RID published; that file is inert here,
|
||||
because the module loads the assemblies with `Add-Type -Path` rather than through
|
||||
the `dotnet` host.
|
||||
|
||||
## The macOS main-thread hook
|
||||
|
||||
Source: [`UI/Avalonia/Bootstrap/MainThreadHook/StartupHook.cs`](../UI/Avalonia/Bootstrap/MainThreadHook/StartupHook.cs).
|
||||
Binary: `Bin/MainThreadHook/IntuneManagement.MainThreadHook.dll` - **committed**, so a
|
||||
plain clone or source ZIP is a complete, runnable download.
|
||||
|
||||
`Start-Avalonia.command` sets two environment variables and runs `pwsh -File`:
|
||||
|
||||
| Variable | Purpose |
|
||||
|---|---|
|
||||
| `DOTNET_STARTUP_HOOKS` | Path of the hook DLL. The .NET runtime calls its `StartupHook.Initialize()` on the main thread before `pwsh`'s own `Main`. |
|
||||
| `IM_MAIN_THREAD_HOOK=1` | Engages the hook. Without it `Initialize` returns at once and pwsh starts normally, so child processes are unaffected (the hook also clears both variables from its own environment). |
|
||||
|
||||
`Initialize` then parses `-File <script> [args]` from the command line, opens a
|
||||
`UseCurrentThread` runspace, runs the script and terminates the process with the
|
||||
script's exit code. The PowerShell side recognises an engaged hook by the presence of
|
||||
the `[IntuneManagement.MainThreadHook.MainThread]` type; `::Verify()` throws when called
|
||||
off the main thread, which `Tests/MainThreadHook.Tests.ps1` and the UI smoke test use.
|
||||
|
||||
Compatibility comes from two choices in the project file: it targets `net8.0` (the
|
||||
runtime of pwsh 7.4, the oldest supported engine) and references
|
||||
`Microsoft.PowerShell.SDK` **7.4.x at compile time only**. .NET binds a reference to a
|
||||
lower `System.Management.Automation` version against whatever newer one pwsh loaded,
|
||||
so the one DLL works in 7.4, 7.5, 7.6, 7.7 and later on .NET 8, 9, 10 and later
|
||||
without a rebuild. Keep it that way: do not bump the SDK reference to "latest".
|
||||
|
||||
Rebuild only when `StartupHook.cs` changes (needs the .NET 8+ SDK and NuGet access),
|
||||
then commit the DLL:
|
||||
|
||||
```powershell
|
||||
./UI/Avalonia/Bootstrap/Publish-MainThreadHook.ps1
|
||||
```
|
||||
|
||||
The project's own tests then run the hook inside a child `pwsh` on every OS (the
|
||||
mechanism is not macOS-specific; only Cocoa needs it) and check thread ownership,
|
||||
parameter forwarding, exit codes, module import and that an un-engaged hook is inert,
|
||||
plus a native-backend smoke test on a Mac desktop session that initializes Avalonia and
|
||||
exercises a dispatcher callback and button event.
|
||||
|
||||
Before calling macOS supported, test on Intel and Apple Silicon: startup, sign-in
|
||||
and browser return, message boxes, Bulk Compare pickers, file dialogs, clipboard,
|
||||
closing child windows and quitting/relaunching.
|
||||
|
||||
## Reporting problems
|
||||
|
||||
Include the platform and architecture (`[System.Runtime.InteropServices.RuntimeInformation]::OSDescription`
|
||||
and `::ProcessArchitecture`), the PowerShell version, and `IntuneManagement.log` from
|
||||
the app data folder.
|
||||
@@ -0,0 +1,315 @@
|
||||
# Documenting policies
|
||||
|
||||
How to produce documentation from a script or a pipeline: the outputs, how to select
|
||||
what gets documented, every option, and what each output provider expects. The UI's
|
||||
Bulk > Document form drives exactly the same engine, so anything set there can be set
|
||||
from `-Options`.
|
||||
|
||||
For the engine's internals - handlers, input providers, the customizer hooks and how to
|
||||
add a renderer for a new type - read the source under `Internal/Documentation/`.
|
||||
|
||||
## Contents
|
||||
|
||||
- [The two commands](#the-two-commands)
|
||||
- [Outputs](#outputs)
|
||||
- [Selecting what to document](#selecting-what-to-document)
|
||||
- [Documenting from an export folder](#documenting-from-an-export-folder)
|
||||
- [Options](#options)
|
||||
- [Per-output options](#per-output-options)
|
||||
- [Where files land and how they are named](#where-files-land-and-how-they-are-named)
|
||||
- [Languages](#languages)
|
||||
- [Which types document, and how](#which-types-document-and-how)
|
||||
- [Pipeline examples](#pipeline-examples)
|
||||
|
||||
## The two commands
|
||||
|
||||
| Command | Use when |
|
||||
|---|---|
|
||||
| `Start-IMGraphBulkDocumentation -OutputFormat <formats> ...` | You want files. Selects by type, group, object or export folder, runs every selected policy through the chosen outputs. |
|
||||
| `Get-IMGraphDocumentation -PolicyObject <policy>` | You want the data. Returns one policy's documentation as an object - `BasicInfo`, `FilteredSettings`, `Assignments`, `Scripts`, `CustomTables` - to build your own report from. |
|
||||
|
||||
Both take `-Language` and `-Options`. Full parameter tables are in
|
||||
[CommandReference.md](CommandReference.md#documentation).
|
||||
|
||||
## Outputs
|
||||
|
||||
`-OutputFormat` names one or more providers, comma-separated. `Get-IMDocumentationOutput`
|
||||
lists them.
|
||||
|
||||
| Value | Produces | Notes |
|
||||
|---|---|---|
|
||||
| `html` | one `.html`, or one per policy | Self-contained; the CSS is inlined. |
|
||||
| `md` | one `.md`, or one per policy | Optionally with the CSS inlined for renderers that honour it. |
|
||||
| `word` | one `.docx` | **Windows only** - needs Word installed (COM automation). Silently degrades elsewhere. |
|
||||
| `json` | one `.json`, or one per policy | The raw documentation objects - the same data `Get-IMGraphDocumentation` returns. |
|
||||
| `csv` | one `.csv` per policy type | Flat rows; good for spreadsheets and diffing. |
|
||||
| `atlassian` | one file, or one per policy, in Confluence storage format | Paste into a Confluence page, or push through the REST API. Headings carry stable anchors that downstream tooling can link to - see [DocumentationAtlassianOutput.md](DocumentationAtlassianOutput.md). |
|
||||
|
||||
```powershell
|
||||
Start-IMGraphBulkDocumentation -OutputFormat html -PolicyGroup Compliance
|
||||
Start-IMGraphBulkDocumentation -OutputFormat 'html,md,json' -PolicyGroup Compliance
|
||||
```
|
||||
|
||||
## Selecting what to document
|
||||
|
||||
One parameter set per way of choosing:
|
||||
|
||||
```powershell
|
||||
# Every policy of one or more types
|
||||
Start-IMGraphBulkDocumentation -OutputFormat html -PolicyType CompliancePolicies, ConditionalAccess
|
||||
|
||||
# Every type in one or more groups
|
||||
Start-IMGraphBulkDocumentation -OutputFormat html -PolicyGroup DeviceConfiguration, EndpointSecurity
|
||||
|
||||
# Specific policies, from the pipeline
|
||||
Get-IMGraphPolicies -PolicyType SettingsCatalog -NameFilter 'Baseline' |
|
||||
Start-IMGraphBulkDocumentation -OutputFormat html
|
||||
|
||||
# Everything in an export folder (see the next section)
|
||||
Start-IMGraphBulkDocumentation -OutputFormat html -SourceFolder C:\IntuneExport
|
||||
```
|
||||
|
||||
Type and group ids are the ones in the README's policy table. Nothing is documented that
|
||||
the signed-in identity cannot read; a type it has no access to is skipped with a log line.
|
||||
|
||||
## Documenting from an export folder
|
||||
|
||||
`-SourceFolder` documents the json files of an export instead of live policies. Two
|
||||
things to know:
|
||||
|
||||
- **It still needs a signed-in tenant - but any tenant, not the source.** Setting
|
||||
definitions, templates and category names are generic Intune data and are looked up
|
||||
live from whatever tenant is connected. The policies themselves come from the files.
|
||||
- **Source-tenant names are not looked up.** The mode sets
|
||||
`SourceTenantUnavailable = $true`, which skips every lookup only the source tenant could
|
||||
answer: assignment group names, scope tag names, filter names, app names. Those are
|
||||
resolved from the export's migration table when the export was made with one, and
|
||||
shown as ids otherwise.
|
||||
|
||||
This is the mode for documenting a tenant you no longer have access to, or for
|
||||
generating documentation in a pipeline from an export artifact rather than from a live
|
||||
sign-in with broad read rights.
|
||||
|
||||
## Options
|
||||
|
||||
`-Options` is a hashtable. Anything not given falls back to the value saved by the UI's
|
||||
documentation form, then to the default below. All keys are optional.
|
||||
|
||||
| Key | Default | Effect |
|
||||
|---|---|---|
|
||||
| `IncludeScripts` | `$true` | Include script bodies (PowerShell, shell, remediation) in the output. |
|
||||
| `ExcludeScriptSignature` | `$false` | Strip Authenticode signature blocks from included scripts. |
|
||||
| `IncludePolicyId` | `$false` | Add the policy's id to its basic information. |
|
||||
| `ExcludeAssignments` | `$false` | Leave assignments out. |
|
||||
| `SkipNotConfigured` | `$false` | Omit settings that are not configured. |
|
||||
| `SkipDefaultValues` | `$false` | Omit settings still at their default. |
|
||||
| `SkipDisabled` | `$true` | Omit settings that are disabled. |
|
||||
| `SetUnconfiguredValue` | `$true` | Render unconfigured settings with the text below instead of blank. |
|
||||
| `SetDefaultValue` | `$false` | Render settings at default with their default value. |
|
||||
| `NotConfiguredText` | `notConfigured` | Text for an unconfigured setting: `notConfigured` (the language string), `empty`, or `asis`. |
|
||||
| `ValueOutputProperty` | `value` | For ADMX settings: `value`, or `valueWithLabel` to include the setting's label. |
|
||||
| `PropertySeparator` | `;` | Separator between values of a multi-value property. |
|
||||
| `ObjectSeparator` | newline | Separator between items of a collection. |
|
||||
| `SkipDocumentInfo` | `$false` | Omit the "documented by / on" block. |
|
||||
| `FallbackDocumentation` | `$true` | Types with no dedicated renderer are documented as basic information plus one row per property. Off, they are skipped with a warning. (The UI calls this *Document unsupported types*.) |
|
||||
| `SourceTenantUnavailable` | `$false` | Set automatically by `-SourceFolder`; see above. The old name `OfflineDocumentation` still works. |
|
||||
| `Outputs` | `@{}` | Per-output options - next section. |
|
||||
|
||||
```powershell
|
||||
$o = @{
|
||||
SkipNotConfigured = $true
|
||||
SkipDefaultValues = $true
|
||||
IncludePolicyId = $true
|
||||
ExcludeAssignments = $true
|
||||
}
|
||||
Start-IMGraphBulkDocumentation -OutputFormat html -PolicyGroup Compliance -Options $o
|
||||
```
|
||||
|
||||
## Per-output options
|
||||
|
||||
Under `Outputs`, one hashtable per provider value. A key given here wins over the saved
|
||||
UI setting, which wins over the default.
|
||||
|
||||
```powershell
|
||||
$o = @{
|
||||
Outputs = @{
|
||||
html = @{ HTMLDocumentName = 'C:\Reports\Intune-%Date%.html'; HTMLDocumentFileType = 'Object' }
|
||||
md = @{ MDDocumentName = 'C:\Reports\Intune.md'; MDIncludeCSS = $false; MDDocumentSkipDate = $true }
|
||||
}
|
||||
}
|
||||
Start-IMGraphBulkDocumentation -OutputFormat 'html,md' -PolicyGroup Compliance -Options $o
|
||||
```
|
||||
|
||||
### `html`
|
||||
|
||||
| Key | Default | Effect |
|
||||
|---|---|---|
|
||||
| `HTMLDocumentName` | `%MyDocuments%\%Organization%-%Date%.html` | Output file. Placeholders below. |
|
||||
| `HTMLDocumentFileType` | `Full` | `Full` - one file; `Object` - one file per policy, named after it, in the same folder. |
|
||||
| `HTMLTitleProperty` | `Intune documentation` | Page title. |
|
||||
| `HTMLCSSFile` | the shipped `DefaultHTMLStyle.css` | Your own stylesheet, inlined. |
|
||||
| `HTMLOpenFile` | `$true` | Open the result when done - set `$false` in a pipeline. |
|
||||
|
||||
### `md`
|
||||
|
||||
| Key | Default | Effect |
|
||||
|---|---|---|
|
||||
| `MDDocumentName` | `%MyDocuments%\%Organization%-%Date%.md` | Output file. |
|
||||
| `MDDocumentFileType` | `Full` | `Full` or `Object`, as for html. |
|
||||
| `MDTitleProperty` | `Intune documentation` | Document title. |
|
||||
| `MDCSSFile` | the shipped `DefaultMDStyle.css` | Stylesheet for `MDIncludeCSS`. |
|
||||
| `MDIncludeCSS` | `$true` | Inline the CSS (renderers that honour `<style>` in Markdown). `$false` for plain Markdown. |
|
||||
| `MDDocumentSkipDate` | `$false` | Leave the date out - keeps a committed file from changing on every run. |
|
||||
| `MDOpenFile` | `$true` | Open when done. |
|
||||
|
||||
### `json`
|
||||
|
||||
| Key | Default | Effect |
|
||||
|---|---|---|
|
||||
| `JSONDocumentName` | `%MyDocuments%\%Organization%-%Date%.json` | Output file. |
|
||||
| `JSONOutputFileType` | `Full` | `Full` or `Object`. |
|
||||
| `JSONOpenFile` | `$true` | Open when done. |
|
||||
|
||||
### `csv`
|
||||
|
||||
| Key | Default | Effect |
|
||||
|---|---|---|
|
||||
| `CSVDocumentationPath` | *(none)* | Root folder for the files. Always set it - with no value the files are written relative to the current directory. One file per policy type, in a subfolder named after the type when `CSVAddObjectType` is on. |
|
||||
| `CSVExportProperties` | `simple` | `simple` - the standard columns; `custom` - the list below. |
|
||||
| `CSVCustomDisplayProperties` | `Name,Value,Category` | Columns for `custom`. |
|
||||
| `CSVDelimiter` | *(culture list separator)* | Override, e.g. `;`. |
|
||||
| `CSVAddObjectType` | `$true` | Object type column. |
|
||||
| `CSVAddCompanyName` | `$false` | Tenant name column. |
|
||||
|
||||
### `atlassian`
|
||||
|
||||
| Key | Default | Effect |
|
||||
|---|---|---|
|
||||
| `AtlassianDocumentName` | `%MyDocuments%\%Organization%-%Date%.html` | Output file (Confluence storage format, despite the extension). |
|
||||
| `AtlassianDocumentFileType` | `Full` | `Full` or `Object`. |
|
||||
| `AtlassianTitleProperty` | `Intune documentation` | Page title. |
|
||||
| `AtlassianOpenFile` | `$true` | Open when done. |
|
||||
|
||||
### `word`
|
||||
|
||||
Windows only.
|
||||
|
||||
| Key | Default | Effect |
|
||||
|---|---|---|
|
||||
| `WordDocumentName` | `%MyDocuments%\%Organization%-%Date%.docx` | Output file. |
|
||||
| `WordDocumentTemplate` | *(none)* | A `.dotx` to build on. |
|
||||
| `WordDocumentFormat` | `wdFormatDocumentDefault` | Any `WdSaveFormat` name, e.g. `wdFormatPDF`. |
|
||||
| `WordDocumentationLevel` | `full` | How much to include. |
|
||||
| `WordExportProperties` | `simple` | `simple` or `custom`; `WordCustomDisplayProperties` lists the columns for `custom`. |
|
||||
| `WordAddCategories` / `WordAddSubCategories` | `$true` / `$true` | Category and sub-category headings. |
|
||||
| `WordTitleProperty` / `WordSubjectProperty` | `Intune documentation` | Document properties. |
|
||||
| `WordCoverPage` | `Ion (Dark)` | A cover page from the template's gallery. |
|
||||
| `WordContentControls` | *(none)* | Content controls to fill. |
|
||||
| `WordHeader1Style`, `WordHeader2Style`, `WordHeader3Style` | template defaults | Heading style names. |
|
||||
| `WordTableStyle` | `Grid table 4 - Accent 3` | Style for settings tables. |
|
||||
| `WordTableHeaderStyle`, `WordTableTextStyle` | template defaults | Styles for table header row and cell text. |
|
||||
| `WordCategoryHeaderStyle`, `WordSubCategoryHeaderStyle` | template defaults | Styles for the category and sub-category headings. |
|
||||
| `WordScriptTableStyle`, `WordScriptStyle` | template defaults | Styles for the table around an included script and the script text. |
|
||||
| `WordTableCaptionPosition` | `below` | `above` or `below`. |
|
||||
| `WordDocumentationLimitMaxLength` / `WordDocumentationLimitTruncateLength` | *(none)* | Truncate very long values; `WordDocumentationLimitAttach` (`$false`) attaches the full value instead. |
|
||||
| `WordAttachJsonFile` | `$false` | Embed the policy json. |
|
||||
| `WordOpenDocument` | `$true` | Open when done. |
|
||||
|
||||
## Where files land and how they are named
|
||||
|
||||
Document names accept placeholders, expanded at run time:
|
||||
|
||||
| Placeholder | Value |
|
||||
|---|---|
|
||||
| `%MyDocuments%` | The user's Documents folder. |
|
||||
| `%Organization%` | The connected tenant's display name. |
|
||||
| `%Date%` | `yyyy-MM-dd`. |
|
||||
| `%DateTime%` | `yyyyMMdd-HHmm`. |
|
||||
|
||||
Placeholders are ordinary environment-variable expansion, so any `%VARIABLE%` in the
|
||||
process environment works as well - `%BUILD_ARTIFACTSTAGINGDIRECTORY%` on an Azure DevOps
|
||||
agent, for example.
|
||||
|
||||
With `*DocumentFileType = 'Object'` the name's folder is used and one file is written per
|
||||
policy, named after the policy (invalid filename characters removed).
|
||||
|
||||
In a pipeline set every `*OpenFile` / `WordOpenDocument` to `$false`, and give absolute
|
||||
paths - `%MyDocuments%` on a build agent is rarely where you want the artifact.
|
||||
|
||||
## Languages
|
||||
|
||||
`-Language` picks the strings file: 23 languages ship under `Config/LanguageStrings/`
|
||||
(`Strings-<code>.json`): `cs de en es fr hu id it ja ko nl pl pt ru sv tr zh zh-chs zh-cht zh-hans zh-hant`. Setting names,
|
||||
categories and values are localized; UI text is always English. The files are generated
|
||||
and must not be edited by hand.
|
||||
|
||||
```powershell
|
||||
Start-IMGraphBulkDocumentation -OutputFormat html -PolicyGroup Compliance -Language sv
|
||||
```
|
||||
|
||||
## Which types document, and how
|
||||
|
||||
A policy is documented by the first of these that claims it:
|
||||
|
||||
1. A **handler** for its exact `@odata.type` (Conditional Access, Named Locations,
|
||||
Scope Tags, Role Definitions, Policy Sets, Kiosk, custom OMA-URI, ...).
|
||||
2. An **input provider**: Settings Catalog policies walk their setting definitions;
|
||||
administrative templates their definition values; endpoint security intents their
|
||||
templates; Linux compliance its settings; and the profile providers use the
|
||||
per-type manifest files under `Config/ObjectInfo/`.
|
||||
3. The **generic fallback** (when `FallbackDocumentation` is on): basic information plus
|
||||
one row per property.
|
||||
|
||||
The README's policy table says which of these each type gets (**yes** = 1 or 2,
|
||||
**generic** = 3). Read-only types under *Intune Info*, ADMX Files and Multi Admin
|
||||
Approval policies are not offered for documentation at all.
|
||||
|
||||
## Pipeline examples
|
||||
|
||||
Nightly HTML and Markdown from a service principal, no interactive state on the agent:
|
||||
|
||||
```powershell
|
||||
Import-Module .\IntuneManagement.psd1
|
||||
Use-IMSettingsStore -Memory
|
||||
Connect-IMIntuneManagement -TenantId $env:TENANT_ID -AppId $env:APP_ID -Secret $env:APP_SECRET
|
||||
|
||||
$o = @{
|
||||
SkipNotConfigured = $true
|
||||
Outputs = @{
|
||||
html = @{ HTMLDocumentName = "$env:BUILD_ARTIFACTSTAGINGDIRECTORY\Intune-%Date%.html"; HTMLOpenFile = $false }
|
||||
md = @{ MDDocumentName = "$env:BUILD_ARTIFACTSTAGINGDIRECTORY\Intune.md"; MDDocumentSkipDate = $true; MDIncludeCSS = $false; MDOpenFile = $false }
|
||||
}
|
||||
}
|
||||
Start-IMGraphBulkDocumentation -OutputFormat 'html,md' -PolicyGroup DeviceConfiguration, Compliance, EndpointSecurity -Options $o
|
||||
```
|
||||
|
||||
Document an export artifact produced by an earlier stage, one Markdown file per policy,
|
||||
without the source tenant:
|
||||
|
||||
```powershell
|
||||
Connect-IMIntuneManagement -Provider OAuth -ManagedIdentity # any tenant the identity can read
|
||||
$o = @{ Outputs = @{ md = @{ MDDocumentName = 'D:\docs\intune\index.md'; MDDocumentFileType = 'Object'; MDOpenFile = $false } } }
|
||||
Start-IMGraphBulkDocumentation -OutputFormat md -SourceFolder D:\artifacts\IntuneExport -Options $o
|
||||
```
|
||||
|
||||
Your own report from the data:
|
||||
|
||||
```powershell
|
||||
Get-IMGraphPolicies -PolicyType CompliancePolicies |
|
||||
ForEach-Object {
|
||||
$d = Get-IMGraphDocumentation -PolicyObject $_ -Options @{ ExcludeAssignments = $true }
|
||||
[PSCustomObject]@{ Policy = $_.Name; Settings = $d.FilteredSettings.Count }
|
||||
} | Sort-Object Settings -Descending
|
||||
```
|
||||
|
||||
Confluence, one page per policy, pushed with the REST API:
|
||||
|
||||
```powershell
|
||||
$o = @{ Outputs = @{ atlassian = @{ AtlassianDocumentName = 'D:\out\intune.html'; AtlassianDocumentFileType = 'Object'; AtlassianOpenFile = $false } } }
|
||||
Start-IMGraphBulkDocumentation -OutputFormat atlassian -PolicyGroup Compliance -Options $o
|
||||
Get-ChildItem D:\out\*.html | ForEach-Object {
|
||||
$body = @{ type = 'page'; title = $_.BaseName; space = @{ key = 'INTUNE' }
|
||||
body = @{ storage = @{ value = (Get-Content $_ -Raw); representation = 'storage' } } } | ConvertTo-Json -Depth 6
|
||||
Invoke-RestMethod -Uri "$confluence/rest/api/content" -Method Post -Headers $auth -ContentType 'application/json' -Body $body
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,119 @@
|
||||
# Atlassian (Confluence) documentation output
|
||||
|
||||
[`Internal/Documentation/OutputProviders/DocumentationOutputAtlassian.ps1`](../Internal/Documentation/OutputProviders/DocumentationOutputAtlassian.ps1)
|
||||
|
||||
Emits Confluence **storage format** (XHTML plus `<ac:*>` macros) for a documentation
|
||||
run: paste it into a page's *Rich Text -> Source* view, or POST it to the Confluence
|
||||
REST API as `representation=storage`. Structurally it mirrors the HTML provider
|
||||
(BasicInfo / FilteredSettings / ComplianceActions / ApplicabilityRules / Assignments /
|
||||
CustomTables plus a per-run table of contents); only the markup differs.
|
||||
|
||||
Select it with `-OutputFormat atlassian`.
|
||||
|
||||
| Option (`$Options.Outputs.atlassian`) | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `AtlassianDocumentName` | `%MyDocuments%\%Organization%-%Date%.html` | Output path. Supports `Expand-FileName` tokens. |
|
||||
| `AtlassianDocumentFileType` | `Full` | `Full` = one file; `Object` = one file per policy plus a TOC index file. |
|
||||
| `AtlassianTitleProperty` | `Intune documentation` | `<h1>` of the index page. |
|
||||
| `AtlassianOpenFile` | `$true` | Launch the file with the OS default handler. Set `$false` for CI. |
|
||||
|
||||
## Heading anchors (a consumer contract)
|
||||
|
||||
Every heading is emitted with an inline anchor macro:
|
||||
|
||||
```xml
|
||||
<h4 id='section-42'><ac:structured-macro ac:name='anchor' ac:schema-version='1'><ac:parameter ac:name=''>section-42</ac:parameter></ac:structured-macro>Contoso Reader</h4>
|
||||
```
|
||||
|
||||
The anchor name has appeared as literal `<!--#section-N-->` text downstream of the
|
||||
generated file. The source of that rewrite is still unproven: the publishing runbook
|
||||
parses the body with mshtml before POSTing, and Confluence also transforms storage
|
||||
format. The inline placement remains the known baseline until those paths are measured
|
||||
separately.
|
||||
|
||||
Three properties are guaranteed, and downstream tooling depends on them:
|
||||
|
||||
1. **The `anchor` macro carries the link target.** Confluence discards author-specified
|
||||
`id=` attributes when it converts storage format to ADF, so `#section-42` can only
|
||||
resolve to an anchor macro or to Confluence's own heading-text-derived anchor.
|
||||
2. **`id=` always equals the macro name, byte for byte.** Confluence strips it, but
|
||||
automation that parses the *generated file* before publishing reads the anchor from
|
||||
it - for example to walk an assignment table cell back to its policy heading and
|
||||
build a deep link into the published page.
|
||||
3. **The table of contents links these names, never the heading text.** Anchor names
|
||||
contain no character that came from tenant data.
|
||||
|
||||
**Naming scheme (breaking-change surface):** `section-N` for every heading, numbered
|
||||
in emission order over the whole run. `section-N` is unique across a run even in
|
||||
`Object` mode, because the counter is reset only in
|
||||
`Invoke-AtlassianPreProcessItems`. Numbering is *not contiguous within the TOC*: a
|
||||
heading consumes a number whenever it is written to the document, including the
|
||||
level-6 table captions that the TOC's level cap (4) filters out and the `-SkipTOC`
|
||||
script captions it never lists. Do not assume contiguity - and do not reconstruct the
|
||||
numbers by counting TOC entries.
|
||||
|
||||
`table-N` is reserved for the `-ToT` caption form of `Add-AtlassianHeader`, which no
|
||||
call site in this provider uses: table captions here are plain level-6 headings, as
|
||||
in the HTML provider. Only the Markdown provider passes `-ToT` (and so is the only
|
||||
output with visible `Table N.` numbering). Enabling it for Atlassian would change
|
||||
rendered captions, so it belongs with the HTML provider as one formatting decision
|
||||
rather than a port detail.
|
||||
|
||||
The scheme is positional, so inserting one policy shifts every later anchor: links a
|
||||
user saved from an earlier run of a scheduled export therefore move. A content-derived
|
||||
scheme (`policy-<objectId>` from the Graph GUID) would be stable and is the natural
|
||||
follow-up; it needs `Add-AtlassianHeader` to accept an explicit anchor from
|
||||
`Invoke-AtlassianProcessItem`, with the positional counter kept as the fallback for
|
||||
headers that have no natural identifier.
|
||||
|
||||
**Deployment ordering:** update the module wherever the export runs *before* a consumer
|
||||
switches to reading `id=` / linking `#section-N`, or its links will point at anchors
|
||||
that do not exist yet.
|
||||
|
||||
## Markup constraints
|
||||
|
||||
- **Single-quote every attribute.** Consumers JSON-escape the document body before a
|
||||
Confluence client serialises it again; a double-quoted attribute arrives as
|
||||
`ac:name=\"anchor\"` and breaks the macro. Every macro in the provider follows this,
|
||||
so the body survives a JSON round-trip byte-identically.
|
||||
- **Storage format is strict XML** and declares only the five XML built-in entities.
|
||||
Use numeric references (` `, never ` `), close every macro, and escape
|
||||
text that comes from the tenant - `Get-AtlassianXmlText` for headings, TOC labels,
|
||||
the title and the document-info lines; `Set-AtlassianText` for table cell values
|
||||
(it also wraps XML-looking values in a `code` macro and long text in an `expand`).
|
||||
A single bare `&` in a policy name invalidates the whole page body, not one heading,
|
||||
and Confluence rejects the upload.
|
||||
- **In `Object` mode the TOC's `href` carries a file name derived from a policy
|
||||
name.** `Get-AtlassianObjectFileName` strips only path-invalid characters, so `&`,
|
||||
`'` and `#` survive into it. `Get-AtlassianHref` percent-encodes the file-name
|
||||
component (never the `#` that introduces the fragment) and then XML-escapes the
|
||||
result; use it rather than interpolating a file name into an attribute.
|
||||
|
||||
## Change history
|
||||
|
||||
Unreleased (part of 4.0, no shipped version has the earlier behaviour):
|
||||
|
||||
- Table of contents entries link the anchor macro each heading emits. They used to
|
||||
target a fragment re-derived from the heading text, so duplicate policy names all
|
||||
jumped to the first occurrence and characters other than a plain space (`.`, `(`,
|
||||
`)`, `:`, `&`, `+`, `,`, U+00A0) leaked into the href unencoded.
|
||||
- Headings carry an inline `anchor` macro; previously they carried only an `id=` that
|
||||
Confluence discards, which nothing could link to. The *shape* of an anchor name is
|
||||
unchanged (`section-N`), so a consumer reading `id=` needs no update. A provisional
|
||||
preceding-paragraph placement was reverted before release because its downstream
|
||||
behavior and blank-line cost had not been measured.
|
||||
- A heading kept out of the TOC now consumes an anchor number. It used to reuse the
|
||||
next listed heading's number, so a document containing script captions (Detection
|
||||
script, Requirement scripts) emitted two `section-N` anchors with one name and the
|
||||
TOC entry landed on the caption. This shifts the numbers in such documents: another
|
||||
reason to read `id=` rather than compute `section-N` from a position.
|
||||
- Object-mode `href` file names are percent-encoded. A policy name containing `&`
|
||||
or `'` used to produce a malformed body, and one containing `#` a link to the wrong
|
||||
fragment.
|
||||
- In the `-ToT` caption path (present but unused, see above) the `Table N. ` prefix
|
||||
moved from the id to the visible text, where the Markdown provider puts it.
|
||||
- Heading text, TOC labels, the title and the document-info lines are XML-escaped.
|
||||
- Confluence keeps generating its own text-derived anchors, so externally saved
|
||||
`#Policy-Name` links still resolve.
|
||||
|
||||
The three guarantees above are covered by the project's own test suite.
|
||||
@@ -0,0 +1,165 @@
|
||||
# Effective Permissions
|
||||
|
||||
What the signed-in identity can actually do per policy type, and how the left-nav
|
||||
access marking, the Profile popup's **Permissions** dialog and
|
||||
`Get-IMGraphEffectivePermissions` derive it. This page is the "how it works now".
|
||||
|
||||
## The two halves of a delegated login
|
||||
|
||||
| Half | Where it lives | What it says |
|
||||
| --- | --- | --- |
|
||||
| App consent | token `scp` (delegated) / `roles` (app-only) | which Graph scopes the *application* was granted |
|
||||
| User authorization | Intune RBAC role assignments (+ scope tags) and Entra directory roles | what the *user* may do |
|
||||
|
||||
The token carries only the first half (plus `wids`, the directory role template
|
||||
ids). A user holding just the built-in *Read Only Operator* Intune role signs in with
|
||||
a token that says `DeviceManagementConfiguration.ReadWrite.All`, and every write is
|
||||
refused with 403. Effective access is the intersection, so the marking has two layers:
|
||||
|
||||
```
|
||||
Update-IntuneAccessLevels Internal/AccessLevel.ps1
|
||||
Layer 1 scp/roles vs _Permissions -> Full / Limited / None
|
||||
Layer 2 Get-IntuneRbacContext -> Full / Limited / None / Unknown Internal/EffectivePermissions.ps1
|
||||
stamp AccessType = worst(L1, L2), AccessInfo = both reasons
|
||||
```
|
||||
|
||||
Layer 2 can only make a type worse. Unknown never colours: Layer 1 stands.
|
||||
|
||||
## Layer 2 in detail
|
||||
|
||||
1. **Applies only to delegated tokens.** `idtyp = app` (or no `scp`) means application
|
||||
permissions, which bypass Intune RBAC - the `roles` claim is the whole answer.
|
||||
2. **Directory-role short-circuit.** Intune Administrator
|
||||
(`3a2c62db-5318-420d-8d74-23affee5d9d5`) or Global Administrator
|
||||
(`62e90394-69f5-4237-9190-012177145e10`) in `wids` grants complete Intune RBAC, so
|
||||
every Intune category is Full and Graph is not asked. Global Reader and the partial
|
||||
roles are *not* shortcuts - the user may also hold an Intune role.
|
||||
3. **Otherwise two Graph calls:**
|
||||
- `GET /beta/deviceManagement/getEffectivePermissions(scope='*')` - the same function
|
||||
the Intune portal uses to enable its buttons. It answers with the **allowed**
|
||||
actions only: `notAllowedResourceActions` comes back empty on every response, so
|
||||
on its own it cannot tell "the role denies this action" from "no such action
|
||||
exists for this category".
|
||||
- `GET /beta/deviceManagement/resourceOperations` - the **catalogue**: every resource
|
||||
action Intune defines, one entry per action, its `id` being exactly the
|
||||
`Microsoft.Intune_<Category>_<Action>` name the first call uses. That is the
|
||||
existence answer the first call cannot give (260 actions over 53 resources on the
|
||||
tenant this was built against). It describes the service, not the user, so it is
|
||||
cached per tenant and survives a token refresh. When it cannot be read, existence
|
||||
is *unknown* and the verdicts degrade as shown below.
|
||||
4. **Per type:** the `_API` path is mapped to an Intune resource category
|
||||
(`deviceManagement/deviceConfigurations` -> `DeviceConfigurations`, ...; table in
|
||||
`Internal/EffectivePermissions.ps1`, overridable per type with `_ResourceCategory`).
|
||||
Only actions that exist for the category are required, so a category with no
|
||||
`_Assign` (Roles) or one that spells it `Modify` (ManagedGooglePlay) is not marked
|
||||
down for the actions it never had.
|
||||
|
||||
| For the category | Level |
|
||||
| --- | --- |
|
||||
| `_Read` is not in the catalogue | Unknown - no such category exists; say nothing |
|
||||
| `_Read` exists, not allowed | None |
|
||||
| `_Read` allowed, type declares only Read scopes | Full (read-only by design) |
|
||||
| `_Read` allowed, an existing write action (`Create/Update/Delete/Assign/Modify`) not allowed | Limited, tooltip lists the missing actions |
|
||||
| every existing action is allowed | Full |
|
||||
|
||||
The Limited tooltip distinguishes the two shapes of it: "read-only for
|
||||
`<Category>`" when no write action at all is allowed, and "partial write access to
|
||||
`<Category>`" when some are (a role that can create and update but not delete or
|
||||
assign is still Limited, but it is not read-only). The Permissions popup's Role,
|
||||
Effective and Result columns show "Partial write" for that second shape rather
|
||||
than "Read" / "Read-only" - Effective and Result only when the token can write
|
||||
too, since a read-only token over a partial-write role really is read-only.
|
||||
|
||||
With no catalogue, the same type yields Unknown when `_Read` is not allowed (it may
|
||||
not exist), Limited when no write action at all is allowed, and Full otherwise - a
|
||||
coarser answer that never invents an action name.
|
||||
|
||||
APIs that Intune RBAC does not govern (`identity/*`, `identityGovernance/*`,
|
||||
`organization/*`) and the few listed in `$script:RbacUnmappedApis` are Unknown. A
|
||||
test fails if a new `deviceManagement/` or `deviceAppManagement/` type is in
|
||||
neither table.
|
||||
|
||||
## Caching and refresh
|
||||
|
||||
The user's answer is cached per tenant under the **token fingerprint** (`tid|oid|iat`)
|
||||
with no TTL; the action catalogue is cached per tenant only, so a refresh re-asks
|
||||
`getEffectivePermissions` and reuses the catalogue. Both are dropped on disconnect. Any newly minted token - routine renewal, a new sign-in, or **Refresh in the
|
||||
Profile popup** - has a new `iat`, misses the cache and re-asks. That single user
|
||||
action therefore covers both kinds of change:
|
||||
|
||||
| Change | Visible in the token? | Caught by |
|
||||
| --- | --- | --- |
|
||||
| Directory role via PIM (Intune Administrator, Global Admin) | only in a newly issued token; the routine silent acquire returns the cached one until near expiry | Refresh mints a token with the current `wids` |
|
||||
| Intune RBAC assignment (a role added, PIM for Groups) | never - the token is identical | Refresh still produces a new `iat`, so Layer 2 re-asks Graph |
|
||||
|
||||
A failed lookup is remembered for the same fingerprint (no retry on every menu
|
||||
rebuild) and retried after a refresh. Refresh is provider-agnostic: MSAL
|
||||
(`WithForceRefresh`), delegated OAuth (`refresh_token` grant) and MgGraph
|
||||
(`Connect-MgGraph` re-run) all mint a new token; BYO bearer tokens cannot refresh and
|
||||
the button is already disabled for them.
|
||||
|
||||
**Scope tags are not modelled.** `getEffectivePermissions` is the global answer; a
|
||||
user limited to some tags can still be refused on individual objects.
|
||||
|
||||
## Surfaces
|
||||
|
||||
- **Left nav** - orange (Limited) / red (None) with the reason in the tooltip; the
|
||||
existing `HideNoAccess` setting hides red rows. A summary line is logged at sign-in.
|
||||
- **Profile popup -> Permissions** - one row per policy type: token level, Intune-role
|
||||
level, effective level, reason, plus a header saying where the Intune half came from
|
||||
and when the token was issued. WPF: `UI/WPF/Extensions/EffectivePermissionsUIWPF.ps1`;
|
||||
Avalonia: `UI/Avalonia/Extensions/EffectivePermissionsUIAvalonia.ps1` with
|
||||
`UI/Avalonia/Classes/EffectivePermissionRowItem.ps1`.
|
||||
- **`Get-IMGraphEffectivePermissions`** - the same rows for scripts
|
||||
(`-PolicyType`, `-TokenId`), or `-Raw` for the context (source, the allowed action
|
||||
set, the catalogue of actions that exist, raw response). Runs even when the setting
|
||||
below is off.
|
||||
|
||||
## Setting
|
||||
|
||||
`UseRbacAccessMarking` (General, default on). Off restores the token-only marking
|
||||
exactly. Registered in `Internal/EffectivePermissions.ps1` because engine code reads
|
||||
it.
|
||||
|
||||
## Verifying against a tenant
|
||||
|
||||
Both endpoints, the response shapes and every **category name** in the table have been
|
||||
run against a live tenant, and `Tests/EffectivePermissions.Tests.ps1` asserts the names
|
||||
against the catalogue fixture, so a typo fails the suite. What is still unverified is
|
||||
the other half of each entry - that a given `_API` really is governed by the category it
|
||||
is mapped to. Those are marked `UNVERIFIED`, and **an unverified mapping is not a safe
|
||||
mapping**: only a category name that does not exist at all degrades to Unknown. A mapping
|
||||
to a name that exists but governs a *different* resource produces a confident verdict
|
||||
derived from the wrong role permissions - a type marked Full because the user's role
|
||||
covers the category it was mistakenly mapped to, or None because it does not. Both are
|
||||
wrong, and neither shows as Unknown. The typo test cannot catch this: the wrong name is a
|
||||
real one. Only a role that grants exactly one category can, so to check on a lab tenant:
|
||||
|
||||
```powershell
|
||||
Import-Module .\IntuneManagement.psd1 -Force
|
||||
Connect-IMIntuneManagement ... # delegated, as a NON-admin test user
|
||||
Get-IMGraphEffectivePermissions -Raw | Select-Object Source, TenantId, AsOf
|
||||
(Get-IMGraphEffectivePermissions -Raw).Allowed | Sort-Object # what the user's role grants
|
||||
(Get-IMGraphEffectivePermissions -Raw).Catalog | Sort-Object # every action Intune defines
|
||||
Get-IMGraphEffectivePermissions | Where-Object RbacLevel | Format-Table Id, ResourceCategory, TokenLevel, RbacLevel, EffectiveLevel
|
||||
```
|
||||
|
||||
Assign the test user a custom role granting exactly one category, refresh the token, and
|
||||
check that the types mapped to it are the ones that move. Move confirmed entries out of
|
||||
`UNVERIFIED`, and add `_ResourceCategory` overrides where a type maps elsewhere.
|
||||
|
||||
## Tests
|
||||
|
||||
`Tests/EffectivePermissions.Tests.ps1` (offline, fixtures under
|
||||
`Tests/Fixtures/EffectivePermissions/`): category resolution, completeness and every
|
||||
category name against the catalogue, response parsing, per-type verdicts for a full
|
||||
admin / read-only operator / custom role and for a missing catalogue, the never-upgrade
|
||||
property, app-only skip, directory-role short-circuit, fingerprint caching, catalogue
|
||||
caching across a refresh, negative caching, the setting gate, disconnect clearing,
|
||||
`Update-IntuneAccessLevels` integration and the cmdlet's rows.
|
||||
|
||||
The fixtures are recorded responses, not hand-written: `ReadOnlyOperator.json` is the
|
||||
built-in role's 55 actions, `ResourceOperations.json` the whole catalogue, and every
|
||||
`notAllowedResourceActions` is empty because that is what Graph returns. A verdict test
|
||||
that fails is a bug in the code or the mapping - do not "fix" it by inventing
|
||||
not-allowed data the API never sends.
|
||||
@@ -0,0 +1,214 @@
|
||||
# Automation Examples
|
||||
|
||||
One page of copy-paste recipes for the exported cmdlets. Everything the UI
|
||||
does runs through these same commands, so anything shown here can be
|
||||
scheduled, piped, or scripted.
|
||||
|
||||
```powershell
|
||||
Import-Module .\IntuneManagement.psd1
|
||||
```
|
||||
|
||||
All exported commands carry the `IM` prefix (`Connect-IMIntuneManagement`,
|
||||
`Start-IMGraphBulkExport`, ...). Most commands take `-TokenId`; when omitted
|
||||
they use the default token from the last `-DefaultToken` connect.
|
||||
|
||||
## Connect
|
||||
|
||||
```powershell
|
||||
# Interactive sign-in (browser / WAM). Cached sessions resume silently.
|
||||
Connect-IMIntuneManagement -DefaultToken
|
||||
|
||||
# Specific user / force a fresh prompt
|
||||
Connect-IMIntuneManagement -Interactive -User admin@contoso.com -ForceInteractive
|
||||
|
||||
# Unattended: client secret (app registration)
|
||||
Connect-IMIntuneManagement -TenantId $tid -AppId $appId -Secret $secret -DefaultToken
|
||||
|
||||
# Unattended: certificate
|
||||
Connect-IMIntuneManagement -TenantId $tid -AppId $appId -CertificatePath .\auth.pfx
|
||||
|
||||
# Device code (headless box, sign in from another device)
|
||||
Connect-IMIntuneManagement -DeviceCode
|
||||
|
||||
# Sovereign clouds
|
||||
Connect-IMIntuneManagement -Cloud USGov -GCCType High -DefaultToken
|
||||
```
|
||||
|
||||
Several tenants can be connected at once; each connect returns a token whose
|
||||
`Id` you pass as `-TokenId` to target that tenant.
|
||||
|
||||
## Settings
|
||||
|
||||
```powershell
|
||||
# Read and write by KEY - no storage paths, no internal functions
|
||||
Get-IMSetting GraphPageSize
|
||||
Get-IMSetting ExportFolder -Detailed # value + where it came from
|
||||
Set-IMSetting GraphPageSize 999
|
||||
Remove-IMSetting ExportFolder # back to the registered default
|
||||
|
||||
# What is configurable at all
|
||||
Get-IMSettingDefinition -Section IntuneManager
|
||||
|
||||
# Per-tenant override (reads prefer it over the global value)
|
||||
Set-IMSetting ExportFolder '\\server\intune\contoso' -Scope Tenant -TenantID $tid
|
||||
```
|
||||
|
||||
Writes persist to the registry (Windows) or the settings file. On a shared
|
||||
worker, where neither should be touched, run against an in-memory store:
|
||||
|
||||
```powershell
|
||||
Use-IMSettingsStore -Memory # nothing from here on hits disk
|
||||
Import-IMSettingsStore .\config\run.json # a checked-in configuration
|
||||
Set-IMSetting UseBatchAPI $true
|
||||
Get-IMSettingsStore # assert the store before trusting it
|
||||
```
|
||||
|
||||
`IM_SETTINGS_STORE=Memory` does the same from the first line of the module
|
||||
load. See [Settings](Settings.md) for the store modes and precedence rules.
|
||||
|
||||
## Export
|
||||
|
||||
```powershell
|
||||
# Everything, to one folder, assignments included
|
||||
Start-IMGraphBulkExport -ExportFolder C:\IntuneExport -ExportAssignments $true
|
||||
|
||||
# Only some types / groups, name filter
|
||||
Start-IMGraphBulkExport -ExportFolder C:\IntuneExport -PolicyType SettingsCatalog,CompliancePolicies
|
||||
Start-IMGraphBulkExport -ExportFolder C:\IntuneExport -PolicyGroup DeviceConfiguration -Filter 'PROD-*'
|
||||
|
||||
# Reusable settings: build once, save, schedule the file
|
||||
$s = [IntuneManagerExportSettings]::new()
|
||||
$s.ExportFolder = 'C:\IntuneExport'
|
||||
$s.ExportAssignments = $true
|
||||
$s.AddCompanyName = $false
|
||||
Save-IMGraphBulkExportSettings -Path C:\Jobs\nightly-export.json -ExportSettings $s
|
||||
Start-IMGraphBulkExport -SettingsFile C:\Jobs\nightly-export.json
|
||||
|
||||
# Single policies via the pipeline
|
||||
Get-IMGraphPolicies -PolicyType SettingsCatalog | Where-Object Name -like 'Win11*' |
|
||||
Export-IMGraphPolicy -ExportSettings $s
|
||||
```
|
||||
|
||||
## Import
|
||||
|
||||
```powershell
|
||||
# Import a full export folder (assignments + scope tags translated)
|
||||
Start-IMGraphBulkImport -ImportFolder C:\IntuneExport -ImportAssignments $true -ImportScopeTags $true
|
||||
|
||||
# Only matching files / only some groups
|
||||
Start-IMGraphBulkImport -ImportFolder C:\IntuneExport -Filter 'PROD-*' -PolicyGroup DeviceConfiguration
|
||||
|
||||
# Import behavior when the object already exists (-ImportType):
|
||||
# alwaysImport (default) | skipIfExist | update | replace | replace_with_assignments
|
||||
Start-IMGraphBulkImport -ImportFolder C:\IntuneExport -ImportType update
|
||||
```
|
||||
|
||||
Cross-tenant: the export folder's `MigrationTable.json` translates group and
|
||||
dependency references automatically. Groups that do not exist in the target
|
||||
tenant are created during import (settings `CreateGroupOnImport` and
|
||||
`ConvertSyncedGroupOnImport`, both on by default). Policy sets re-point their
|
||||
member references by display name.
|
||||
|
||||
## Documentation
|
||||
|
||||
```powershell
|
||||
# One object -> result object (inspect or serialize yourself)
|
||||
$p = Get-IMGraphPolicies -PolicyType CompliancePolicies | Select-Object -First 1
|
||||
Get-IMGraphDocumentation -PolicyObject $p -Language en
|
||||
|
||||
# Bulk: whole policy groups to a single HTML file
|
||||
Start-IMGraphBulkDocumentation -OutputFormat html -PolicyGroup DeviceConfiguration -Options @{
|
||||
Outputs = @{ html = @{ HTMLDocumentName = 'C:\Docs\Intune.html'; HTMLOpenFile = $false } }
|
||||
}
|
||||
|
||||
# Several formats in one run, each with its own settings
|
||||
Start-IMGraphBulkDocumentation -OutputFormat 'html,word,csv' -PolicyType SettingsCatalog -Options @{
|
||||
Outputs = @{
|
||||
html = @{ HTMLDocumentName = 'C:\Docs\Intune.html'; HTMLOpenFile = $false }
|
||||
word = @{ WordDocumentName = 'C:\Docs\Intune.docx'; WordOpenDocument = 'false' }
|
||||
csv = @{ CSVDocumentationPath = 'C:\Docs\CSV' }
|
||||
}
|
||||
}
|
||||
|
||||
# Localized output
|
||||
Start-IMGraphBulkDocumentation -OutputFormat html -PolicyGroup CompliancePolicies -Language sv
|
||||
|
||||
# Document an EXPORT folder instead of the live tenant
|
||||
Start-IMGraphBulkDocumentation -OutputFormat md -SourceFolder C:\IntuneExport
|
||||
|
||||
# Pipeline: document exactly the policies you select
|
||||
Get-IMGraphPolicies -PolicyType ConditionalAccess | Start-IMGraphBulkDocumentation -OutputFormat json
|
||||
```
|
||||
|
||||
Formats: `html`, `word`, `csv`, `md`, `json`, `atlassian`. Frequently used
|
||||
per-format option keys (full list: `Get-IMDocumentationOutput`):
|
||||
|
||||
| Format | Keys |
|
||||
| --- | --- |
|
||||
| html | `HTMLDocumentName`, `HTMLDocumentFileType` (`Full`/`Object`), `HTMLCSSFile`, `HTMLOpenFile` |
|
||||
| word | `WordDocumentName`, `WordDocumentTemplate`, `WordCoverPage`, `WordOpenDocument` |
|
||||
| csv | `CSVDocumentationPath`, `CSVDelimiter`, `CSVAddObjectType` |
|
||||
| md | `MDDocumentName`, `MDDocumentFileType`, `MDIncludeCSS` |
|
||||
| json | `JSONDocumentName`, `JSONOutputFileType` |
|
||||
|
||||
`*DocumentFileType = 'Object'` writes one file per policy instead of one big
|
||||
document.
|
||||
|
||||
## Compare
|
||||
|
||||
```powershell
|
||||
# Live tenant vs an export folder (drift detection)
|
||||
$provider = [CompareIntuneWithExportProvider]::new()
|
||||
$provider.ExportPath = 'C:\IntuneExport'
|
||||
Compare-IMGraphPolicy -IntuneWithExport $provider -PolicyGroupIds DeviceConfiguration
|
||||
|
||||
# Two selected policies against each other
|
||||
$two = Get-IMGraphPolicies -PolicyType SettingsCatalog | Where-Object Name -in 'Baseline v1','Baseline v2'
|
||||
Compare-IMGraphPolicy -Policies $two
|
||||
```
|
||||
|
||||
## Bulk assignments
|
||||
|
||||
```powershell
|
||||
# Add a group assignment (with an assignment filter) across a policy group
|
||||
$s = [IntuneManagerAssignmentSettings]::new()
|
||||
$s.Action = 'Add' # Add | Replace | Remove
|
||||
$s.Assignments = @([PSCustomObject]@{
|
||||
TargetType = 'groupAssignmentTarget' # or exclusionGroupAssignmentTarget,
|
||||
GroupId = '<entra-group-id>' # allDevicesAssignmentTarget,
|
||||
FilterId = '<filter-id>' # allLicensedUsersAssignmentTarget
|
||||
FilterType = 'include'
|
||||
})
|
||||
Set-IMGraphBulkAssignments -AssignmentSettings $s -PolicyGroup DeviceConfiguration -Filter 'PROD-*'
|
||||
|
||||
# Remove the same assignment again
|
||||
Set-IMGraphBulkAssignments -AssignmentSettings $s -Action Remove -PolicyGroup DeviceConfiguration
|
||||
```
|
||||
|
||||
## Bulk scope tags, copy, delete
|
||||
|
||||
```powershell
|
||||
# Tag everything matching a name pattern
|
||||
Set-IMGraphBulkScopeTags -Action Add -ScopeTagIds $tagId -PolicyType SettingsCatalog -Filter 'PROD-*'
|
||||
|
||||
# Copy every policy whose name contains the pattern, replacing it in the copy
|
||||
# ("Test - Baseline" -> "Prod - Baseline"); re-run safe (existing names skipped)
|
||||
Start-IMGraphBulkCopy -CopyFromPattern 'Test - ' -CopyToPattern 'Prod - ' -PolicyGroup DeviceConfiguration
|
||||
|
||||
# Delete by filter - test-prefix your filter, this is destructive
|
||||
Start-IMGraphBulkDelete -Filter '[Test]*' -PolicyGroup DeviceConfiguration
|
||||
```
|
||||
|
||||
## Lower-level building blocks
|
||||
|
||||
```powershell
|
||||
# List objects (optionally with assignments)
|
||||
Get-IMGraphPolicies -PolicyType SettingsCatalog -IncludeAssignments
|
||||
|
||||
# Load exported files back into policy objects (for selective import)
|
||||
Get-ChildItem C:\IntuneExport\SettingsCatalog\*.json |
|
||||
Get-IMGraphPolicyFromFile | Import-IMGraphPolicy
|
||||
|
||||
# Raw Graph, with the module's auth, throttling, paging and batching
|
||||
Invoke-IMMSGraphAPI -Url 'deviceManagement/managedDevices?$top=5' -AllPages
|
||||
```
|
||||
@@ -0,0 +1,129 @@
|
||||
# Graph batching and parallelism
|
||||
|
||||
Two settings control how the module talks to Microsoft Graph, and since the
|
||||
2026-09-07 rework each one means exactly what its name says:
|
||||
|
||||
| setting key | title in Settings | meaning |
|
||||
| --- | --- | --- |
|
||||
| `UseBatchAPI` | Combine requests into batch calls | Combine logical requests into `POST /$batch`, twenty per call, on **every** path that can batch. Off means `$batch` is never used: each request is its own HTTP call. Default on. |
|
||||
| `UseParallelBatchAPI` | Send batch calls in parallel (experimental) | Send those `$batch` POSTs concurrently instead of one at a time. Concurrency and nothing else. PowerShell 7 only. Default off. |
|
||||
|
||||
Working definitions used on this page:
|
||||
|
||||
- **Batched** - several logical requests combined into one `POST /$batch`.
|
||||
- **Parallel** - more than one HTTP request in flight at once. In this module
|
||||
that is always a set of `$batch` POSTs; there is no parallel direct-call path.
|
||||
|
||||
Both keys are read in exactly one place each, the two predicates at the top of
|
||||
`Internal/MSGraph.ps1`:
|
||||
|
||||
```powershell
|
||||
Test-GraphBatchEnabled # (Get-SettingValue "UseBatchAPI") -eq $true
|
||||
Test-GraphParallelEnabled # (Get-SettingValue "UseParallelBatchAPI") -eq $true -and PS 7+
|
||||
```
|
||||
|
||||
`Tests/Static.Tests.ps1` fails the build if either key is consulted anywhere
|
||||
else. That rule is what keeps the two concerns from re-tangling: before it,
|
||||
`UseBatchAPI` was checked at four call sites and ignored by the dispatcher
|
||||
itself, and `UseParallelBatchAPI` silently selected an entire export pipeline.
|
||||
|
||||
## Where the decisions are made
|
||||
|
||||
`Invoke-GraphBatchRequest` (`Internal/MSGraph.ps1`) is the only place that
|
||||
decides between batched, direct, serial and parallel dispatch. Every caller
|
||||
hands it a queue of sub-requests and gets back the batch response shape
|
||||
(`id`, `status`, `headers`, `body`) whichever way the requests went out.
|
||||
|
||||
1. Paced sub-requests (see below) are split into their own queue first.
|
||||
2. **Batching off, or exactly one non-paced request**: every sub-request goes
|
||||
out as its own `Invoke-MSGraphAPI` call through
|
||||
`Invoke-GraphBatchRequestDirect`. The sub-request's `Accept` header becomes
|
||||
`-ODataMetadata`, other headers ride on `-AdditionalHeaders`, `-AllPages`
|
||||
is forwarded for GETs, and the status comes from the telemetry row the
|
||||
wrapper records for every call. A lone request takes this path even with
|
||||
batching on, because a `$batch` envelope around one GET is a whole extra
|
||||
round-trip for nothing.
|
||||
3. **Batching on**: the paced queue drains first, one sub-request per POST,
|
||||
each gated on the tenant clock. Then the normal queue goes twenty per POST.
|
||||
4. **Parallel on** and the normal queue has more than
|
||||
`$script:GraphParallelBatchMinQueue` (20) sub-requests: the chunks are
|
||||
dispatched concurrently through `Invoke-ParallelGraphBatchPosts`, up to
|
||||
`ParallelBatchThrottle` at a time. At or below the threshold the queue goes
|
||||
out serially and the log says so once:
|
||||
`Batch <type>: parallel requested but the queue is N (threshold 20) - dispatching serially`.
|
||||
|
||||
## Every Graph call path
|
||||
|
||||
| path | entry point | batches? | can be parallel? | controlled by |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Policy listing | `Get-GraphPolicies` | yes, list requests coalesced by URL; types without a batch object list directly | yes | both settings |
|
||||
| Policy bodies (hydrate) | `Invoke-PolicyHydrateBodyBatch` | yes, chunks of `ParallelBatchThrottle` x 20; a single policy is a direct GET | yes | both |
|
||||
| Sub-resources (branding images, app relationships, ToU files, ...) | `Invoke-PolicySubresourceFetch` | yes | yes | both |
|
||||
| Assignments | `Add-GraphPolicyAssignments` | yes | yes | both |
|
||||
| Bulk export group prefetch | `Sync-BulkExportMigrationGroups` | no `$batch`: one `directoryObjects/getByIds` POST per thousand ids | no | always runs; not a batching decision |
|
||||
| Nested group hierarchy | `Sync-BulkExportNestedGroupHierarchy` | yes | yes | both |
|
||||
| Migration objects queued as cache misses | `Resolve-GraphMigrationObjectsPending` | getByIds for groups/users/devices/service principals; `$batch` for assignment filters and anything else | for the `$batch` part | `UseBatchAPI` for the non-directory part |
|
||||
| Documentation prefetch | `Initialize-DocumentationRunPrefetch` | scope tags, filters and category lists in one `$batch`; assignment groups through one getByIds preload | yes | both for the `$batch`; the preload always runs |
|
||||
| Import | `Import-GraphPolicy` | bulk: POSTs queued then batched; direct: each object's own `ImportObject` | yes (bulk) | `UseBatchAPI` chooses bulk versus direct - they are different code, the direct path runs class overrides and carries `PreImportCommand` headers |
|
||||
| Delete | `Remove-GraphPolicy` | same shape as import | yes (bulk) | `UseBatchAPI` chooses |
|
||||
| Everything single-request | `Invoke-MSGraphAPI` | no | no | not a batching path |
|
||||
|
||||
### The two overrides
|
||||
|
||||
**Paced endpoints.** Conditional Access policies, named locations,
|
||||
authentication strengths, authentication context references, risk detections
|
||||
and risky users are limited by Graph to one request per second per tenant,
|
||||
counted across every application, with no `Retry-After` on throttle. Requests
|
||||
to them are always one sub-request per POST, never parallel, each gated on a
|
||||
per-tenant clock, whatever the two settings say. The rules live in
|
||||
`Internal/GraphRateLimits.ps1` and the `GraphPaceIdentityEndpoints` setting
|
||||
switches only the pacing off. Pacing legitimately wins over both settings.
|
||||
|
||||
**Single requests.** A queue of one non-paced request goes direct regardless
|
||||
of `UseBatchAPI`. See step 2 above.
|
||||
|
||||
## Bulk export specifically
|
||||
|
||||
`Start-GraphBulkExport` runs one pipeline whatever the settings say: list every
|
||||
type in one call, hydrate every body across types, prefetch every referenced
|
||||
group, walk the nested hierarchy when nested export is on, then write per
|
||||
type. Concurrency takes effect only inside the dispatcher. The old per-type
|
||||
interleaved loop that ran with parallel off is gone; it skipped the group
|
||||
prefetch, which on a 963-object tenant cost 22 of a measured 26m 35s (one
|
||||
direct GET per assignment group at ~1.6 s, against ~2 s for twenty of them in
|
||||
one `$batch`).
|
||||
|
||||
`Add-GraphMigrationObject` never calls Graph. A cache miss is queued and
|
||||
resolved in bulk when the migration tables are flushed, so a single-policy
|
||||
export and a bulk export both pay one getByIds POST for all their groups, not
|
||||
one GET each.
|
||||
|
||||
## Benchmark protocol
|
||||
|
||||
Export to a fresh scratch folder, never over a previous run, then compare
|
||||
against a reference export:
|
||||
|
||||
| configuration | expectation |
|
||||
| --- | --- |
|
||||
| batch on, parallel off (the default) | close to the parallel time, not 6x slower |
|
||||
| batch on, parallel on | fastest; no regression |
|
||||
| batch off, parallel off | slowest; zero `Invoke batch` log lines; identical output |
|
||||
|
||||
Output equivalence means the same file list, content differences limited to
|
||||
`lastModifiedDateTime` on objects genuinely edited between runs, and the same
|
||||
`(Id, Type)` set in `MigrationTable.json` (its ordering may differ).
|
||||
|
||||
## Reading the log
|
||||
|
||||
| log line | meaning |
|
||||
| --- | --- |
|
||||
| `Invoke batch N <type> (M requests)` | one `$batch` POST, serial path |
|
||||
| `<type>: dispatching N request(s) in parallel batches` | the parallel dispatcher took the queue |
|
||||
| `Batch <type>: parallel requested but the queue is N (threshold 20) - dispatching serially` | parallel is on but the queue was too small |
|
||||
| `Direct dispatch <type> (N requests, batching off)` | `UseBatchAPI` is off; each request is its own call |
|
||||
| `Bulk export: pre-fetching N AAD group(s) ...` / `... prefetch complete - R resolved, M missing/deleted` | the getByIds group prefetch |
|
||||
| `Migration objects: resolving N queued <kind> id(s) for tenant ...` | the deferred migration-object queue draining |
|
||||
| `<type>: N of M requests - Conditional Access allows one per second` | the paced queue |
|
||||
|
||||
Design record: `Docs/superpowers/specs/2026-09-07-graph-batching-parallelism-control-design.md`.
|
||||
Tests: `Tests/GraphBatching.Tests.ps1`, `Tests/GraphRateLimits.Tests.ps1`.
|
||||
@@ -0,0 +1,268 @@
|
||||
# Settings
|
||||
|
||||
Settings are the tool's configuration: everything the Settings dialog shows, plus a
|
||||
handful of hidden keys and per-feature state. This page is about how they are stored,
|
||||
how they are addressed, and how to drive them from automation without touching the
|
||||
machine they run on.
|
||||
|
||||
For cache and logging, see [Settings Cache Logging](SettingsCacheLogging.md).
|
||||
|
||||
|
||||
Every registered key, with type, default and description, is in
|
||||
[SettingsReference.md](SettingsReference.md) - generated from the code, so it is always current.
|
||||
|
||||
## Three layers
|
||||
|
||||
Addressing a setting used to mean knowing where it lives. It does not any more:
|
||||
|
||||
| Layer | Where | Addressed by | Functions |
|
||||
| --- | --- | --- | --- |
|
||||
| storage | `Internal/Core.ps1` | **path** (`SubPath` + key) | `Get-SettingStoreValue`, `Save-SettingStoreValue`, `Remove-SettingStoreValue` |
|
||||
| resolver | `Internal/Settings.ps1` | **key** | `Get-SettingValue`, `Set-SettingValue`, `Remove-SettingValue`, `Test-SettingValueConfigured`, `Resolve-SettingValue` |
|
||||
| public | `Public/*Setting*.ps1` | **key** | `Get-IMSetting`, `Set-IMSetting`, `Remove-IMSetting`, `Get-IMSettingDefinition`, plus the store cmdlets below |
|
||||
|
||||
The dependency direction is one-way: public calls the resolver, the resolver calls
|
||||
storage. Nothing goes the other way.
|
||||
|
||||
**Prefer the resolver.** It looks the key up in the registered definitions and derives
|
||||
the `SubPath` from the registration, so a write cannot land somewhere a read never
|
||||
looks. Every SubPath bug the project has had - `GraphPageSize` resolving differently in
|
||||
headless sessions, the bulk-export remember-last-used round trip being silently dead,
|
||||
`DefaultCloud` - was a hand-written path on one side only. `Tools/Audit-Settings.ps1`
|
||||
reports remaining path-addressed access to a registered key as a warning.
|
||||
|
||||
`Get-SettingValue` lives in `Internal/Core.ps1` rather than with the rest of the
|
||||
resolver because `Write-Log` reads `LogFile` through it during preload, long before
|
||||
`Internal/` is dot-sourced.
|
||||
|
||||
### Registration
|
||||
|
||||
A setting exists because some file called `Add-SettingsObject`:
|
||||
|
||||
```powershell
|
||||
Add-SettingsObject -Key "GraphPageSize" -Section "IntuneManager" -SubPath "IntuneManager" `
|
||||
-Title "Page size" -Type "List" -DefaultValue "0" -ItemsSource $sizes
|
||||
```
|
||||
|
||||
`-SubPath` is the storage path. With no `-SubPath` the `-Section` is used, except
|
||||
`General`, which stores at the root of the store. `-Type` is a **UI editor hint**
|
||||
(`Boolean`, `Int`, `File`, `List`, `Text`), not a storage type - everything on disk is
|
||||
a string.
|
||||
|
||||
`Get-IMSettingDefinition` lists the registrations; `Get-IMSettingDefinition -Section
|
||||
IntuneManager` narrows to one section.
|
||||
|
||||
## Three store modes
|
||||
|
||||
The mode is decided at the very top of `Internal/Core.ps1`, before anything can log:
|
||||
|
||||
| Mode | Backing | Default when |
|
||||
| --- | --- | --- |
|
||||
| `Registry` | `HKCU:\Software\IntuneManagement` | on Windows |
|
||||
| `Json` | `IM_SETTINGS_FILE`, else `LocalApplicationData/IntuneManagement/Settings.json` | off Windows, or `IM_SETTINGS_FILE` is set |
|
||||
| `Memory` | a tree with no file behind it - reads and writes work, nothing touches disk | never; opt in explicitly |
|
||||
|
||||
Environment variables, read once at load:
|
||||
|
||||
| Variable | Effect |
|
||||
| --- | --- |
|
||||
| `IM_SETTINGS_STORE` | `Memory`, `Json` or `Registry`. `Registry` off Windows falls back to `Json`. An unrecognized value falls back to the platform default and is warned about once logging works. |
|
||||
| `IM_SETTINGS_FILE` | the `Json` store's file. Created if missing. Implies `Json`. |
|
||||
|
||||
Memory mode is not a fourth code path: the storage primitives take their JSON branch
|
||||
whenever a settings **object** exists, and persist only when a settings **file** exists
|
||||
too. Memory mode is an object with no file.
|
||||
|
||||
`Get-IMSettingsStore` reports what the store actually is - which is not always what was
|
||||
requested, because a `Json` store whose file cannot be read falls back to the registry,
|
||||
and off Windows that leaves no store at all (`Mode = "None"`: reads return registered
|
||||
defaults, writes go nowhere).
|
||||
|
||||
```powershell
|
||||
Get-IMSettingsStore # Mode / Path / Persisted / ValueCount / RequestedMode
|
||||
Get-IMSettingsStore -IncludeValues # ... plus every SubPath/Key/Value row
|
||||
```
|
||||
|
||||
## Public API
|
||||
|
||||
```powershell
|
||||
Get-IMSetting GraphPageSize # the effective value
|
||||
Get-IMSetting GraphPageSize -Detailed # value + Source (Default/Global/Tenant) + Type + Default + ...
|
||||
Get-IMSetting ExportFolder -Scope Global # ignore any tenant override
|
||||
Get-IMSetting ExportFolder -Scope Tenant -TenantID <guid>
|
||||
|
||||
Set-IMSetting GraphPageSize 999
|
||||
Set-IMSetting ExportFolder '\\server\intune\contoso' -Scope Tenant
|
||||
Set-IMSetting UseBatchAPI $false -PassThru # returns the resolved value afterwards
|
||||
|
||||
Remove-IMSetting ExportFolder -Scope Tenant # revert to the global value
|
||||
Remove-IMSetting ExportFolder # revert to the registered default
|
||||
```
|
||||
|
||||
`Set-IMSetting` and `Remove-IMSetting` **persist by default** - to the registry or the
|
||||
settings file, whichever the active store is. There is no opt-in switch. They support
|
||||
`-WhatIf`, they log the store and path they wrote to, and `Get-IMSettingsStore` lets a
|
||||
script assert on the store before writing anything. If a script must not write to the
|
||||
machine it runs on, switch to a memory store first.
|
||||
|
||||
`Get-IMSetting` accepts a key from the pipeline, so `Get-IMSettingDefinition -Section
|
||||
IntuneManager | Get-IMSetting -Detailed` dumps a whole section with provenance.
|
||||
|
||||
### Scope and precedence
|
||||
|
||||
A setting can be written globally or for one tenant. Reads take the tenant value first:
|
||||
|
||||
```text
|
||||
tenant value -> global value -> registered DefaultValue
|
||||
```
|
||||
|
||||
`-Scope Tenant` with no `-TenantID` uses the connected tenant, and **fails loudly** if
|
||||
there is none - writing the global value instead would change every tenant when the
|
||||
caller asked for one.
|
||||
|
||||
`Get-IMSetting -Detailed` reports which level answered in `Source`. That is computed
|
||||
fresh from the store on every call; the `.Value` cached on a definition object by
|
||||
`Get-SettingValue` is a per-session artifact of whoever read it last.
|
||||
|
||||
A **scoped** read (`-Scope Global` / `-Scope Tenant`) reports the absence of a value at
|
||||
that level as `$null` with `Source = 'NotSet'`. It does not fall back to the registered
|
||||
default, because "this tenant does not override the setting" and "this tenant overrides
|
||||
it to the same value as the default" are different facts. Only the default `-Scope
|
||||
Effective` falls back, which is what the application itself resolves.
|
||||
|
||||
### Keys with no registration
|
||||
|
||||
The hidden keys (`ExportReplaceTokens`) and the per-feature state namespaces are not
|
||||
registered with `Add-SettingsObject`, so they have no path to take from a registration.
|
||||
`-SubPath` supplies it, and is accepted on **all three** cmdlets - a key that can be
|
||||
written this way can be read and removed the same way:
|
||||
|
||||
```powershell
|
||||
Set-IMSetting ExportReplaceTokens 'TenantId' -SubPath 'IntuneManager'
|
||||
Get-IMSetting ExportReplaceTokens -SubPath 'IntuneManager'
|
||||
Remove-IMSetting ExportReplaceTokens -SubPath 'IntuneManager'
|
||||
```
|
||||
|
||||
Such a key has no registered default and no declared type, so its `Source` is `Tenant`,
|
||||
`Global` or `NotSet` - never `Default` - and the value comes back as the string the
|
||||
store holds. Tenant precedence works as usual (`-Scope Tenant`, `-TenantID`), which is
|
||||
how a per-tenant `ExportReplaceTokens` is set.
|
||||
|
||||
For a **registered** key `-SubPath` is reported in the log and ignored: the registration
|
||||
decides the path, so a write cannot be aimed somewhere the application never reads.
|
||||
|
||||
## Automation: a run that reads and writes nothing on the worker
|
||||
|
||||
The problem: a runbook on a shared Hybrid Worker has no business reading, let alone
|
||||
writing, that worker's `HKCU` hive or settings file. A memory store solves it, and a
|
||||
checked-in settings file makes the configuration reviewable:
|
||||
|
||||
```powershell
|
||||
Import-Module .\IntuneManagement.psd1
|
||||
|
||||
# Nothing from here on touches the worker's registry or settings file.
|
||||
Use-IMSettingsStore -Memory
|
||||
Import-IMSettingsStore .\config\documentation-run.json
|
||||
|
||||
# Whatever the file did not cover.
|
||||
Set-IMSetting GraphPageSize 999
|
||||
Set-IMSetting UseBatchAPI $true
|
||||
|
||||
Connect-IMIntuneManagement -Provider OAuth -TenantId $tid -AppId $appId -Certificate $cert
|
||||
Start-IMGraphBulkDocumentation -OutputFormat html -PolicyGroup DeviceConfiguration
|
||||
```
|
||||
|
||||
`IM_SETTINGS_STORE=Memory` in the environment does the same thing from the first line
|
||||
of the module load, which matters if anything the module logs during load would
|
||||
otherwise resolve against the real store.
|
||||
|
||||
| Cmdlet | Use |
|
||||
| --- | --- |
|
||||
| `Use-IMSettingsStore -Memory [-Seed]` | switch to memory. `-Seed` copies the persisted values in first, so the session starts from the real configuration and then diverges without writing back. |
|
||||
| `Use-IMSettingsStore <path>` | switch to a `Json` store at that file, creating it if missing. |
|
||||
| `Use-IMSettingsStore -Registry` | switch back to `HKCU`. Windows only. |
|
||||
| `Export-IMSettingsStore <path>` | write the whole current store to a JSON file, whatever mode it is in - including a registry store. |
|
||||
| `Import-IMSettingsStore <path>` | load a settings file into the **active** store, value by value. |
|
||||
|
||||
Two things about `Import-IMSettingsStore` worth knowing:
|
||||
|
||||
- It **merges** into what is already there rather than replacing it. For a clean slate,
|
||||
`Use-IMSettingsStore -Memory` (without `-Seed`) first, then import into that.
|
||||
- It goes through the storage layer, so one implementation is correct for all three
|
||||
modes: values persist in `Json` mode, land in the registry in `Registry` mode, stay in
|
||||
memory in `Memory` mode. Which also means importing into a registry store **writes to
|
||||
the registry**.
|
||||
- Keys with no registration are imported anyway - the hidden keys and per-feature state
|
||||
namespaces are real - but they are listed in a warning, because a typo looks
|
||||
identical and would otherwise silently do nothing.
|
||||
|
||||
## Stored shape
|
||||
|
||||
Everything is a string. Booleans are PascalCase `"True"`/`"False"`, which is what
|
||||
`$true.ToString()` produces and what every existing store contains.
|
||||
`Set-SettingValue` coerces through `Format-SettingStoreValue`, so `$true`, `"true"` and
|
||||
`"TRUE"` all land in the one canonical shape, and a value written through the resolver
|
||||
is indistinguishable from one written by the Settings dialog.
|
||||
|
||||
Watch out for `[bool]"False"` - it is `$true` in PowerShell, as any non-empty string is.
|
||||
Stored Booleans are compared to `"true"`, never cast. Both the reader and the writer do
|
||||
this the same way on purpose.
|
||||
|
||||
An **empty string** counts as "not set" on read, so clearing a text box in the Settings
|
||||
dialog restores the registered default rather than persisting `""`. "Is it configured"
|
||||
is therefore a different question from "does it have a value", and
|
||||
`Test-SettingValueConfigured` (`Get-IMSetting` does not expose it) answers it by asking
|
||||
whether the value exists at that path at all.
|
||||
|
||||
## Legacy layout note
|
||||
|
||||
A `SubPath` with more than one level (`<tenantid>\IntuneManager`) used to be stored by
|
||||
the JSON/memory store as a **single flat property** literally named
|
||||
`"<tenantid>\IntuneManager"`, while the registry stored the same path as nested keys.
|
||||
The cause was `"a\b".Split(@('/','\'))` not splitting at all - PowerShell binds a string
|
||||
array to a different `String.Split` overload and hands back the whole path as one
|
||||
element. It is now `[char[]]`-cast and splits properly.
|
||||
|
||||
Existing settings files keep working: `Get-SettingsTreeNode` resolves a flat property
|
||||
first and writes back to it when it finds one, so no tenant-specific value is orphaned
|
||||
by the fix. New values nest.
|
||||
|
||||
## Testing
|
||||
|
||||
Settings leak from the developer machine into tests, and tests leak into the developer
|
||||
machine. `Tests/TestBootstrap.ps1` has the two helpers:
|
||||
|
||||
```powershell
|
||||
Describe 'x' {
|
||||
BeforeEach { $script:saved = Use-TestSettingsStore }
|
||||
AfterEach { Restore-TestSettingsStore -State $script:saved }
|
||||
|
||||
InModuleScope IntuneManagement { It 'y' { ... } }
|
||||
}
|
||||
```
|
||||
|
||||
`Use-TestSettingsStore` swaps in an empty memory store (so every key resolves to its
|
||||
registered default) and returns the previous state for `Restore-TestSettingsStore`.
|
||||
|
||||
**The hooks must be declared at `Describe` level, wrapping `InModuleScope`.** Pester 4
|
||||
silently never runs a `BeforeEach`/`AfterEach` declared *inside* an `InModuleScope`
|
||||
block - nothing fails, the tests just run against the developer's real store and write
|
||||
to it. `Static.Tests.ps1` has a gate for this with a shrink-only allowlist of the files
|
||||
that still get it wrong.
|
||||
|
||||
`Tests/SettingsStore.Tests.ps1` covers the store modes, path resolution, value shape,
|
||||
provenance, portability and the public surface.
|
||||
|
||||
## Auditing
|
||||
|
||||
`Tools/Audit-Settings.ps1` cross-references every registration against every read and
|
||||
write. It is wired into `Static.Tests.ps1` as a gate; see its help for the full
|
||||
taxonomy. Errors: orphan registrations, engine-consumed-but-UI-registered (breaks
|
||||
headless sessions), SubPath-mismatched access, duplicate registrations. Warnings:
|
||||
UI-only-but-engine-registered, and registered keys addressed by path instead of through
|
||||
the resolver.
|
||||
|
||||
```powershell
|
||||
.\Tools\Audit-Settings.ps1
|
||||
.\Tools\Audit-Settings.ps1 -IncludeUndefined # also keys read but never registered
|
||||
```
|
||||
@@ -0,0 +1,79 @@
|
||||
# Settings Cache Logging
|
||||
|
||||
## Settings
|
||||
|
||||
**[Settings](Settings.md) is the reference** - the three layers, the three store modes
|
||||
(registry / JSON file / in-memory), scope and precedence, the public `*-IMSetting`
|
||||
cmdlets, portable settings files, and the testing helpers. In short:
|
||||
|
||||
```text
|
||||
Add-SettingsObject
|
||||
-> setting metadata registered in a section
|
||||
-> Get-SettingValue / Get-IMSetting
|
||||
-> tenant-specific persisted value
|
||||
-> global persisted value
|
||||
-> setting DefaultValue
|
||||
```
|
||||
|
||||
Important setting groups include:
|
||||
|
||||
| Area | Examples |
|
||||
| --- | --- |
|
||||
| Graph/auth | `ActiveAuthProvider`, app ID/secret/cert settings, cloud settings. |
|
||||
| performance | `UseBatchAPI`, `UseParallelBatchAPI`, `ParallelBatchThrottle`, `GraphPaceIdentityEndpoints`. |
|
||||
| import/export | `ImportType`, assignment/scope tag import defaults, matching settings. |
|
||||
| UI | delete/bulk delete visibility, expand assignments, formatting. |
|
||||
| cache | clear cache before import/export, clear all cache. |
|
||||
|
||||
## Cache
|
||||
|
||||
Core cache functions:
|
||||
|
||||
| Function | Purpose |
|
||||
| --- | --- |
|
||||
| `Set-CacheObject` | writes in-memory or persistent cache values. |
|
||||
| `Get-CacheObject` | reads cache values with optional default. |
|
||||
| `Clear-CacheObject` | removes cache values. |
|
||||
| `Get-CacheStats` | diagnostics. |
|
||||
|
||||
Common cache keys:
|
||||
|
||||
| Key pattern | Meaning |
|
||||
| --- | --- |
|
||||
| `AADObjectCache_<tenant>` | resolved groups/users/service principals for migration/import. |
|
||||
| `TenantCache_<tenant>` | persistent backing file for tenant cache values. |
|
||||
| `_migFileCache` script variables | per-bulk-export migration table buffers. |
|
||||
| `_appConfigTargetAppCache` | app config target app lookup cache. |
|
||||
|
||||
## Logging
|
||||
|
||||
Main functions:
|
||||
|
||||
| Function | Use |
|
||||
| --- | --- |
|
||||
| `Write-Log` | normal and warning/error messages. |
|
||||
| `Write-LogDebug` | debug-only diagnostics. |
|
||||
| `Write-LogError` | exception-aware logging. |
|
||||
| `Write-Status` | UI status line updates for long operations. |
|
||||
|
||||
Log behavior is influenced by settings such as `Debug`, `LogFile`, `LogFileSize`, and `LogOutputError`.
|
||||
|
||||
## Graph Call Telemetry
|
||||
|
||||
`Invoke-MSGraphAPI` and batch helpers populate `$script:AllGraphCalls`. The Graph Calls UI uses it to show:
|
||||
|
||||
| Field | Meaning |
|
||||
| --- | --- |
|
||||
| request URL/method | what was called. |
|
||||
| provider | MSAL, MgGraph, etc. |
|
||||
| status/error | result and parsed Graph error. |
|
||||
| duration/KB/object count/page count | performance and response shape. |
|
||||
| batch sub-requests | item-level status inside `$batch`. |
|
||||
|
||||
## Testing Notes
|
||||
|
||||
Settings can leak from the developer machine into tests, and tests can write into the
|
||||
developer's real store. Use `Use-TestSettingsStore` / `Restore-TestSettingsStore` from
|
||||
`Tests/TestBootstrap.ps1` - and declare the hooks at `Describe` level, not inside
|
||||
`InModuleScope`. See [Settings: Testing](Settings.md#testing).
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
# Settings reference
|
||||
|
||||
Every setting the module registers, by the section it appears under in the Settings
|
||||
dialog. **Setting** is the name shown in the dialog; **Key** is what `Get-IMSetting` /
|
||||
`Set-IMSetting` and a settings file use. Where values are stored and how tenant and global
|
||||
values combine is in [Settings.md](Settings.md).
|
||||
|
||||
**Generated** by `Tools/Export-SettingsReference.ps1` from `Get-IMSettingDefinition` - do not edit by hand. 83 settings.
|
||||
|
||||
A runbook settings file for `Import-IMSettingsStore` is a json object keyed by these names:
|
||||
|
||||
```json
|
||||
{ "ExportFolder": "D:\\exports", "ExportAssignments": true, "UseBatchAPI": true, "UseParallelBatchAPI": true }
|
||||
```
|
||||
|
||||
## Contents
|
||||
|
||||
- [Authentication](#authentication) (8)
|
||||
- [General](#general) (13)
|
||||
- [Import/Export](#importexport) (30)
|
||||
- [Intune](#intune) (8)
|
||||
- [Intune Tools](#intune-tools) (1)
|
||||
- [MS Graph General](#ms-graph-general) (5)
|
||||
- [MSAL](#msal) (14)
|
||||
- [OAuth](#oauth) (4)
|
||||
|
||||
## Authentication
|
||||
|
||||
| Setting | Key | Type | Default | Description |
|
||||
|---|---|---|---|---|
|
||||
| Active authentication provider | `ActiveAuthProvider` | List | `MSAL` | Which authentication backend to use. MSAL is the built-in default. MgGraph requires the Microsoft.Graph.Authentication PowerShell module to be installed. OAuth is a pure-PowerShell provider for automation (CI / scheduled tasks / managed identity / workload identity federation) - no SDK required. Values: `Microsoft Authentication Library`, `Microsoft Graph PowerShell SDK`, `Direct OAuth (no SDK)`. |
|
||||
| Application Id | `EntraCustomAppId` | String | | Custom Entra application (client) id used by MSAL and OAuth sign-in when no built-in application is selected above. The app registration needs the http://localhost redirect URI for browser-based login. |
|
||||
| Authority | `EntraCustomAuthority` | String | | |
|
||||
| Default cloud | `DefaultCloud` | List | `Public` | Microsoft cloud this tool signs in to by default. Public covers commercial + GCC commercial; USGov is GCC High; USGovDOD is GCC DoD; China is the Vianet cloud. Values: `Public (Global)`, `US Government (GCC High)`, `US Government (DoD)`, `China (Vianet)`. |
|
||||
| Entra application | `EntraApp` | List | | Built-in Entra application used for interactive sign-in by both the MSAL and OAuth providers. Leave empty to use the custom Application Id below, or the default Microsoft Graph PowerShell app. Values: `*** Do NOT use *** Microsoft Intune PowerShell`, `Microsoft Graph PowerShell`. |
|
||||
| Interactive login timeout (seconds) | `MSGraphInteractiveTimeoutSec` | Int | `600` | Maximum time to wait for an interactive login to complete (MSAL embedded/broker and OAuth browser flows). Only applies to flows a user is waiting in front of; silent and app-secret token requests have their own much shorter cap. The default was 180 s, which killed legitimate sign-ins that needed an account picker plus an MFA approval - and the sign-in status has a Cancel button, so an abandoned window does not depend on this timeout. |
|
||||
| Redirect URL | `EntraCustomAppRedirect` | String | | |
|
||||
| Tenant Id | `EntraCustomTenantId` | String | | |
|
||||
|
||||
## General
|
||||
|
||||
| Setting | Key | Type | Default | Description |
|
||||
|---|---|---|---|---|
|
||||
| Add errors to PowerShell output | `LogOutputError` | Boolean | `true` | Write errors to the Error Output of the PS Host. If disabled, errors will be written as a Warning. Eg. disable this if automation should skip logging PowerShell errors. |
|
||||
| Check for updates | `CheckForUpdates` | Boolean | `true` | Check GitHub if there is a later version available |
|
||||
| Debug | `Debug` | Boolean | `false` | |
|
||||
| Environment color | `EnvironmentColor` | List | | Background color of the environment badge. Text color is auto-calculated for contrast. |
|
||||
| Environment name | `EnvironmentText` | Text | | Label shown as a badge in the toolbar (e.g. Production, Lab). Leave empty to hide. |
|
||||
| Hide experimental platform notice | `HideExperimentalPlatformNotice` | Boolean | `false` | Stop showing the startup notice that macOS and Linux support is experimental. Clear this to see it again. |
|
||||
| Hide No-access items | `HideNoAccess` | Boolean | `false` | Remove items from the menu if object permissions is missing. Default is to mark them with red |
|
||||
| Log file | `LogFile` | File | | |
|
||||
| Max log file size | `LogFileSize` | Int | `1024` | |
|
||||
| Proxy URI | `ProxyURI` | | | Specify the URI for the proxy eg http://<server>:<port> |
|
||||
| Show tenant name | `MenuShowOrganizationName` | Boolean | `true` | Adds the organization name next to the login info on the menu bar |
|
||||
| Theme | `AppTheme` | List | `Default` | Application color theme. Default follows the Windows app theme. Values: `Default (follow Windows)`, `Light`, `Dark`. |
|
||||
| Use Intune role permissions for access marking | `UseRbacAccessMarking` | Boolean | `true` | Also ask Intune which resource actions the signed-in user's role allows, and mark menu items the user cannot change (orange) or read (red). Off = mark from the app's token scopes only. Refresh the token from the Profile popup after a role change. |
|
||||
|
||||
## Import/Export
|
||||
|
||||
| Setting | Key | Type | Default | Description |
|
||||
|---|---|---|---|---|
|
||||
| Add company name | `AddCompanyName` | Boolean | `true` | Default setting for adding company name to the export folder |
|
||||
| Add ID to export file | `AddIDToExportFile` | Boolean | `true` | This will add object ID to the export file to support objects with the same name e.g. ObjectName_ObjectId.json |
|
||||
| Add object type | `AddObjectType` | Boolean | `true` | Default setting for adding object type to the export folder |
|
||||
| Clear All Objects From Cache | `ClearAllObjectsFromCache` | Boolean | `false` | Whether to clear all cached objects (default is to keep assignments and scopes cached). Use this option if you experience stale cache issues. |
|
||||
| Clear Cache Before Export/Import | `ClearCacheBeforeExportImport` | List | `1` | Automatically clear object cache before export/import operations (useful when re-downloading profiles to get latest data) Values: `Bulk export only`, `Bulk and manual export`, `Bulk export and import`, `All operations`. |
|
||||
| Combine requests into batch calls | `UseBatchAPI` | Boolean | `true` | Combine Graph requests into $batch calls, up to 20 per call, on every path that can batch: listing, policy bodies, sub-resources, assignments, import and delete. Turn off to send every request on its own - slower, but each call shows individually in the Graph log. See Docs/GraphBatching.md. |
|
||||
| Convert synced groups | `ConvertSyncedGroupOnImport` | Boolean | `true` | When a group referenced by an imported policy was AD-synced in the source tenant and does not exist in the target, recreate it as a cloud Entra group. When off, the group is skipped and the reference is left untranslated. |
|
||||
| Create groups and filters | `CreateGroupOnImport` | Boolean | `true` | Create Entra groups and assignment filters referenced by an imported policy when they do not exist in the target tenant. Groups are created from the export's Groups sidecar (dynamic groups keep their membership rule) or as a default cloud security group; filters from the AssignmentFilters sidecar (platform + rule preserved). |
|
||||
| Default Conditional Access Policy State | `ConditionalAccessState` | List | `disabled` | Define the state imported Conditional Access policies get. It is recommended to keep this Off (disabled) to avoid accidental tenant lock out. Values: `As Exported - Change On to Report-only`, `As Exported`, `Report-only`, `Off`. |
|
||||
| Export Assignments | `ExportAssignments` | Boolean | `true` | Default setting for exporting assignments |
|
||||
| Export file encoding | `ExportFileEncoding` | List | `utf8` | Character encoding for exported JSON files. UTF-8 is recommended - it is what Intune, git and other tools expect. Change it only if an existing pipeline depends on another encoding. Values: `UTF-8`, `UTF-8 with BOM`, `Unicode (UTF-16 LE)`. |
|
||||
| Export Json format | `ExportJsonFormat` | List | `indented` | Layout of exported JSON. Indented is readable but PowerShell 5.1 and 7 indent differently, so files exported from different hosts differ even when the data is identical. Compact is identical on both - use it with 'Sort Json Properties' if the export is stored in git or compared by automation. Values: `Indented (readable)`, `Compact (single line)`. |
|
||||
| Export nested group levels | `ExportNestedGroupLevels` | String | `1` | Depth of group-membership recursion when exporting groups. 1 (default) only exports the group assigned to the policy. 2 also exports groups that are direct members of the assigned group. 3 goes one level deeper, and so on. Higher values mean more Graph calls and may hit throttling on large group trees. |
|
||||
| Graph request timeout (seconds) | `MSGraphRequestTimeoutSec` | Int | `100` | Maximum time in seconds to wait for a single Graph request before it is aborted. Bounds how long a stalled request can block the UI. Default 100. |
|
||||
| Import Assignments | `ImportAssignments` | Boolean | `true` | Default value for Import assignments when importing objects |
|
||||
| Import match normalized name | `ImportMatchEnableNormalizedName` | Boolean | `true` | Allow update and skip-if-exists import matching after removing organization-specific prefixes or variables from names. |
|
||||
| Import match organization tokens | `ImportMatchOrganizationTokens` | String | | Extra organization-specific words or prefixes to remove before normalized-name matching. Separate values with comma, semicolon, or new lines. |
|
||||
| Import match policy reference | `ImportMatchEnablePolicyToken` | Boolean | `true` | Allow update and skip-if-exists import matching by policy reference token in the object name, e.g. [SEC-1053]. Exact name and same-tenant ID matching are always available. |
|
||||
| Import match policy reference regex | `ImportMatchPolicyReferenceRegex` | String | `(?i)(?:\[(?<ref>[A-Z][A-Z0-9]{1,15}-\d{2,10})\])|(?<ref>\b[A-Z][A-Z0-9]{1,15}-\d{2,10}\b)` | Regex used to find policy reference tokens in names. It should include a named capture group called ref. Default matches values like [SEC-1053] or SEC-1053. |
|
||||
| Import Scope (Tags) | `ImportScopeTags` | Boolean | `true` | Default value for Import Scope (Tags) when importing objects |
|
||||
| Import type | `ImportType` | List | `alwaysImport` | How files are imported. Always import: no detection of existing objects. Skip if object exists: skip when a matching object is found. Replace: import the file, copy the existing object's assignments to it, then delete the existing object. Replace with assignments: same but assignments come from the import file. Update: settings on the existing object are replaced from the file. Values: `Always import`, `Skip if object exists`, `Replace`, `Replace with assignments`, `Update`. |
|
||||
| Multi Admin Approval justification | `MultiAdminApprovalJustification` | String | | Reason sent with every create/update/delete when the tenant requires Multi Admin Approval. Leave empty unless Tenant administration > Multi Admin Approval has an access policy covering what you are changing. With a justification set, protected changes are queued for a second administrator to approve instead of failing. |
|
||||
| Pace one-per-second Graph endpoints | `GraphPaceIdentityEndpoints` | Boolean | `true` | Graph allows one request per second per tenant, across all applications, on Conditional Access policies, named locations, authentication strengths and identity protection - and sends no Retry-After when it throttles them. When enabled, requests to those endpoints are sent one at a time, one second apart, and are kept out of parallel batch dispatch. Turn off only if Microsoft has raised the limit for your tenant. |
|
||||
| Parallel batch throttle limit | `ParallelBatchThrottle` | String | `4` | Maximum number of concurrent $batch POST requests when 'Parallel batch dispatch' is enabled. Higher values are faster but more likely to trigger HTTP 429 throttling. Recommended range: 2-8. |
|
||||
| Portable file names | `PortableFileNames` | Boolean | `false` | Remove characters that are invalid on ANY supported platform from exported file names, not just the ones invalid on the current one. Windows rejects " < > \| : * ? \ / while Linux and macOS reject only /, so an export made on Linux can contain file names Windows cannot open. Enable this when exports are shared between platforms. Changes the file names of new exports. |
|
||||
| Replace organization values in export files | `ExportReplaceOrganizationValues` | Boolean | `true` | Replace tenant-specific values with placeholders in exported JSON. By default the tenant id becomes %OrganizationId%; the organization name is left as written unless you opt in. Turn this off to export the raw values - readable and diff-friendly, but the file is then tied to the tenant it came from. Importing a file that already contains placeholders always resolves them, whatever this is set to. Which values are replaced can be changed, see Docs/ExportImportAndCopy.md. |
|
||||
| Resolve reference info | `ResolveReferenceInfo` | Boolean | `true` | This will export/import info for referenced/navigation properties eg certificates in VPN profiles etc. |
|
||||
| Root folder | `RootFolder` | Folder | | Root folder for exporting/importing objects |
|
||||
| Send batch calls in parallel (experimental) | `UseParallelBatchAPI` | Boolean | `false` | Send $batch calls concurrently instead of one at a time. Controls concurrency only - whether requests are batched at all is 'Combine requests into batch calls'. Requires PowerShell 7+. Speeds up large queries but raises the chance of HTTP 429 throttling; queues of 20 requests or fewer still go out one at a time. Leave off unless you've tested it in your tenant. |
|
||||
| Sort Json Properties | `SortJsonProperties` | Boolean | `false` | Sort JSON properties alphabetically when exporting to improve file readability and consistency |
|
||||
|
||||
## Intune
|
||||
|
||||
| Setting | Key | Type | Default | Description |
|
||||
|---|---|---|---|---|
|
||||
| App download folder | `IntuneAppDownloadFolder` | Folder | | Folder where app packages will be downloaded and where encryption files will be saved |
|
||||
| App packages folder | `IntuneAppPackagesFolder` | Folder | | Root folder where intune app packages are located |
|
||||
| Get all pages | `GetAllPages` | Boolean | `true` | Get all pages when getting items in the UI. Note: This can take long time in environments with lots of policies and apps. |
|
||||
| Graph Page Size | `GraphPageSize` | List | `0` | How many items load at a time Values: `Graph Default`, `5`, `20`, `50`, `100`, `1000`, `All`. |
|
||||
| Max characters per cell | `ObjectListMaxCellLength` | Int | `50` | With single-line values on, cut a cell's text at this many characters so one long description cannot push the other columns out of view. Hover the cell for the full text; sorting and the filter still use the whole value. 0 = no limit. |
|
||||
| Menu Object Type | `ObjectViewType` | List | `Group` | Specify object type for the menu. Group: Groups items together like the portal. Type - Single item based on API. Some APIs are split into multiple menu items. Values: `Group`, `Type (API)`. |
|
||||
| Save Encryption File | `IntuneSaveEncryptionFile` | Boolean | | Save encryption file when uploading an app. This can then be used to when downloading the app file. |
|
||||
| Single-line values in object list | `ObjectListFirstLineOnly` | Boolean | `true` | Show only the first line of multi-line values (e.g. store app descriptions) in the object list, so every row is one line high. Hover a cell for the full text. Takes effect when the list is next loaded. |
|
||||
|
||||
## Intune Tools
|
||||
|
||||
| Setting | Key | Type | Default | Description |
|
||||
|---|---|---|---|---|
|
||||
| Format OMA-URI Settings | `FormatOMAURI` | Boolean | `false` | Automatically clean up XML formatting in OMA-URI and ADMX registry policies for consistent output |
|
||||
|
||||
## MS Graph General
|
||||
|
||||
| Setting | Key | Type | Default | Description |
|
||||
|---|---|---|---|---|
|
||||
| Expand assignments | `ExpandAssignments` | Boolean | `true` | Expand assignments when listing objects. This can be used in custom columns based on assignment info |
|
||||
| Refresh Objects after copy | `RefreshObjectsAfterCopy` | Boolean | `true` | Reload the object list after copying an object |
|
||||
| Show Bulk Delete | `AllowBulkDelete` | Boolean | `true` | Allow using bulk delete to delete all objects of selected types |
|
||||
| Show Delete button | `AllowDelete` | Boolean | `false` | Allow deleting individual objectes |
|
||||
| Use Graph 1.0 (Not Recommended) | `UseGraphV1` | Boolean | `false` | This will use production verionof graph, v1.0. Note: Thot officially supported since this can have unpredicted results. Some parts will require Beta version of Graph. |
|
||||
|
||||
## MSAL
|
||||
|
||||
| Setting | Key | Type | Default | Description |
|
||||
|---|---|---|---|---|
|
||||
| Enable Continuous Access Evaluation (CAE) | `EnableCAE` | Boolean | `true` | Adds the 'cp1' client capability so Entra ID can revoke this session in near-real-time when admin policies change. Takes effect after an app restart / fresh login. |
|
||||
| Get Tenant List | `GetTenantList` | Boolean | `false` | Get a list of all tenants the current user has access to. Only used when the user has access to multiple tenants. This may cause duplicate login/consent prompts first time |
|
||||
| Log MSAL correlation IDs | `MSALCorrelationLogging` | Boolean | `false` | Generate and log a correlation Guid per AcquireToken call. Useful when working with Microsoft support; off by default to keep logs quiet. |
|
||||
| MSAL log level | `MSALLogLevel` | List | `Info` | Minimum severity of MSAL log messages to capture. Verbose is noisy. Values: `Error`, `Warning`, `Info`, `Verbose`. |
|
||||
| MSAL logging | `MSALEnableLogging` | Boolean | `true` | Write MSAL's internal log messages to msal.log under %LOCALAPPDATA%\IntuneManagement. Useful for diagnosing authentication failures. |
|
||||
| MSAL logging: include personal data (PII) | `MSALEnablePiiLogging` | Boolean | `false` | Include MSAL's PII log messages in msal.log and unredact broker/WAM error text (logged as '(pii)' otherwise). The log will then contain user names, tenant and object ids and token details - review it before sharing. Requires app restart. |
|
||||
| MSAL region (optional) | `MSALRegion` | String | | Force a regional ESTS endpoint (e.g. 'westeurope'). Leave empty for automatic. Applied via MSAL_FORCE_REGION; requires app restart. |
|
||||
| Remember Login | `CacheMSALToken` | Boolean | `true` | Store the MSAL token in an encrypted file and automatically log on when the script starts. The token is stored in the users profile and can only be decrypted by the user that created it. Note: Requires restart |
|
||||
| Sort Account List | `SortAccountList` | Boolean | `false` | Sort the list of cached accounts based on user name. Updated at restart or account change |
|
||||
| Sort Tenant List | `SortTenantList` | Boolean | `false` | Sort the list of available tenants based on Tenant name. Updated at restart or account change |
|
||||
| Use MsalCacheHelper library | `MSALUseCacheHelperLib` | Boolean | `true` | Use Microsoft.Identity.Client.Extensions.Msal.MsalCacheHelper for the token cache. Provides cross-process file locking and is the supported library. Disable to fall back to the legacy TokenCacheHelperEx (DPAPI, process-local lock). |
|
||||
| Use system browser for login | `UseSystemBrowser` | Boolean | `true` | Sign in using the default web browser instead of the embedded view or WAM. Enables passkey/FIDO2 login and browser extensions. Takes precedence over WAM. Custom app registrations need the http://localhost redirect URI (Mobile and desktop applications). Turn off to use the embedded window. Requires app restart. |
|
||||
| Use Web Account Manager (WAM) for login | `UseWAM` | Boolean | `false` | Use the Windows Web Account Manager broker (Windows Hello, device compliance claims). Turn this on only when the account you sign in with is your Windows account or one added under Settings > Accounts - for any other account WAM cannot keep the session alive and you are prompted again about every hour. Requires PowerShell 7 and an app restart. |
|
||||
| WAM: list OS accounts | `WAMListOSAccounts` | Boolean | `true` | When WAM is enabled, also surface machine-joined Entra accounts in the account picker (PS7+). Off-by-default on PS5. |
|
||||
|
||||
## OAuth
|
||||
|
||||
| Setting | Key | Type | Default | Description |
|
||||
|---|---|---|---|---|
|
||||
| OAuth browser prompt | `OAuthPrompt` | List | `select_account` | OAuth /authorize prompt behaviour. 'Force login' re-authenticates even with an active browser session (equivalent to force-interactive); 'None' fails if interaction would be required. Values: `Select account`, `Force login`, `Consent`, `None (silent)`. |
|
||||
| OAuth login hint (UPN) | `OAuthLoginHint` | String | | Optional UPN to pre-fill on the sign-in page (login_hint). |
|
||||
| OAuth redirect port | `OAuthRedirectPort` | Int | `0` | Fixed loopback port for the browser redirect (http://localhost:<port>). 0 = pick a free port automatically. Set a fixed port only if your app registration requires a specific http://localhost:<port> redirect. |
|
||||
| Remember login (cache token) | `OAuthCacheToken` | Boolean | `false` | Persist the OAuth refresh token (DPAPI-encrypted, current user) so the app silently resumes the browser session after a restart. When off, you sign in again after each restart (usually a quick browser redirect via existing SSO). |
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 396 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 177 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 272 KiB |
Reference in New Issue
Block a user