Files
IntuneManagement/Classes/IntuneSettingsBaseClasses.ps1

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