Files
macOS_IntuneManagement/Headless/IntuneManagement.Headless.psm1
Tomas Kracmar e13d14edcb feat(toolkit): complete macOS Intune Toolkit v1
Core enhancements:
- Expanded default export/import scope to ~45 object types including DeviceManagementIntents
- Added -AllPages pagination support across Graph queries for large tenants
- Invoke-GraphRequest now throws on 4xx/5xx instead of silently returning null
- Added macOS Keychain fallback for secret retrieval in headless auth flow
- Added NameSearchPattern/NameReplacePattern mutation support through export/import forms

New toolkit scripts:
- Bulk-AppAssignment.ps1: bulk-assign apps to groups/All Users/All Devices
- Bulk-AssignmentManager.ps1: add/remove assignments for any policy type with correct @odata.type
- Backup-Restore-Assignments.ps1: JSON backup with cross-tenant group resolution
- Export-AssignmentsToCsv.ps1: CSV/Markdown documentation output
- Bulk-RenamePolicies.ps1: regex search/replace and prefix mutations
- Bulk-DeviceOperations.ps1: delete/retire/wipe/lock/sync with -WhatIf safeguards
- Start-IntuneManagementTui.ps1: interactive terminal UI for headless operations
- Create-IntuneManagementApp.ps1: helper for app registration setup

Updated existing scripts:
- Export-Policies.ps1 / Import-Policies.ps1: wired mutation params through
- Start-HeadlessIntune.ps1: integrated TUI and new parameter forwarding
2026-04-14 15:11:09 +02:00

470 lines
13 KiB
PowerShell

$script:coreModulePath = Join-Path (Split-Path -Parent $PSScriptRoot) "Core.psm1"
if (Test-Path $script:coreModulePath)
{
Import-Module $script:coreModulePath -Force
}
function Get-DefaultIntunePolicyObjectTypes
{
@(
"ScopeTags",
"AssignmentFilters",
"ReusableSettings",
"RoleDefinitions",
"Notifications",
"DeviceHealthScripts",
"ComplianceScripts",
"PowerShellScripts",
"MacScripts",
"MacCustomAttributes",
"ADMXFiles",
"IntuneBranding",
"AzureBranding",
"TermsAndConditions",
"TermsOfUse",
"EnrollmentStatusPage",
"EnrollmentRestrictions",
"AppleEnrollmentTypes",
"AutoPilot",
"AndroidOEMConfig",
"DeviceCategories",
"AuthenticationStrengths",
"AuthenticationContext",
"NamedLocations",
"ConditionalAccess",
"CoManagementSettings",
"Applications",
"AppProtection",
"AppConfigurationManagedApp",
"AppConfigurationManagedDevice",
"UpdatePolicies",
"FeatureUpdates",
"QualityUpdates",
"DriverUpdateProfiles",
"HardwareConfigurations",
"InventoryPolicies",
"W365ProvisioningPolicies",
"W365UserSettings",
"AdministrativeTemplates",
"DeviceConfiguration",
"SettingsCatalog",
"CompliancePolicies",
"CompliancePoliciesV2",
"EndpointSecurity",
"DeviceManagementIntents",
"PolicySets"
)
}
function Get-DefaultBrowserAppId
{
"14d82eec-204b-4c2f-b7e8-296a70dab67e"
}
function Get-IntuneManagementProjectRoot
{
Split-Path -Parent $PSScriptRoot
}
function Resolve-HeadlessSettingsPath
{
param([string]$SettingsFile)
if($SettingsFile)
{
return $SettingsFile
}
# Default to the persistent data folder (same location used by Initialize-IntuneAuth)
Join-Path (Get-CloudApiDataFolder) "Settings.json"
}
function New-TemporaryBatchFile
{
param([string]$Prefix)
Join-Path ([IO.Path]::GetTempPath()) ("IntuneManagement.{0}.{1}.json" -f $Prefix, [guid]::NewGuid().ToString())
}
function Test-AuthParameters
{
param(
[string]$AuthMode,
[string]$AppId,
[string]$Secret,
[string]$Certificate
)
if($AuthMode -eq "Browser" -or $AuthMode -eq "DeviceCode")
{
return
}
if(-not $AppId)
{
throw "Specify -AppId for AppOnly auth."
return
}
if((-not $Secret) -and (-not $Certificate))
{
throw "Specify -Secret or -Certificate for AppOnly auth, or use -AuthMode Browser."
}
}
function Invoke-IntuneHeadlessBatch
{
param(
[Parameter(Mandatory = $true)]
[string]$TenantId,
[string]$AppId,
[string]$Secret,
[string]$Certificate,
[ValidateSet("AppOnly","Browser","DeviceCode")]
[string]$AuthMode = "AppOnly",
[string]$RedirectUri,
[Parameter(Mandatory = $true)]
[psobject]$BatchConfig,
[string]$SettingsFile,
[string]$BatchFile
)
if(($AuthMode -eq "Browser" -or $AuthMode -eq "DeviceCode") -and -not $AppId)
{
$AppId = Get-DefaultBrowserAppId
}
# Pre-load settings to fill missing AppId/Secret before auth validation
$settingsPath = Resolve-HeadlessSettingsPath $SettingsFile
if($AuthMode -eq "AppOnly" -and (Test-Path $settingsPath) -and (-not $AppId -or -not $Secret -and -not $Certificate))
{
try
{
$raw = Get-Content -Path $settingsPath -Raw -ErrorAction Stop
$settingsObj = ConvertFrom-Json $raw -AsHashtable -ErrorAction Stop
if($settingsObj -and $settingsObj.ContainsKey($TenantId))
{
$tenantNode = $settingsObj[$TenantId]
if(-not $AppId -and $tenantNode.ContainsKey("GraphAzureAppId"))
{
$AppId = $tenantNode["GraphAzureAppId"]
}
if(-not $Secret -and $tenantNode.ContainsKey("GraphAzureAppSecret"))
{
$Secret = $tenantNode["GraphAzureAppSecret"]
}
if(-not $Certificate -and $tenantNode.ContainsKey("GraphAzureAppCert"))
{
$Certificate = $tenantNode["GraphAzureAppCert"]
}
}
# macOS Keychain fallback for secret
if(-not $Secret -and $IsMacOS -and $AppId)
{
try
{
$keychainSecret = security find-generic-password -a "IntuneManagement" -s "IntuneMgmt-$AppId" -w 2>$null
if($keychainSecret) { $Secret = $keychainSecret }
}
catch { }
}
}
catch { }
}
Test-AuthParameters -AuthMode $AuthMode -AppId $AppId -Secret $Secret -Certificate $Certificate
$projectRoot = Get-IntuneManagementProjectRoot
$runtimeModule = Join-Path $projectRoot "Runtime/IntuneManagement.Runtime.psd1"
if(-not (Test-Path $runtimeModule))
{
throw "Could not find IntuneManagement.Runtime.psd1 in $projectRoot"
}
$deleteBatchFile = $false
if(-not $BatchFile)
{
$BatchFile = New-TemporaryBatchFile "Batch"
$deleteBatchFile = $true
}
try
{
$BatchConfig | ConvertTo-Json -Depth 20 | Out-File -LiteralPath $BatchFile -Encoding utf8 -Force
$invokeParams = @{
Silent = $true
JSonSettings = $true
JSonFile = $settingsPath
TenantId = $TenantId
AppId = $AppId
SilentBatchFile = $BatchFile
AuthMode = $AuthMode
}
if($RedirectUri)
{
$invokeParams.RedirectUri = $RedirectUri
}
if($AuthMode -eq "AppOnly" -and $Secret)
{
$invokeParams.Secret = $Secret
}
elseif($AuthMode -eq "AppOnly")
{
$invokeParams.Certificate = $Certificate
}
Import-Module $runtimeModule -Force
Initialize-IntuneManagementRuntime -View "IntuneGraphAPI" @invokeParams
}
finally
{
if($deleteBatchFile -and (Test-Path $BatchFile))
{
Remove-Item -LiteralPath $BatchFile -Force -ErrorAction SilentlyContinue
}
}
}
function Export-IntunePolicies
{
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$TenantId,
[string]$AppId,
[string]$Secret,
[string]$Certificate,
[ValidateSet("AppOnly","Browser","DeviceCode")]
[string]$AuthMode = "AppOnly",
[string]$RedirectUri,
[Parameter(Mandatory = $true)]
[string]$ExportPath,
[string]$SettingsFile,
[string]$BatchFile,
[string]$NameFilter = "",
[string]$NameSearchPattern = "",
[string]$NameReplacePattern = "",
[string[]]$ObjectTypes = (Get-DefaultIntunePolicyObjectTypes),
[switch]$IncludeAssignments,
[switch]$AddCompanyName
)
$batchConfig = [PSCustomObject]@{
BulkExport = @(
[PSCustomObject]@{ Name = "txtExportPath"; Value = $ExportPath },
[PSCustomObject]@{ Name = "txtExportNameFilter"; Value = $NameFilter },
[PSCustomObject]@{ Name = "txtExportNameSearchPattern"; Value = $NameSearchPattern },
[PSCustomObject]@{ Name = "txtExportNameReplacePattern"; Value = $NameReplacePattern },
[PSCustomObject]@{ Name = "chkAddObjectType"; Value = $true },
[PSCustomObject]@{ Name = "chkExportAssignments"; Value = $IncludeAssignments.IsPresent },
[PSCustomObject]@{ Name = "chkAddCompanyName"; Value = $AddCompanyName.IsPresent },
[PSCustomObject]@{ Name = "ObjectTypes"; Type = "Custom"; ObjectTypes = @($ObjectTypes) }
)
}
Invoke-IntuneHeadlessBatch `
-TenantId $TenantId `
-AppId $AppId `
-Secret $Secret `
-Certificate $Certificate `
-AuthMode $AuthMode `
-RedirectUri $RedirectUri `
-BatchConfig $batchConfig `
-SettingsFile $SettingsFile `
-BatchFile $BatchFile
}
function Import-IntunePolicies
{
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$TenantId,
[string]$AppId,
[string]$Secret,
[string]$Certificate,
[ValidateSet("AppOnly","Browser","DeviceCode")]
[string]$AuthMode = "AppOnly",
[string]$RedirectUri,
[Parameter(Mandatory = $true)]
[string]$ImportPath,
[string]$SettingsFile,
[string]$BatchFile,
[string]$NameFilter = "",
[string]$NameSearchPattern = "",
[string]$NameReplacePattern = "",
[ValidateSet("alwaysImport","skipIfExist","replace","replace_with_assignments","update")]
[string]$ImportType = "alwaysImport",
[string[]]$ObjectTypes = (Get-DefaultIntunePolicyObjectTypes),
[switch]$IncludeAssignments,
[switch]$IncludeScopeTags,
[switch]$ReplaceDependencyIds
)
$batchConfig = [PSCustomObject]@{
BulkImport = @(
[PSCustomObject]@{ Name = "txtImportPath"; Value = $ImportPath },
[PSCustomObject]@{ Name = "txtImportNameFilter"; Value = $NameFilter },
[PSCustomObject]@{ Name = "txtImportNameSearchPattern"; Value = $NameSearchPattern },
[PSCustomObject]@{ Name = "txtImportNameReplacePattern"; Value = $NameReplacePattern },
[PSCustomObject]@{ Name = "chkAddObjectType"; Value = $true },
[PSCustomObject]@{ Name = "chkImportScopes"; Value = $IncludeScopeTags.IsPresent },
[PSCustomObject]@{ Name = "chkImportAssignments"; Value = $IncludeAssignments.IsPresent },
[PSCustomObject]@{ Name = "chkReplaceDependencyIDs"; Value = $ReplaceDependencyIds.IsPresent },
[PSCustomObject]@{ Name = "cbImportType"; Value = $ImportType },
[PSCustomObject]@{ Name = "ObjectTypes"; Type = "Custom"; ObjectTypes = @($ObjectTypes) }
)
}
Invoke-IntuneHeadlessBatch `
-TenantId $TenantId `
-AppId $AppId `
-Secret $Secret `
-Certificate $Certificate `
-AuthMode $AuthMode `
-RedirectUri $RedirectUri `
-BatchConfig $batchConfig `
-SettingsFile $SettingsFile `
-BatchFile $BatchFile
}
function Invoke-IntunePolicyAction
{
[CmdletBinding(DefaultParameterSetName = 'Export')]
param(
[Parameter(Mandatory = $true)]
[ValidateSet("Export","Import")]
[string]$Action,
[Parameter(Mandatory = $true)]
[string]$TenantId,
[string]$AppId,
[string]$Secret,
[string]$Certificate,
[ValidateSet("AppOnly","Browser","DeviceCode")]
[string]$AuthMode = "AppOnly",
[string]$RedirectUri,
[string]$SettingsFile,
[string]$BatchFile,
[string]$NameFilter = "",
[string]$NameSearchPattern = "",
[string]$NameReplacePattern = "",
[string[]]$ObjectTypes = (Get-DefaultIntunePolicyObjectTypes),
[string]$ExportPath,
[string]$ImportPath,
[ValidateSet("alwaysImport","skipIfExist","replace","replace_with_assignments","update")]
[string]$ImportType = "alwaysImport",
[switch]$IncludeAssignments,
[switch]$AddCompanyName,
[switch]$IncludeScopeTags,
[switch]$ReplaceDependencyIds
)
switch($Action)
{
"Export"
{
if(-not $ExportPath) { throw "Export requires -ExportPath." }
Export-IntunePolicies `
-TenantId $TenantId `
-AppId $AppId `
-Secret $Secret `
-Certificate $Certificate `
-AuthMode $AuthMode `
-RedirectUri $RedirectUri `
-ExportPath $ExportPath `
-SettingsFile $SettingsFile `
-BatchFile $BatchFile `
-NameFilter $NameFilter `
-NameSearchPattern $NameSearchPattern `
-NameReplacePattern $NameReplacePattern `
-ObjectTypes $ObjectTypes `
-IncludeAssignments:$IncludeAssignments `
-AddCompanyName:$AddCompanyName
}
"Import"
{
if(-not $ImportPath) { throw "Import requires -ImportPath." }
Import-IntunePolicies `
-TenantId $TenantId `
-AppId $AppId `
-Secret $Secret `
-Certificate $Certificate `
-AuthMode $AuthMode `
-RedirectUri $RedirectUri `
-ImportPath $ImportPath `
-SettingsFile $SettingsFile `
-BatchFile $BatchFile `
-NameFilter $NameFilter `
-NameSearchPattern $NameSearchPattern `
-NameReplacePattern $NameReplacePattern `
-ImportType $ImportType `
-ObjectTypes $ObjectTypes `
-IncludeAssignments:$IncludeAssignments `
-IncludeScopeTags:$IncludeScopeTags `
-ReplaceDependencyIds:$ReplaceDependencyIds
}
}
}