mirror of
https://github.com/Micke-K/IntuneManagement.git
synced 2026-09-28 10:55:38 +02:00
153 lines
7.4 KiB
PowerShell
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()
|
|
}
|