Files
IntuneManagement/Internal/IntunePolicySetImport.ps1
T

153 lines
7.4 KiB
PowerShell

# Cross-tenant Policy Set import support (architecture rule R9 - new feature
# in a new file; the PolicySet helpers used by Replace-mode import live in
# Internal/IntuneManager.ps1).
#
# A policySet bundles references to other objects via items[].payloadId.
# Importing a set exported from ANOTHER tenant carries source-tenant ids that
# fail the POST. Resolve-IntunePolicySetItems rewrites each item so the set
# imports cleanly:
# 1. payloadId still valid in the target tenant -> keep (same-tenant import)
# 2. else match the item's displayName against the mapped PolicyType's
# target list (client-side - Intune ignores server-side name filters)
# 3. else DROP the item with a warning - one dangling reference fails the
# whole set.
# Items are then stripped to the POST-safe shape (the GET carries per-item
# ids, links, status and timestamps that Graph rejects on create) - same rule
# as the original project's Start-PreImportPolicySets.
# policySetItem @odata.type -> owning PolicyType Id(s) for name resolution.
# Unmapped item types keep their payloadId untouched.
$script:PolicySetItemTypeMap = @{
'#microsoft.graph.mobileAppPolicySetItem' = @('Applications')
'#microsoft.graph.targetedManagedAppConfigurationPolicySetItem' = @('AppConfigurationManagedApp')
'#microsoft.graph.managedAppProtectionPolicySetItem' = @('AppProtection')
'#microsoft.graph.mdmWindowsInformationProtectionPolicyPolicySetItem' = @('AppProtection')
'#microsoft.graph.windowsManagedAppProtectionPolicySetItem' = @('AppProtection')
'#microsoft.graph.managedDeviceMobileAppConfigurationPolicySetItem' = @('AppConfigurationManagedDevice')
'#microsoft.graph.iosLobAppProvisioningConfigurationPolicySetItem' = @('IosLobAppProvisioningConfigurations')
'#microsoft.graph.deviceCompliancePolicyPolicySetItem' = @('CompliancePolicies')
'#microsoft.graph.deviceConfigurationPolicySetItem' = @('DeviceConfiguration')
'#microsoft.graph.deviceManagementConfigurationPolicyPolicySetItem' = @('SettingsCatalog', 'EndpointSecuritySettingsCatalog')
'#microsoft.graph.groupPolicyConfigurationPolicySetItem' = @('AdministrativeTemplates')
'#microsoft.graph.deviceManagementScriptPolicySetItem' = @('PowerShellScripts')
'#microsoft.graph.enrollmentRestrictionsConfigurationPolicySetItem' = @('EnrollmentLimit')
'#microsoft.graph.windows10EnrollmentCompletionPageConfigurationPolicySetItem' = @('EnrollmentStatusPage')
'#microsoft.graph.windowsAutopilotDeploymentProfilePolicySetItem' = @('AutoPilot')
}
# Update-mode import: policySet items are a navigation property Graph refuses
# on PATCH - membership changes go through the dedicated /update action with
# added/updated/deleted item deltas (same flow as the original project's
# Start-PreUpdatePolicySets). The caller's PATCH then carries metadata only
# (_PropertiesToRemoveForUpdate strips 'items').
function Update-IntunePolicySetItems
{
param($PolicyObject, $ExistingObject, [int]$TokenId)
if($TokenId -eq 0) { $TokenId = [int](Get-DefaultTokenId) }
# Same cross-tenant re-pointing rules as create-mode import.
Resolve-IntunePolicySetItems -PolicyObject $PolicyObject -TokenId $TokenId
$json = $PolicyObject.JsonObject
$targetId = [string]$ExistingObject.Id
if(-not $targetId) { return }
$current = Invoke-MSGraphAPI -Url "deviceAppManagement/policySets/$($targetId)?`$expand=items" -TokenId $TokenId
$currentItems = @($current.items)
$addedItems = @()
$updatedItems = @()
$deletedItems = @()
foreach($item in @($json.items)) {
if(@($currentItems | Where-Object { [string]$_.payloadId -eq [string]$item.payloadId }).Count -gt 0) {
$updatedItems += $item
}
else {
$addedItems += $item
}
}
foreach($currentItem in $currentItems) {
if(@($json.items | Where-Object { [string]$_.payloadId -eq [string]$currentItem.payloadId }).Count -eq 0) {
$deletedItems += [string]$currentItem.id
}
}
Write-Log "Policy set '$($json.displayName)': update items. Add: $($addedItems.Count), Update: $($updatedItems.Count), Delete: $($deletedItems.Count)"
$updateBody = [PSCustomObject]@{
addedPolicySetItems = $addedItems
updatedPolicySetItems = $updatedItems
deletedPolicySetItems = $deletedItems
}
$result = Invoke-MSGraphAPI -Url "deviceAppManagement/policySets/$targetId/update" -HttpMethod "POST" -Content (ConvertTo-Json $updateBody -Depth 15) -TokenId $TokenId -FullResponseObject
if(-not $result.Success) {
Write-Log "Policy set '$($json.displayName)': item update action failed - $($result.StatusDescription)" 2
}
}
function Resolve-IntunePolicySetItems
{
param($PolicyObject, [int]$TokenId)
# A file-loaded import source has no token; fall back to the default.
# (Passing 0 into Get-GraphPolicies resolves ALL registered tokens when
# more than one provider is connected.)
if($TokenId -eq 0) { $TokenId = [int](Get-DefaultTokenId) }
$json = $PolicyObject.JsonObject
if(-not $json -or -not $json.items) { return }
Remove-Property $json 'items@odata.context'
$keepProps = @('@odata.type', 'payloadId', 'intent', 'settings')
$typeListCache = @{}
$resolvedItems = [System.Collections.Generic.List[object]]::new()
foreach($item in @($json.items)) {
$odata = [string]$item.'@odata.type'
$itemName = [string]$item.displayName
$payloadId = [string]$item.payloadId
$typeIds = $script:PolicySetItemTypeMap[$odata]
if($typeIds) {
$candidates = @()
foreach($typeId in $typeIds) {
if(-not $typeListCache.ContainsKey($typeId)) {
$typeListCache[$typeId] = @(Get-GraphPolicies -PolicyType $typeId -TokenId $TokenId)
}
$candidates += $typeListCache[$typeId]
}
if($payloadId -and @($candidates | Where-Object { [string]$_.Id -eq $payloadId }).Count -gt 0) {
# Reference still valid in the target tenant - keep as-is.
}
else {
$byName = @($candidates | Where-Object { $itemName -and [string]$_.Name -eq $itemName })
if($byName.Count -eq 1) {
Write-Log "Policy set '$($json.displayName)': item '$itemName' re-pointed from '$payloadId' to '$($byName[0].Id)'"
$item.payloadId = [string]$byName[0].Id
}
elseif($byName.Count -gt 1) {
Write-Log "Policy set '$($json.displayName)': multiple objects named '$itemName' in the target tenant - dropping item (ambiguous reference)" 2
continue
}
else {
Write-Log "Policy set '$($json.displayName)': no object named '$itemName' in the target tenant - dropping item (a dangling reference fails the whole set)" 2
continue
}
}
}
foreach($prop in @($item.PSObject.Properties | Where-Object { $_.Name -notin $keepProps })) {
Remove-Property $item $prop.Name
}
[void]$resolvedItems.Add($item)
}
$json.items = $resolvedItems.ToArray()
}