mirror of
https://github.com/Micke-K/IntuneManagement.git
synced 2026-09-28 10:55:38 +02:00
274 lines
11 KiB
PowerShell
274 lines
11 KiB
PowerShell
#ImportOrder 110
|
|
|
|
#########################################################################################
|
|
#
|
|
# Settings Catalog
|
|
#
|
|
#########################################################################################
|
|
|
|
# region Settings Catalog
|
|
|
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("TypeNotFound","", Justification = "")]
|
|
class SettingsCatalogTypeBase : IntunePolicyTypeBase
|
|
{
|
|
static [bool] $IsAbstract = $true
|
|
Hidden [string[]]$_FamilyTypes = @()
|
|
|
|
SettingsCatalogTypeBase() : Base()
|
|
{
|
|
([SettingsCatalogTypeBase]$this).Init()
|
|
}
|
|
|
|
Init()
|
|
{
|
|
$this._NameProperty = "name"
|
|
$this._PolicyName = "Settings Catalog"
|
|
$this._ID = "SettingsCatalogBase"
|
|
$this._API = "deviceManagement/configurationPolicies"
|
|
$this._PolicyBaseName = "Settings Catalog"
|
|
$this._PropertiesToRemove = @('settingCount')
|
|
$this._Permissions = @("DeviceManagementConfiguration.ReadWrite.All")
|
|
$this._SubTypeColumn = "TemplateFamily=Family"
|
|
$this._Expand = "Settings"
|
|
$this._Icon = "DeviceConfiguration"
|
|
$this._Dependencies = @("ReusableSettings")
|
|
$this._ObjectClass = "SettingsCatalogObject"
|
|
$this._VerifyObject = $true
|
|
|
|
$this._PolicyTypeOrder = 200
|
|
}
|
|
|
|
[Hashtable]PreUpdateCommand([PSCustomObject]$PolicyObject, [PSCustomObject]$SourceObject)
|
|
{
|
|
# Settings Catalog updates must PUT the full body (including settings);
|
|
# a PATCH carrying the settings payload is rejected by Graph. Same
|
|
# contract as the portal and the original project
|
|
# (Start-PreUpdateSettingsCatalog).
|
|
return @{ "Method" = "PUT" }
|
|
}
|
|
|
|
[Hashtable]GetCompareConfig()
|
|
{
|
|
return @{
|
|
Prop = "settings"
|
|
GetKey = { param($s) Get-SettingsCatalogSettingKey $s }
|
|
GetValue = { param($s) Get-SettingsCatalogSettingValue $s }
|
|
GetCategory = { param($s) Get-SettingsCatalogSettingCategory $s }
|
|
}
|
|
}
|
|
|
|
[Boolean]CheckPolicy([PSCustomObject]$PolicyObject)
|
|
{
|
|
if($PolicyObject.'@odata.type' -ne "#microsoft.graph.deviceManagementConfigurationPolicy") { return $false }
|
|
|
|
if($PolicyObject.templateReference.templateFamily -notin $this._FamilyTypes) {
|
|
return $false
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
[Hashtable]PreImportCommand([PSCustomObject]$PolicyObject)
|
|
{
|
|
if($PolicyObject.JsonObject.templateReference.templateId) {
|
|
# I do not like this at all and it is a lazy but simple implementation...
|
|
# It turns out that settingInstanceTemplateId and settingValueTemplateId are case sensitive
|
|
# and there is ONE setting with a different casing in the Windows Baseline template.
|
|
# The export saves it with lowercase which causes the import to fail.
|
|
|
|
Write-Log "Get template $($PolicyObject.JsonObject.templateReference.templateId)"
|
|
$templateObj = Invoke-MSGraphAPI -Url "/deviceManagement/configurationPolicyTemplates('$($PolicyObject.JsonObject.templateReference.templateId)')"
|
|
if($templateObj.lifecycleState -and $templateObj.lifecycleState -ne "active") {
|
|
Write-Log "Template '$($templateObj.displayName)' '$($templateObj.displayVersion)' is in '$($templateObj.lifecycleState)' state. Current state: $($templateObj.lifecycleState). Import might fail." 2
|
|
}
|
|
#Todo: Should probably check for the latest active version and use that instead of the one in the templateReference
|
|
|
|
if(-not $script:baseLineTemplate) {
|
|
$script:baseLineTemplate = @{}
|
|
}
|
|
if($script:baseLineTemplate.ContainsKey($PolicyObject.JsonObject.templateReference.templateId)) {
|
|
$templateReference = $script:baseLineTemplate[$PolicyObject.JsonObject.templateReference.templateId]
|
|
}
|
|
else {
|
|
Write-Log "Get template settings for '$($templateObj.displayName)' '$($templateObj.displayVersion)' ($($PolicyObject.JsonObject.templateReference.templateId))"
|
|
$templateReference = Invoke-MSGraphAPI -Url "/deviceManagement/configurationPolicyTemplates('$($PolicyObject.JsonObject.templateReference.templateId)')/settingTemplates?`$expand=settingDefinitions&top=1000"
|
|
$script:baseLineTemplate.Add($PolicyObject.JsonObject.templateReference.templateId, $templateReference)
|
|
}
|
|
|
|
if($templateReference) {
|
|
$settingsJson = $PolicyObject.JsonObject.Settings | ConvertTo-Json -Depth 50
|
|
$templateIDs, $dummy = Get-DependencyIDs ($templateReference | ConvertTo-Json -Depth 50)
|
|
$objectIDs, $dummy = Get-DependencyIDs $settingsJson
|
|
$diff = Compare-Object @($templateIDs) @($objectIDs) -CaseSensitive
|
|
$updated = $false
|
|
foreach($diffItem in ($diff | Where-Object SideIndicator -eq "=>")) {
|
|
$templateID = $templateIDs | Where-Object { $_ -eq $diffItem.InputObject }
|
|
if($templateID) {
|
|
# Found but with different casing
|
|
$settingsJson = $settingsJson -replace $diffItem.InputObject, $templateID
|
|
$updated = $true
|
|
}
|
|
}
|
|
if($updated) {
|
|
$PolicyObject.JsonObject.Settings = @($settingsJson | ConvertFrom-Json -Depth 50)
|
|
}
|
|
}
|
|
}
|
|
return $null
|
|
}
|
|
}
|
|
|
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("TypeNotFound","", Justification = "")]
|
|
class SettingsCatalogObject : IntunePolicyBase
|
|
{
|
|
Hidden [String]$_TemplateFamilyName = $null
|
|
|
|
SettingsCatalogObject([PSCustomObject]$JsonObj) : Base($JsonObj) { $this.Init() }
|
|
|
|
SettingsCatalogObject() : Base()
|
|
{
|
|
$this.Init()
|
|
}
|
|
|
|
Hidden Init()
|
|
{
|
|
$settingCatalogClasses = Get-IntuneSettingsCatalogClasses
|
|
$policyType = $settingCatalogClasses | Where-Object { $_._FamilyTypes -contains $this.JsonObject.templateReference.templateFamily }
|
|
if($policyType) {
|
|
$this._PolicyType = $policyType
|
|
}
|
|
|
|
$this._PolicyName = ?? $this.JsonObject.templateReference.templateDisplayName $this._PolicyType.PolicyBaseType
|
|
|
|
# Opt into the sub-resource batching contract so Invoke-PolicyHydrate
|
|
# fans out the assignments fetch via $batch.
|
|
$this._HasSubResourceBatch = $true
|
|
|
|
Add-ObjectProperty $this "TemplateVersion" { $this.JsonObject.templateReference.templateDisplayVersion }
|
|
if($this.JsonObject.templateReference.templateFamily) {
|
|
$this._TemplateFamilyName = (?? (Get-EndpointSecurityCategoryName $this.JsonObject.templateReference.templateFamily) $this.JsonObject.templateReference.templateFamily)
|
|
}
|
|
else {
|
|
$this._TemplateFamilyName = $null
|
|
}
|
|
Add-ObjectProperty $this "TemplateFamily" { $this._TemplateFamilyName }
|
|
Add-ObjectProperty $this "Category" { $this._TemplateFamilyName }
|
|
}
|
|
|
|
#Hidden [String] GetName()
|
|
#{
|
|
# return $this.JsonObject.Name
|
|
#}
|
|
|
|
# Sub-resource contract. The per-id body GET ($expand=assignments,settings)
|
|
# returns an empty `assignments` array — known Graph quirk on
|
|
# configurationPolicies. We hit the dedicated /assignments endpoint via
|
|
# $batch instead so Invoke-PolicyHydrate fans them out in parallel.
|
|
[PSCustomObject[]] GetSubResourceBatchRequests([int]$Phase)
|
|
{
|
|
if($Phase -ne 1) { return @() }
|
|
return @([PSCustomObject]@{
|
|
Key = 'assignments'
|
|
Url = "$($this._PolicyType.API)/$($this.Id)/assignments"
|
|
})
|
|
}
|
|
|
|
[PSCustomObject[]] ApplySubResourceBatchResult([int]$Phase, [string]$Key, $Body)
|
|
{
|
|
if($Phase -eq 1 -and $Key -eq 'assignments') {
|
|
# Comma-prefix forces an array reference through the if-expression —
|
|
# without it, PowerShell unwraps a single-element @() on assignment
|
|
# and ConvertTo-Json then emits a bare object instead of [{...}].
|
|
# Lowercase `assignments` matches the Graph wire shape and the
|
|
# `assignments@odata.*` metadata properties the body fetch leaves
|
|
# alongside. PSObject is case-insensitive on read, so existing
|
|
# callers reading `Assignments` keep working.
|
|
$assignments = if($Body -and $Body.value) { ,@($Body.value) } else { ,@() }
|
|
Add-Member -InputObject $this.JsonObject -MemberType NoteProperty -Name 'assignments' -Value $assignments -Force
|
|
}
|
|
return @()
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#########################################################################################
|
|
#
|
|
# Reusable Settings
|
|
#
|
|
#########################################################################################
|
|
|
|
# region Reusable Settings
|
|
|
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("TypeNotFound","", Justification = "")]
|
|
class ReusableSettingsTypeBase : IntunePolicyTypeBase
|
|
{
|
|
static [bool] $IsAbstract = $true
|
|
|
|
ReusableSettingsTypeBase() : Base()
|
|
{
|
|
([ReusableSettingsTypeBase]$this).Init()
|
|
}
|
|
|
|
Init()
|
|
{
|
|
$this._PolicyName = "Reusable Settings"
|
|
$this._ID = "ReusableSettingsBase"
|
|
$this._API = "deviceManagement/reusablePolicySettings"
|
|
$this._PolicyBaseName = "Reusable Settings"
|
|
$this._PropertiesToRemove = @('Settings','@OData.Type')
|
|
$this._Permissions=@("DeviceManagementConfiguration.ReadWrite.All")
|
|
$this._ImportOrder = 70
|
|
$this._ExpandAssignmentsList = $false
|
|
$this._SkipRemoveProperties = @("@OData.Type")
|
|
$this._ObjectClass = "ReusableSettingObject"
|
|
$this._SupportsAssignments = $false
|
|
|
|
$this._PolicyTypeOrder = 210
|
|
}
|
|
|
|
[Boolean]CheckPolicy([PSCustomObject]$PolicyObject)
|
|
{
|
|
return $false
|
|
}
|
|
}
|
|
|
|
class ReusableSettingsObjectBase : IntunePolicyBase
|
|
{
|
|
ReusableSettingsObjectBase([PSCustomObject]$JsonObj) : Base($JsonObj) { $this.InitReusable() }
|
|
|
|
ReusableSettingsObjectBase() : Base() { $this.InitReusable() }
|
|
|
|
Hidden InitReusable()
|
|
{
|
|
# The list endpoint omits settingInstance; hydration fetches it via the
|
|
# sub-resource contract (single GET coalesced into the hydrate $batch).
|
|
# Owns the API in one place — was previously duplicated in
|
|
# Sync-BulkExportReusableSettings (Internal/PolicyHydrateExtras.ps1).
|
|
$this._HasSubResourceBatch = $true
|
|
}
|
|
|
|
[PSCustomObject[]] GetSubResourceBatchRequests([int]$Phase)
|
|
{
|
|
if($Phase -ne 1) { return @() }
|
|
if($this.JsonObject.settingInstance) { return @() } # already present
|
|
return @([PSCustomObject]@{
|
|
Key = 'reusableSettingInstance'
|
|
Url = "$($this._PolicyType.API)/$($this.Id)?`$select=settinginstance,displayname,description"
|
|
Headers = @{ Accept = 'application/json;odata.metadata=none' }
|
|
})
|
|
}
|
|
|
|
[PSCustomObject[]] ApplySubResourceBatchResult([int]$Phase, [string]$Key, $Body)
|
|
{
|
|
if($Phase -eq 1 -and $Key -eq 'reusableSettingInstance' -and $Body -and $Body.settingInstance) {
|
|
Add-Member -InputObject $this.JsonObject -MemberType NoteProperty -Name 'settingInstance' -Value $Body.settingInstance -Force
|
|
}
|
|
return @()
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#endregion
|