Policies: Import ingesteter (custom) ADMX-Vorlagen tenantübergreifend
Custom-ADMX-Definitionen (policyType 'admxIngested') haben tenant-spezifische IDs -> Binding per Export-Id scheiterte im Ziel-Tenant mit 404. Import löst solche Definitionen jetzt im Ziel über displayName/classType/categoryPath neu auf und bindet an die dortige Id; Presentations werden per label/@odata.type (Fallback: Reihenfolge je Typ) zugeordnet. Eingebaute Vorlagen (admxBacked) unverändert per stabiler Id. categoryPath wird dafür mitexportiert. Voraussetzung: dieselbe ADMX ist im Ziel-Tenant importiert; fehlt sie, wird die betroffene Einstellung mit klarer Meldung übersprungen (Rest wird angelegt). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+83
-8
@@ -243,7 +243,7 @@ function Get-GraphPolicyDetail {
|
|||||||
if ($cfg.Key -eq 'administrativetemplate') {
|
if ($cfg.Key -eq 'administrativetemplate') {
|
||||||
$cfgRoot = "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations/$Id"
|
$cfgRoot = "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations/$Id"
|
||||||
$base = Invoke-MgGraphRequestRetry -Uri $cfgRoot -Method GET -AsRawJson
|
$base = Invoke-MgGraphRequestRetry -Uri $cfgRoot -Method GET -AsRawJson
|
||||||
$dvUri = "$cfgRoot/definitionValues?`$expand=definition(`$select=id,classType,displayName,policyType,version)"
|
$dvUri = "$cfgRoot/definitionValues?`$expand=definition(`$select=id,classType,displayName,categoryPath,policyType,version)"
|
||||||
$dvs = @(Get-GraphPaged -Uri $dvUri -AsRawJson)
|
$dvs = @(Get-GraphPaged -Uri $dvUri -AsRawJson)
|
||||||
foreach ($dv in $dvs) {
|
foreach ($dv in $dvs) {
|
||||||
if (-not $dv) { continue }
|
if (-not $dv) { continue }
|
||||||
@@ -484,6 +484,62 @@ function Import-GraphSettingsCatalogPolicy {
|
|||||||
return Invoke-MgGraphRequestRetry -Uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -Method POST -Body $json -ContentType 'application/json'
|
return Invoke-MgGraphRequestRetry -Uri 'https://graph.microsoft.com/beta/deviceManagement/configurationPolicies' -Method POST -Body $json -ContentType 'application/json'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Loest eine ingestete (custom) ADMX-Definition im AKTUELLEN Ziel-Tenant auf.
|
||||||
|
# Custom-ADMX-IDs sind tenant-spezifisch -> Binding per Export-Id scheitert (404).
|
||||||
|
# Match ueber stabile Merkmale: displayName + classType (+ categoryPath).
|
||||||
|
# Voraussetzung: die ADMX ist im Ziel-Tenant importiert. Sonst $null.
|
||||||
|
function Resolve-TargetGpDefinition {
|
||||||
|
param($SrcDefinition)
|
||||||
|
$dn = [string](Get-PolicyProp $SrcDefinition 'displayName')
|
||||||
|
$ct = [string](Get-PolicyProp $SrcDefinition 'classType')
|
||||||
|
$cat = [string](Get-PolicyProp $SrcDefinition 'categoryPath')
|
||||||
|
if (-not $dn) { return $null }
|
||||||
|
$dnEsc = $dn -replace "'", "''"
|
||||||
|
$base = 'https://graph.microsoft.com/beta/deviceManagement/groupPolicyDefinitions'
|
||||||
|
$cands = @()
|
||||||
|
try {
|
||||||
|
$uri = "$base`?`$filter=" + [uri]::EscapeDataString("displayName eq '$dnEsc'")
|
||||||
|
$cands = @(Get-GraphPaged -Uri $uri -AsRawJson)
|
||||||
|
} catch { $cands = @() }
|
||||||
|
# Fallback, falls $filter auf displayName nicht unterstuetzt wird: ueber categoryPath.
|
||||||
|
if ($cands.Count -eq 0 -and $cat) {
|
||||||
|
try {
|
||||||
|
$catEsc = $cat -replace "'", "''"
|
||||||
|
$uri2 = "$base`?`$filter=" + [uri]::EscapeDataString("categoryPath eq '$catEsc'")
|
||||||
|
$cands = @(Get-GraphPaged -Uri $uri2 -AsRawJson) | Where-Object { [string](Get-PolicyProp $_ 'displayName') -eq $dn }
|
||||||
|
} catch { $cands = @() }
|
||||||
|
}
|
||||||
|
if (@($cands).Count -eq 0) { return $null }
|
||||||
|
$narrow = @($cands) | Where-Object {
|
||||||
|
((-not $ct) -or ([string](Get-PolicyProp $_ 'classType') -eq $ct)) -and
|
||||||
|
((-not $cat) -or ([string](Get-PolicyProp $_ 'categoryPath') -eq $cat))
|
||||||
|
}
|
||||||
|
$match = @($narrow) | Select-Object -First 1
|
||||||
|
if (-not $match) { $match = @($cands) | Select-Object -First 1 }
|
||||||
|
return $match
|
||||||
|
}
|
||||||
|
|
||||||
|
# Findet zu einer Quell-Presentation die passende Ziel-Presentation-Id:
|
||||||
|
# 1) per label + @odata.type, 2) Fallback: i-te Ziel-Presentation gleichen Typs
|
||||||
|
# (in Definitions-Reihenfolge; $Counter zaehlt je Typ mit).
|
||||||
|
function Resolve-TargetPresentationId {
|
||||||
|
param($TargetPres, $SrcPresentation, [hashtable]$Counter)
|
||||||
|
$srcLabel = [string](Get-PolicyProp $SrcPresentation 'label')
|
||||||
|
$srcType = [string](Get-PolicyProp $SrcPresentation '@odata.type')
|
||||||
|
if ($srcLabel) {
|
||||||
|
$m = @($TargetPres) | Where-Object {
|
||||||
|
([string](Get-PolicyProp $_ 'label') -eq $srcLabel) -and
|
||||||
|
([string](Get-PolicyProp $_ '@odata.type') -eq $srcType)
|
||||||
|
} | Select-Object -First 1
|
||||||
|
if ($m) { return [string](Get-PolicyProp $m 'id') }
|
||||||
|
}
|
||||||
|
$idx = 0; if ($Counter.ContainsKey($srcType)) { $idx = [int]$Counter[$srcType] }
|
||||||
|
$sameType = @($TargetPres) | Where-Object { [string](Get-PolicyProp $_ '@odata.type') -eq $srcType }
|
||||||
|
$Counter[$srcType] = $idx + 1
|
||||||
|
if ($idx -lt @($sameType).Count) { return [string](Get-PolicyProp $sameType[$idx] 'id') }
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
function Import-GraphAdministrativeTemplate {
|
function Import-GraphAdministrativeTemplate {
|
||||||
param([Parameter(Mandatory=$true)]$Policy)
|
param([Parameter(Mandatory=$true)]$Policy)
|
||||||
|
|
||||||
@@ -504,23 +560,43 @@ function Import-GraphAdministrativeTemplate {
|
|||||||
if (-not $newId) { throw 'Anlegen der Administrative-Vorlage-Huelle lieferte keine Id.' }
|
if (-not $newId) { throw 'Anlegen der Administrative-Vorlage-Huelle lieferte keine Id.' }
|
||||||
|
|
||||||
# 2) Jeden definitionValue einzeln anhaengen. Definition + Presentations werden
|
# 2) Jeden definitionValue einzeln anhaengen. Definition + Presentations werden
|
||||||
# per @odata.bind referenziert — die IDs eingebauter ADMX-Vorlagen sind
|
# per @odata.bind referenziert. EINGEBAUTE ADMX-Vorlagen (policyType
|
||||||
# tenantuebergreifend identisch, daher tenantunabhaengig einsetzbar.
|
# 'admxBacked') haben tenantuebergreifend identische IDs -> Export-Id direkt
|
||||||
|
# verwendbar. INGESTETE/CUSTOM ADMX ('admxIngested') hat tenant-spezifische
|
||||||
|
# IDs -> im Ziel-Tenant ueber displayName/classType/categoryPath neu aufloesen
|
||||||
|
# (setzt voraus, dass dieselbe ADMX im Ziel importiert ist; sonst 404).
|
||||||
$errors = @()
|
$errors = @()
|
||||||
foreach ($dv in @(Get-PolicyProp $Policy 'definitionValues')) {
|
foreach ($dv in @(Get-PolicyProp $Policy 'definitionValues')) {
|
||||||
if (-not $dv) { continue }
|
if (-not $dv) { continue }
|
||||||
$def = Get-PolicyProp $dv 'definition'
|
$def = Get-PolicyProp $dv 'definition'
|
||||||
$defId = [string](Get-PolicyProp $def 'id')
|
$srcDefId = [string](Get-PolicyProp $def 'id')
|
||||||
if (-not $defId) {
|
$defName = [string](Get-PolicyProp $def 'displayName'); if (-not $defName) { $defName = $srcDefId }
|
||||||
$errors += 'definitionValue ohne Definition-Id uebersprungen'
|
if (-not $srcDefId) { $errors += 'definitionValue ohne Definition-Id uebersprungen'; continue }
|
||||||
|
|
||||||
|
$ingested = ([string](Get-PolicyProp $def 'policyType') -eq 'admxIngested')
|
||||||
|
$defId = $srcDefId
|
||||||
|
$tgtPres = $null
|
||||||
|
if ($ingested) {
|
||||||
|
$tgtDef = Resolve-TargetGpDefinition $def
|
||||||
|
if (-not $tgtDef) {
|
||||||
|
$errors += "${defName}: ingestete ADMX-Definition im Ziel-Tenant nicht gefunden - die passende ADMX muss dort importiert sein"
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
$defId = [string](Get-PolicyProp $tgtDef 'id')
|
||||||
|
try { $tgtPres = @(Get-GraphPaged -Uri "$defRoot/$defId/presentations" -AsRawJson) } catch { $tgtPres = @() }
|
||||||
|
}
|
||||||
|
|
||||||
$presVals = @()
|
$presVals = @()
|
||||||
|
$typeCounter = @{}
|
||||||
foreach ($pv in @(Get-PolicyProp $dv 'presentationValues')) {
|
foreach ($pv in @(Get-PolicyProp $dv 'presentationValues')) {
|
||||||
if (-not $pv) { continue }
|
if (-not $pv) { continue }
|
||||||
$pres = Get-PolicyProp $pv 'presentation'
|
$pres = Get-PolicyProp $pv 'presentation'
|
||||||
|
if ($ingested) {
|
||||||
|
$presId = Resolve-TargetPresentationId -TargetPres $tgtPres -SrcPresentation $pres -Counter $typeCounter
|
||||||
|
if (-not $presId) { $errors += "${defName}: Presentation im Ziel nicht zuordenbar - Wert uebersprungen"; continue }
|
||||||
|
} else {
|
||||||
$presId = [string](Get-PolicyProp $pres 'id')
|
$presId = [string](Get-PolicyProp $pres 'id')
|
||||||
|
}
|
||||||
$entry = [ordered]@{
|
$entry = [ordered]@{
|
||||||
'@odata.type' = [string](Get-PolicyProp $pv '@odata.type')
|
'@odata.type' = [string](Get-PolicyProp $pv '@odata.type')
|
||||||
'presentation@odata.bind' = "$defRoot('$defId')/presentations('$presId')"
|
'presentation@odata.bind' = "$defRoot('$defId')/presentations('$presId')"
|
||||||
@@ -543,8 +619,7 @@ function Import-GraphAdministrativeTemplate {
|
|||||||
} catch {
|
} catch {
|
||||||
$m = $_.Exception.Message
|
$m = $_.Exception.Message
|
||||||
try { if ($_.ErrorDetails.Message) { $m = $_.ErrorDetails.Message } } catch {}
|
try { if ($_.ErrorDetails.Message) { $m = $_.ErrorDetails.Message } } catch {}
|
||||||
$dn = [string](Get-PolicyProp $def 'displayName'); if (-not $dn) { $dn = $defId }
|
$errors += "${defName}: $m"
|
||||||
$errors += "${dn}: $m"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user