mirror of
https://github.com/Micke-K/IntuneManagement.git
synced 2026-09-28 19:05:38 +02:00
IntuneManagement 4.0.0-beta1
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
# 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()
|
||||
}
|
||||
Reference in New Issue
Block a user