Modules/M365DSCDRGUtil.psm1
function Get-StringFirstCharacterToUpper { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [System.String] $Value ) return $Value.Substring(0,1).ToUpper() + $Value.Substring(1,$Value.length-1) } function Get-StringFirstCharacterToLower { [CmdletBinding()] [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] [System.String] $Value ) return $Value.Substring(0,1).ToLower() + $Value.Substring(1,$Value.length-1) } function Remove-M365DSCCimInstanceTrailingCharacterFromExport { [CmdletBinding()] [OutputType([System.String])] param( [Parameter(Mandatory = $true)] [System.String] $DSCBlock ) $DSCBlock = $DSCBlock.replace(" ,`r`n" , " `r`n" ) $DSCBlock = $DSCBlock.replace("`r`n;`r`n" , "`r`n" ) $DSCBlock = $DSCBlock.replace("`r`n,`r`n" , "`r`n" ) return $DSCBlock } function Rename-M365DSCCimInstanceParameter { [CmdletBinding()] [OutputType([System.Collections.Hashtable], [System.Collections.Hashtable[]])] param( [Parameter(Mandatory = $true)] $Properties, [Parameter(Mandatory = $false)] [System.Collections.Hashtable] $KeyMapping = @{'odataType' = '@odata.type'} ) $result = $Properties $type = $Properties.getType().FullName #region Array if ($type -like '*[[\]]') { $values = @() foreach ($item in $Properties) { try { $values += Rename-M365DSCCimInstanceParameter $item -KeyMapping $KeyMapping } catch { Write-Verbose -Message "Error getting values for item {$item}" } } $result = $values return , $result } #endregion #region Single if ($type -like '*Hashtable') { $result = ([Hashtable]$Properties).clone() } if ($type -like '*CimInstance*' -or $type -like '*Hashtable*' -or $type -like '*Object*') { $hashProperties = Get-M365DSCDRGComplexTypeToHashtable -ComplexObject $result $keys = ($hashProperties.clone()).keys foreach ($key in $keys) { $keyName = $key.substring(0, 1).tolower() + $key.substring(1, $key.length - 1) if ($key -in $KeyMapping.Keys) { $keyName = $KeyMapping.$key } $property = $hashProperties.$key if ($null -ne $property) { $hashProperties.Remove($key) try { $subValue = Rename-M365DSCCimInstanceParameter $property -KeyMapping $KeyMapping if ($null -ne $subValue) { $hashProperties.add($keyName, $subValue) } } catch { Write-Verbose -Message "Error adding $property" } } } $result = $hashProperties } return $result #endregion } function Get-M365DSCDRGComplexTypeToHashtable { [CmdletBinding()] [OutputType([hashtable], [hashtable[]])] param( [Parameter()] $ComplexObject ) if ($null -eq $ComplexObject) { return $null } if ($ComplexObject.gettype().fullname -like '*[[\]]') { $results = @() foreach ($item in $ComplexObject) { if ($item) { $hash = Get-M365DSCDRGComplexTypeToHashtable -ComplexObject $item $results += $hash } } # PowerShell returns all non-captured stream output, not just the argument of the return statement. #An empty array is mangled into $null in the process. #However, an array can be preserved on return by prepending it with the array construction operator (,) return , [hashtable[]]$results } if ($ComplexObject.getType().fullname -like '*Dictionary*') { $results = @{} $ComplexObject = [hashtable]::new($ComplexObject) $keys = $ComplexObject.Keys foreach ($key in $keys) { if ($null -ne $ComplexObject.$key) { $keyName = $key $keyType = $ComplexObject.$key.gettype().fullname if ($keyType -like '*CimInstance*' -or $keyType -like '*Dictionary*' -or $keyType -like 'Microsoft.Graph.PowerShell.Models.*' -or $keyType -like 'Microsoft.Graph.Beta.PowerShell.Models.*' -or $keyType -like '*[[\]]') { $hash = Get-M365DSCDRGComplexTypeToHashtable -ComplexObject $ComplexObject.$key $results.Add($keyName, $hash) } else { $results.Add($keyName, $ComplexObject.$key) } } } return [hashtable]$results } $results = @{} if ($ComplexObject.getType().Fullname -like '*hashtable') { $keys = $ComplexObject.keys } else { $keys = $ComplexObject | Get-Member | Where-Object -FilterScript { $_.MemberType -eq 'Property' } } foreach ($key in $keys) { $keyName = $key if ($ComplexObject.getType().Fullname -notlike '*hashtable') { $keyName = $key.Name } if ($null -ne $ComplexObject.$keyName) { $keyType = $ComplexObject.$keyName.gettype().fullname if ($keyType -like '*CimInstance*' -or $keyType -like '*Dictionary*' -or $keyType -like 'Microsoft.Graph.*PowerShell.Models.*') { $hash = Get-M365DSCDRGComplexTypeToHashtable -ComplexObject $ComplexObject.$keyName if ($null -ne $hash -and $hash.Keys.Count -gt 0) { $results.Add($keyName, $hash) } } else { $results.Add($keyName, $ComplexObject.$keyName) } } } return [hashtable]$results } <# Use ComplexTypeMapping to overwrite the type of nested CIM Example $complexMapping=@( @{ Name="ApprovalStages" CimInstanceName="MSFT_MicrosoftGraphapprovalstage1" IsRequired=$false } @{ Name="PrimaryApprovers" CimInstanceName="MicrosoftGraphuserset" IsRequired=$false } @{ Name="EscalationApprovers" CimInstanceName="MicrosoftGraphuserset" IsRequired=$false } ) With Name: the name of the parameter to be overwritten CimInstanceName: The type of the CIM instance (can include or not the prefix MSFT_) IsRequired: If isRequired equals true, an empty hashtable or array will be returned. Some of the Graph parameters are required even though they are empty #> function Get-M365DSCDRGComplexTypeToString { [CmdletBinding()] param( [Parameter()] $ComplexObject, [Parameter(Mandatory = $true)] [System.String] $CIMInstanceName, [Parameter()] [Array] $ComplexTypeMapping, [Parameter()] [System.String] $Whitespace = '', [Parameter()] [System.uint32] $IndentLevel = 3, [Parameter()] [switch] $IsArray ) if ($null -eq $ComplexObject) { return $null } $indent = '' for ($i = 0; $i -lt $IndentLevel; $i++) { $indent += ' ' } #If ComplexObject is an Array if ($ComplexObject.GetType().FullName -like '*[[\]]') { $currentProperty = @() $IndentLevel++ foreach ($item in $ComplexObject) { $splat = @{ 'ComplexObject' = $item 'CIMInstanceName' = $CIMInstanceName 'IndentLevel' = $IndentLevel } if ($ComplexTypeMapping) { $splat.Add('ComplexTypeMapping', $ComplexTypeMapping) } $currentProperty += Get-M365DSCDRGComplexTypeToString -IsArray @splat } # PowerShell returns all non-captured stream output, not just the argument of the return statement. #An empty array is mangled into $null in the process. #However, an array can be preserved on return by prepending it with the array construction operator (,) return , $currentProperty } $currentProperty = '' if ($IsArray) { $currentProperty += "`r`n" $currentProperty += $indent } $CIMInstanceName = $CIMInstanceName.Replace('MSFT_', '') $currentProperty += "MSFT_$CIMInstanceName{`r`n" $IndentLevel++ $indent = ' ' * $IndentLevel $keyNotNull = 0 if ($ComplexObject.Keys.Count -eq 0) { return $null } foreach ($key in $ComplexObject.Keys) { if ($null -ne $ComplexObject.$key) { $keyNotNull++ if ($ComplexObject.$key.GetType().FullName -like 'Microsoft.Graph.PowerShell.Models.*' -or $key -in $ComplexTypeMapping.Name) { $hashPropertyType = $ComplexObject[$key].GetType().Name.ToLower() $IsArray = $false if ($ComplexObject[$key].GetType().FullName -like '*[[\]]') { $IsArray = $true } #overwrite type if object defined in mapping complextypemapping if ($key -in $ComplexTypeMapping.Name) { $hashPropertyType = ([Array]($ComplexTypeMapping | Where-Object -FilterScript { $_.Name -eq $key }).CimInstanceName)[0] $hashProperty = $ComplexObject[$key] #$currentProperty += "`r`n" } else { $hashProperty = Get-M365DSCDRGComplexTypeToHashtable -ComplexObject $ComplexObject[$key] } if (-not $IsArray) { $currentProperty += $indent + $key + ' = ' } if ($IsArray -and $key -in $ComplexTypeMapping.Name) { if ($ComplexObject.$key.Count -gt 0) { $currentProperty += $indent + $key + ' = ' $currentProperty += '@(' } } if ($IsArray) { $IndentLevel++ for ($i = 0; $i -lt $ComplexObject[$key].Count; $i++) { $item = $ComplexObject.$key[$i] if ($ComplexObject.$key.GetType().FullName -like 'Microsoft.Graph.PowerShell.Models.*') { $item = Get-M365DSCDRGComplexTypeToHashtable -ComplexObject $item } $nestedPropertyString = Get-M365DSCDRGComplexTypeToString ` -ComplexObject $item ` -CIMInstanceName $hashPropertyType ` -IndentLevel $IndentLevel ` -ComplexTypeMapping $ComplexTypeMapping ` -IsArray if ([string]::IsNullOrWhiteSpace($nestedPropertyString)) { $nestedPropertyString = "@()`r`n" } if ($i -ne 0) { # Remove the line break at the start because every item contains a trailing line break # which would lead to two line breaks between each item $nestedPropertyString = $nestedPropertyString.Substring(2) } $currentProperty += $nestedPropertyString } $IndentLevel-- } else { $nestedPropertyString = Get-M365DSCDRGComplexTypeToString ` -ComplexObject $hashProperty ` -CIMInstanceName $hashPropertyType ` -IndentLevel $IndentLevel ` -ComplexTypeMapping $ComplexTypeMapping if ([string]::IsNullOrWhiteSpace($nestedPropertyString)) { $nestedPropertyString = "`$null`r`n" } $currentProperty += $nestedPropertyString } if ($IsArray) { if ($ComplexObject.$key.Count -gt 0) { $currentProperty += $indent $currentProperty += ')' $currentProperty += "`r`n" } } $IsArray = $PSBoundParameters.IsArray } else { $currentValue = $ComplexObject[$key] if ($currentValue.GetType().Name -eq 'String') { $currentValue = $ComplexObject[$key].Replace("'", "''").Replace("�", "''") } $currentProperty += Get-M365DSCDRGSimpleObjectTypeToString -Key $key -Value $currentValue -Space ($indent) } } else { $mappedKey = $ComplexTypeMapping | Where-Object -FilterScript { $_.name -eq $key } if ($mappedKey -and $mappedKey.isRequired) { if ($mappedKey.IsArray) { $currentProperty += "$indent$key = @()`r`n" } else { $currentProperty += "$indent$key = `$null`r`n" } } } } $indent = '' $indent = ' ' * ($IndentLevel -1) if ($key -in $ComplexTypeMapping.Name) { $currentProperty += "`r`n" } $currentProperty += "$indent}" if ($IsArray -or $IndentLevel -gt 4) { $currentProperty += "`r`n" } #Indenting last parenthesis when the cim instance is an array if ($IndentLevel -eq 5) { $indent = ' ' * ($IndentLevel -2) $currentProperty += $indent } $emptyCIM = $currentProperty.Replace(' ', '').Replace("`r`n", '') if ($emptyCIM -eq "MSFT_$CIMInstanceName{}") { $currentProperty = $null } if ($null -ne $currentProperty) { $fancySingleQuotes = "[\u2019\u2018]" $fancyDoubleQuotes = "[\u201C\u201D]" $currentProperty = [regex]::Replace($currentProperty, $fancySingleQuotes, "''") $currentProperty = [regex]::Replace($currentProperty, $fancyDoubleQuotes, '"') } return $currentProperty } function Get-M365DSCDRGSimpleObjectTypeToString { [CmdletBinding()] [OutputType([System.String])] param( [Parameter(Mandatory = $true)] [System.String] $Key, [Parameter(Mandatory = $true)] $Value, [Parameter()] [System.String] $Space = ' ' ) $returnValue = '' switch -Wildcard ($Value.GetType().Fullname ) { '*.Boolean' { $returnValue = $Space + $Key + " = `$" + $Value.ToString() + "`r`n" } '*.String' { if ($key -eq '@odata.type') { $key = 'odataType' } $returnValue = $Space + $Key + " = '" + $Value + "'`r`n" } '*.DateTime' { $returnValue = $Space + $Key + " = '" + $Value + "'`r`n" } '*[[\]]' { $returnValue = $Space + $key + ' = @(' $whitespace = '' $newline = '' if ($Value.count -gt 1) { $returnValue += "`r`n" $whitespace = $Space + ' ' $newline = "`r`n" } foreach ($item in ($Value | Where-Object -FilterScript { $null -ne $_ })) { switch -Wildcard ($item.GetType().Fullname) { '*.String' { $returnValue += "$whitespace'$item'$newline" } '*.DateTime' { $returnValue += "$whitespace'$item'$newline" } Default { $returnValue += "$whitespace$item$newline" } } } if ($Value.count -gt 1) { $returnValue += "$Space)`r`n" } else { $returnValue += ")`r`n" } } Default { $returnValue = $Space + $Key + ' = ' + $Value + "`r`n" } } return $returnValue } function Compare-M365DSCComplexObject { [CmdletBinding()] [OutputType([System.Boolean])] param( [Parameter()] $Source, [Parameter()] $Target ) #Comparing full objects if ($null -eq $Source -and $null -eq $Target) { return $true } $sourceValue = '' $targetValue = '' if (($null -eq $Source) -xor ($null -eq $Target)) { if ($null -eq $Source) { $sourceValue = 'Source is null' } if ($null -eq $Target) { $targetValue = 'Target is null' } Write-Verbose -Message "Configuration drift - Complex object: {$sourceValue$targetValue}" return $false } if ($Source.GetType().FullName -like '*CimInstance[[\]]' -or $Source.GetType().FullName -like '*Hashtable[[\]]') { if ($Source.Count -ne $Target.Count) { Write-Verbose -Message "Configuration drift - The complex array have different number of items: Source {$($Source.Count)} Target {$($Target.Count)}" return $false } if ($Source.Count -eq 0) { return $true } if ($Source[0].CimClass.CimClassName -eq 'MSFT_DeviceManagementConfigurationPolicyAssignments' -or ($Source[0].CimClass.CimClassName -like 'MSFT_Intune*Assignments' -and $Source[0].CimClass.CimClassName -ne 'MSFT_IntuneDeviceRemediationPolicyAssignments')) { $compareResult = Compare-M365DSCIntunePolicyAssignment ` -Source @($Source) ` -Target @($Target) if (-not $compareResult) { Write-Verbose -Message "Configuration drift - Intune Policy Assignment: $key Source {$Source} Target {$Target}" return $false } return $true } foreach ($item in $Source) { foreach ($targetItem in $Target) { $compareResult = Compare-M365DSCComplexObject ` -Source $item ` -Target $targetItem if ($compareResult) { break } } if (-not $compareResult) { Write-Verbose -Message 'Configuration drift - The complex array items are not identical' return $false } } return $true } if ($Source.GetType().FullName -like "*CimInstance") { $keys = @() $Source.CimInstanceProperties | Foreach-Object { if ($_.Name -notin @('PSComputerName', 'CimClass', 'CimInstanceProperties', 'CimSystemProperties') ` -and $_.IsValueModified) { $keys += $_.Name } } } else { $keys = $Source.Keys | Where-Object -FilterScript { $_ -ne 'PSComputerName' } } if ($Target.GetType().FullName -like "*CimInstance") { $targetKeys = @() $Target.CimInstanceProperties | Foreach-Object { if ($_.Name -notin @('PSComputerName', 'CimClass', 'CimInstanceProperties', 'CimSystemProperties') ` -and $_.IsValueModified) { $targetKeys += $_.Name } } } elseif ($Target.GetType().FullName -like "*Hashtable") { $targetKeys = $Target.Keys | Where-Object -FilterScript { $_ -ne 'PSComputerName' } } else # Most likely a Microsoft Graph Model { $Target = Get-M365DSCDRGComplexTypeToHashtable -ComplexObject $Target $targetKeys = $Target.Keys | Where-Object -FilterScript { $_ -ne 'PSComputerName' } } foreach ($key in $keys) { #Matching possible key names between Source and Target $sourceValue = $Source.$key # Some classes might contain default properties that have the same name as the key, # so we need to check if the key is present in the target object --> Hashtable <-> IsReadOnly property if ($key -in $targetKeys) { $targetValue = $Target.$key } else { $targetValue = $null } #One of the item is null and not the other if (($null -eq $Source.$key) -xor ($null -eq $targetValue)) { if ($null -eq $Source.$key) { $sourceValue = 'null' } if ($null -eq $targetValue) { $targetValue = 'null' } Write-Verbose -Message "Configuration drift - key: $key Source {$sourceValue} Target {$targetValue}" return $false } #Both keys aren't null or empty if (($null -ne $Source.$key) -and ($null -ne $Target.$key)) { if ($Source.$key.GetType().FullName -like '*CimInstance*' -or $Source.$key.GetType().FullName -like '*hashtable*') { if ($Source.$key.GetType().FullName -like '*CimInstance' -and ( $Source.$key.CimClass.CimClassName -eq 'MSFT_DeviceManagementConfigurationPolicyAssignments' -or $Source.$key.CimClass.CimClassName -like 'MSFT_Intune*Assignments')) { $compareResult = Compare-M365DSCIntunePolicyAssignment ` -Source @($Source.$key) ` -Target @($Target.$key) } else { #Recursive call for complex object $compareResult = Compare-M365DSCComplexObject ` -Source $Source.$key ` -Target $Target.$key } if (-not $compareResult) { Write-Verbose -Message "Configuration drift - complex object key: $key Source {$sourceValue} Target {$targetValue}" return $false } } else { #Simple object comparison $referenceObject = $Target.$key $differenceObject = $Source.$key #Identifying date from the current values $targetType = ($Target.$key.GetType()).Name if ($targetType -like '*Date*') { $compareResult = $true $sourceDate = [DateTime]$Source.$key if ($sourceDate -ne $targetType) { $compareResult = $null } } else { $compareResult = Compare-Object ` -ReferenceObject ($referenceObject) ` -DifferenceObject ($differenceObject) } if ($null -ne $compareResult) { Write-Verbose -Message "Configuration drift - simple object key: $key Source {$sourceValue} Target {$targetValue}" return $false } } } } return $true } function Convert-M365DSCDRGComplexTypeToHashtable { [CmdletBinding()] [OutputType([hashtable], [hashtable[]])] param( [Parameter(Mandatory = $true)] $ComplexObject ) if ($ComplexObject.getType().Fullname -like '*[[\]]') { $results = @() foreach ($item in $ComplexObject) { $hash = Convert-M365DSCDRGComplexTypeToHashtable -ComplexObject $item $results += $hash } #Write-Verbose -Message ("Convert-M365DSCDRGComplexTypeToHashtable >>> results: "+(convertTo-JSON $results -Depth 20)) # PowerShell returns all non-captured stream output, not just the argument of the return statement. #An empty array is mangled into $null in the process. #However, an array can be preserved on return by prepending it with the array construction operator (,) return , [hashtable[]]$results } $hashComplexObject = Get-M365DSCDRGComplexTypeToHashtable -ComplexObject $ComplexObject if ($null -ne $hashComplexObject) { $results = $hashComplexObject.clone() $keys = $hashComplexObject.Keys | Where-Object -FilterScript { $_ -ne 'PSComputerName' } foreach ($key in $keys) { if ($hashComplexObject[$key] -and $hashComplexObject[$key].getType().Fullname -like '*CimInstance*') { $results[$key] = Convert-M365DSCDRGComplexTypeToHashtable -ComplexObject $hashComplexObject[$key] } else { $propertyName = $key[0].ToString().ToLower() + $key.Substring(1, $key.Length - 1) $propertyValue = $results[$key] $results.remove($key) | Out-Null $results.add($propertyName, $propertyValue) } } } return [hashtable]$results } function Get-SettingCatalogSettingValue { [CmdletBinding()] [OutputType([System.Collections.Hashtable],[System.Collections.Hashtable[]])] param ( [Parameter()] $SettingValue, [Parameter()] $SettingValueType ) switch -Wildcard ($SettingValueType) { '*ChoiceSettingInstance' { $complexValue = @{} $complexValue.Add('odataType',$SettingValue.'@odata.type') $complexValue.Add('Value',$SettingValue.value) $children = @() foreach($child in $SettingValue.children) { $complexChild = @{} $complexChild.Add('SettingDefinitionId', $child.settingDefinitionId) $complexChild.Add('odataType', $child.'@odata.type') $valueName = $child.'@odata.type'.replace('#microsoft.graph.deviceManagementConfiguration', '').replace('Instance', 'Value') $valueName = Get-StringFirstCharacterToLower -Value $valueName $rawValue = $child.$valueName $childSettingValue = Get-SettingCatalogSettingValue -SettingValue $rawValue -SettingValueType $child.'@odata.type' $complexChild.Add($valueName,$childSettingValue) $children += $complexChild } $complexValue.Add('Children',$children) } '*ChoiceSettingCollectionInstance' { $complexCollection = @() foreach($item in $SettingValue) { $complexValue = @{} $complexValue.Add('Value',$item.value) $children = @() foreach($child in $item.children) { $complexChild = @{} $complexChild.Add('SettingDefinitionId', $child.settingDefinitionId) $complexChild.Add('odataType', $child.'@odata.type') $valueName = $child.'@odata.type'.replace('#microsoft.graph.deviceManagementConfiguration', '').replace('Instance', 'Value') $valueName = Get-StringFirstCharacterToLower -Value $valueName $rawValue = $child.$valueName $childSettingValue = Get-SettingCatalogSettingValue -SettingValue $rawValue -SettingValueType $child.'@odata.type' $complexChild.Add($valueName,$childSettingValue) $children += $complexChild } $complexValue.Add('Children',$children) $complexCollection += $complexValue } return ,([hashtable[]]$complexCollection) } '*SimpleSettingInstance' { $complexValue = @{} $complexValue.Add('odataType',$SettingValue.'@odata.type') $valueName = 'IntValue' $value = $SettingValue.value if($SettingValue.'@odata.type' -ne '#microsoft.graph.deviceManagementConfigurationIntegerSettingValue') { $valueName = 'StringValue' } $complexValue.Add($valueName,$value) if($SettingValue.'@odata.type' -eq '#microsoft.graph.deviceManagementConfigurationSecretSettingValue') { $complexValue.Add('ValueState',$SettingValue.valueState) } } '*SimpleSettingCollectionInstance' { $complexCollection = @() foreach($item in $SettingValue) { $complexValue = @{} $complexValue.Add('odataType',$item.'@odata.type') $valueName = 'IntValue' $value = $item.value if($item.'@odata.type' -ne '#microsoft.graph.deviceManagementConfigurationIntegerSettingValue') { $valueName = 'StringValue' } $complexValue.Add($valueName,$value) if($item.'@odata.type' -eq '#microsoft.graph.deviceManagementConfigurationSecretSettingValue') { $complexValue.Add('ValueState',$item.valueState) } $complexCollection += $complexValue } return ,([hashtable[]]$complexCollection) } '*GroupSettingInstance' { $complexValue = @{} $complexValue.Add('odataType',$SettingValue.'@odata.type') $children = @() foreach($child in $SettingValue.children) { $complexChild = @{} $complexChild.Add('SettingDefinitionId', $child.settingDefinitionId) $complexChild.Add('odataType', $child.'@odata.type') $valueName = $child.'@odata.type'.replace('#microsoft.graph.deviceManagementConfiguration', '').replace('Instance', 'Value') $valueName = Get-StringFirstCharacterToLower -Value $valueName $rawValue = $child.$valueName $settingValue = Get-SettingCatalogSettingValue -SettingValue $rawValue -SettingValueType $child.'@odata.type' $complexChild.Add($valueName,$settingValue) $children += $complexChild } $complexValue.Add('Children',$children) } '*GroupSettingCollectionInstance' { $complexCollection = @() foreach($groupSettingValue in $SettingValue) { $complexValue = @{} #$complexValue.Add('odataType',$SettingValue.'@odata.type') $children = @() foreach($child in $groupSettingValue.children) { $complexChild = @{} $complexChild.Add('SettingDefinitionId', $child.settingDefinitionId) $complexChild.Add('odataType', $child.'@odata.type') $valueName = $child.'@odata.type'.replace('#microsoft.graph.deviceManagementConfiguration', '').replace('Instance', 'Value') $valueName = Get-StringFirstCharacterToLower -Value $valueName $rawValue = $child.$valueName $settingValue = Get-SettingCatalogSettingValue -SettingValue $rawValue -SettingValueType $child.'@odata.type' $complexChild.Add($valueName,$settingValue) $children += $complexChild } $complexValue.Add('Children',$children) $complexCollection += $complexValue } return ,([hashtable[]]$complexCollection) } } return $complexValue } function Get-SettingCatalogPolicySettingsFromTemplate { [CmdletBinding()] [OutputType([System.Array])] param ( [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $DSCParams, [Parameter(Mandatory = $true)] [System.String] $templateReferenceId ) $DSCParams.Remove('Identity') | Out-Null $DSCParams.Remove('DisplayName') | Out-Null $DSCParams.Remove('Description') | Out-Null $settings = @() $templateSettings = Get-MgDeviceManagementConfigurationPolicyTemplateSettingTemplate -DeviceManagementConfigurationPolicyTemplateId $templateReferenceId $simpleSettings = @() $simpleSettings += $templateSettings.SettingInstanceTemplate | Where-Object -FilterScript ` { $_.AdditionalProperties.'@odata.type' -ne '#microsoft.graph.deviceManagementConfigurationGroupSettingCollectionInstanceTemplate' } foreach ($templateSetting in $simpleSettings) { $setting = @{} $settingKey = $DSCParams.keys | Where-Object -FilterScript { $templateSetting.settingDefinitionId -like "*$($_)" } if ((-not [String]::IsNullOrEmpty($settingKey)) -and $DSCParams."$settingKey") { $setting.add('@odata.type', '#microsoft.graph.deviceManagementConfigurationSetting') $myFormattedSetting = Format-M365DSCParamsToSettingInstance -DSCParams @{$settingKey = $DSCParams."$settingKey" } ` -TemplateSetting $templateSetting $setting.add('settingInstance', $myFormattedSetting) $settings += $setting $DSCParams.Remove($settingKey) | Out-Null } } #Prepare attacksurfacereductionrules groupCollectionTemplateSettings $groupCollectionTemplateSettings = @() $groupCollectionTemplateSettings += $templateSettings.SettingInstanceTemplate | Where-Object -FilterScript ` { $_.AdditionalProperties.'@odata.type' -eq '#microsoft.graph.deviceManagementConfigurationGroupSettingCollectionInstanceTemplate' } foreach ($groupCollectionTemplateSetting in $groupCollectionTemplateSettings) { $setting = @{} $setting.add('@odata.type', '#microsoft.graph.deviceManagementConfigurationSetting') $settingInstance = [ordered]@{} $settingInstance.add('@odata.type', '#microsoft.graph.deviceManagementConfigurationGroupSettingCollectionInstance') $settingInstance.add('settingDefinitionId', $groupCollectionTemplateSetting.settingDefinitionId) $settingInstance.add('settingInstanceTemplateReference', @{ '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSettingInstanceTemplateReference' 'settingInstanceTemplateId' = $groupCollectionTemplateSetting.settingInstanceTemplateId }) $groupSettingCollectionValues = @() $groupSettingCollectionValueChildren = @() $groupSettingCollectionValue = @{} $groupSettingCollectionValue.add('@odata.type', '#microsoft.graph.deviceManagementConfigurationGroupSettingValue') $settingValueTemplateId = $groupCollectionTemplateSetting.AdditionalProperties.groupSettingCollectionValueTemplate.settingValueTemplateId if (-Not [string]::IsNullOrEmpty($settingValueTemplateId)) { $groupSettingCollectionValue.add('settingValueTemplateReference', @{'settingValueTemplateId' = $SettingValueTemplateId }) } foreach ($key in $DSCParams.keys) { $templateValue = $groupCollectionTemplateSetting.AdditionalProperties.groupSettingCollectionValueTemplate.children | Where-Object ` -FilterScript { $_.settingDefinitionId -like "*$key" } if ($templateValue) { $groupSettingCollectionValueChild = Format-M365DSCParamsToSettingInstance ` -DSCParams @{$key = $DSCParams."$key" } ` -TemplateSetting $templateValue ` -IncludeSettingValueTemplateId $false ` -IncludeSettingInstanceTemplateId $false $groupSettingCollectionValueChildren += $groupSettingCollectionValueChild } } $groupSettingCollectionValue.add('children', $groupSettingCollectionValueChildren) $groupSettingCollectionValues += $groupSettingCollectionValue $settingInstance.add('groupSettingCollectionValue', $groupSettingCollectionValues) $setting.add('settingInstance', $settingInstance) if ($setting.settingInstance.groupSettingCollectionValue.children.count -gt 0) { $settings += $setting } } return $settings } function ConvertFrom-IntunePolicyAssignment { [CmdletBinding()] [OutputType([System.Collections.Hashtable[]])] param ( [Parameter(Mandatory = $true)] [Array] $Assignments, [Parameter()] [System.Boolean] $IncludeDeviceFilter = $true ) $assignmentResult = @() foreach ($assignment in $Assignments) { $hashAssignment = @{} if ($null -ne $assignment.Target.'@odata.type') { $dataType = $assignment.Target.'@odata.type' } else { $dataType = $assignment.Target.AdditionalProperties.'@odata.type' } if ($null -ne $assignment.Target.groupId) { $groupId = $assignment.Target.groupId } else { $groupId = $assignment.Target.AdditionalProperties.groupId } if ($null -ne $assignment.Target.collectionId) { $collectionId = $assignment.Target.collectionId } else { $collectionId = $assignment.Target.AdditionalProperties.collectionId } $hashAssignment.Add('dataType',$dataType) if (-not [string]::IsNullOrEmpty($groupId)) { $hashAssignment.Add('groupId', $groupId) $group = Get-MgGroup -GroupId ($groupId) -ErrorAction SilentlyContinue if ($null -ne $group) { $groupDisplayName = $group.DisplayName } } if (-not [string]::IsNullOrEmpty($collectionId)) { $hashAssignment.Add('collectionId', $collectionId) } if ($dataType -eq '#microsoft.graph.allLicensedUsersAssignmentTarget') { $groupDisplayName = 'All users' } if ($dataType -eq '#microsoft.graph.allDevicesAssignmentTarget') { $groupDisplayName = 'All devices' } if ($null -ne $groupDisplayName) { $hashAssignment.Add('groupDisplayName', $groupDisplayName) } if ($IncludeDeviceFilter) { if ($null -ne $assignment.Target.DeviceAndAppManagementAssignmentFilterType) { $hashAssignment.Add('deviceAndAppManagementAssignmentFilterType', $assignment.Target.DeviceAndAppManagementAssignmentFilterType.ToString()) } if ($null -ne $assignment.Target.DeviceAndAppManagementAssignmentFilterId) { $hashAssignment.Add('deviceAndAppManagementAssignmentFilterId', $assignment.Target.DeviceAndAppManagementAssignmentFilterId) } } $assignmentResult += $hashAssignment } return ,$assignmentResult } function ConvertTo-IntunePolicyAssignment { [CmdletBinding()] [OutputType([Hashtable[]])] param ( [Parameter(Mandatory = $true)] [AllowNull()] $Assignments, [Parameter()] [System.Boolean] $IncludeDeviceFilter = $true ) if ($null -eq $Assignments) { return ,@() } $assignmentResult = @() foreach ($assignment in $Assignments) { $target = @{"@odata.type" = $assignment.dataType} if ($IncludeDeviceFilter) { if ($null -ne $assignment.DeviceAndAppManagementAssignmentFilterType) { $target.Add('deviceAndAppManagementAssignmentFilterType', $assignment.DeviceAndAppManagementAssignmentFilterType) $target.Add('deviceAndAppManagementAssignmentFilterId', $assignment.DeviceAndAppManagementAssignmentFilterId) } } if ($assignment.dataType -like '*CollectionAssignmentTarget') { $target.add('collectionId', $assignment.collectionId) } elseif ($assignment.dataType -like '*GroupAssignmentTarget') { $group = Get-MgGroup -GroupId ($assignment.groupId) -ErrorAction SilentlyContinue if ($null -eq $group) { if ($assignment.groupDisplayName) { $group = Get-MgGroup -Filter "DisplayName eq '$($assignment.groupDisplayName)'" -ErrorAction SilentlyContinue if ($null -eq $group) { $message = "Skipping assignment for the group with DisplayName {$($assignment.groupDisplayName)} as it could not be found in the directory.`r`n" $message += "Please update your DSC resource extract with the correct groupId or groupDisplayName." Write-Verbose -Message $message $target = $null } if ($group -and $group.Count -gt 1) { $message = "Skipping assignment for the group with DisplayName {$($assignment.groupDisplayName)} as it is not unique in the directory.`r`n" $message += "Please update your DSC resource extract with the correct groupId or a unique group DisplayName." Write-Verbose -Message $message $group = $null $target = $null } } else { $message = "Skipping assignment for the group with Id {$($assignment.groupId)} as it could not be found in the directory.`r`n" $message += "Please update your DSC resource extract with the correct groupId or a unique group DisplayName." Write-Verbose -Message $message $target = $null } } #Skipping assignment if group not found from either groupId or groupDisplayName if ($null -ne $group) { $target.Add('groupId', $group.Id) } } if ($target) { $assignmentResult += @{target = $target} } } return ,$assignmentResult } function Compare-M365DSCIntunePolicyAssignment { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter()] [array]$Source, [Parameter()] [array]$Target ) $testResult = $Source.Count -eq $Target.Count Write-Verbose "Count: $($Source.Count) - $($Target.Count)" if ($testResult) { foreach ($assignment in $Source) { if ($assignment.dataType -like '*AssignmentTarget') { $assignmentTarget = $Target | Where-Object -FilterScript { $_.dataType -eq $assignment.DataType -and $_.groupId -eq $assignment.groupId } $testResult = $null -ne $assignmentTarget # Using assignment groupDisplayName only if the groupId is not found in the directory otherwise groupId should be the key if (-not $testResult) { Write-Verbose 'Group not found by groupId, checking if group exists by id' $groupNotFound = $null -eq (Get-MgGroup -GroupId ($assignment.groupId) -ErrorAction SilentlyContinue) } if (-not $testResult -and $groupNotFound) { Write-Verbose 'Group not found by groupId, looking for group by groupDisplayName' $assignmentTarget = $Target | Where-Object -FilterScript { $_.dataType -eq $assignment.DataType -and $_.groupDisplayName -eq $assignment.groupDisplayName } $testResult = $null -ne $assignmentTarget } if ($testResult) { Write-Verbose 'Group found by groupId or groupDisplayName, checking filters' $isFilterTypeSpecified = ($null -ne $assignment.deviceAndAppManagementAssignmentFilterType -and $assignment.deviceAndAppManagementAssignmentFilterType -ne 'none') -or ` ($null -ne $assignmentTarget.deviceAndAppManagementAssignmentFilterType -and $assignmentTarget.deviceAndAppManagementAssignmentFilterType -ne 'none') $isFilterIdSpecified = ($null -ne $assignment.deviceAndAppManagementAssignmentFilterId -and $assignment.deviceAndAppManagementAssignmentFilterId -ne '00000000-0000-0000-0000-000000000000') -or ` ($null -ne $assignmentTarget.deviceAndAppManagementAssignmentFilterId -and $assignmentTarget.deviceAndAppManagementAssignmentFilterId -ne '00000000-0000-0000-0000-000000000000') if ($isFilterTypeSpecified) { Write-Verbose 'FilterType specified, checking filterType' $testResult = $assignment.deviceAndAppManagementAssignmentFilterType -eq $assignmentTarget.deviceAndAppManagementAssignmentFilterType } if ($testResult -and $isFilterTypeSpecified -and $isFilterIdSpecified) { Write-Verbose 'FilterId specified, checking filterId' $testResult = $assignment.deviceAndAppManagementAssignmentFilterId -eq $assignmentTarget.deviceAndAppManagementAssignmentFilterId } } if ($testResult) { Write-Verbose 'Group and filters match, checking collectionId' $testResult = $assignment.collectionId -eq $assignmentTarget.collectionId } } else { $testResult = $null -ne ($Target | Where-Object -FilterScript { $_.dataType -eq $assignment.DataType }) } if (-not $testResult) { break } } } return $testResult } function Update-DeviceConfigurationPolicyAssignment { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $true)] [System.String] $DeviceConfigurationPolicyId, [Parameter()] [Array] $Targets, [Parameter()] [System.String] $Repository = 'deviceManagement/configurationPolicies', [Parameter()] [ValidateSet('v1.0','beta')] [System.String] $APIVersion = 'beta', [Parameter()] [System.String] $RootIdentifier = 'assignments' ) try { $deviceManagementPolicyAssignments = @() $Uri = "/$APIVersion/$Repository/$DeviceConfigurationPolicyId/assign" foreach ($target in $targets) { if ($target.target -is [hashtable]) { $target = $target.target } $formattedTarget = @{"@odata.type" = $target.dataType} if(-not $formattedTarget."@odata.type" -and $target."@odata.type") { $formattedTarget."@odata.type" = $target."@odata.type" } if ($target.groupId) { $group = Get-MgGroup -GroupId ($target.groupId) -ErrorAction SilentlyContinue if ($null -eq $group) { if ($target.groupDisplayName) { $group = Get-MgGroup -Filter "DisplayName eq '$($target.groupDisplayName)'" -ErrorAction SilentlyContinue if ($null -eq $group) { $message = "Skipping assignment for the group with DisplayName {$($target.groupDisplayName)} as it could not be found in the directory.`r`n" $message += "Please update your DSC resource extract with the correct groupId or groupDisplayName." write-verbose -Message $message $target = $null } if ($group -and $group.count -gt 1) { $message = "Skipping assignment for the group with DisplayName {$($target.groupDisplayName)} as it is not unique in the directory.`r`n" $message += "Please update your DSC resource extract with the correct groupId or a unique group DisplayName." write-verbose -Message $message $group = $null $target = $null } } else { $message = "Skipping assignment for the group with Id {$($target.groupId)} as it could not be found in the directory.`r`n" $message += "Please update your DSC resource extract with the correct groupId or a unique group DisplayName." write-verbose -Message $message $target = $null } } #Skipping assignment if group not found from either groupId or groupDisplayName if ($null -ne $group) { $formattedTarget.add('groupId',$group.Id) } } if ($target.collectionId) { $formattedTarget.Add('collectionId',$target.collectionId) } if ($target.deviceAndAppManagementAssignmentFilterType) { $formattedTarget.Add('deviceAndAppManagementAssignmentFilterType',$target.deviceAndAppManagementAssignmentFilterType) } if ($target.deviceAndAppManagementAssignmentFilterId) { $formattedTarget.Add('deviceAndAppManagementAssignmentFilterId',$target.deviceAndAppManagementAssignmentFilterId) } $deviceManagementPolicyAssignments += @{'target' = $formattedTarget} } $body = @{$RootIdentifier = $deviceManagementPolicyAssignments} | ConvertTo-Json -Depth 20 Write-Verbose -Message $body Invoke-MgGraphRequest -Method POST -Uri $Uri -Body $body -ErrorAction Stop } catch { New-M365DSCLogEntry -Message 'Error updating data:' ` -Exception $_ ` -Source $($MyInvocation.MyCommand.Source) ` -TenantId $TenantId ` -Credential $Credential return $null } } function Get-OmaSettingPlainTextValue { [CmdletBinding()] [OutputType([System.String])] Param( [Parameter(Mandatory = $true)] [System.String] $SecretReferenceValueId, [Parameter()] [ValidateSet('v1.0', 'beta')] [System.String] $APIVersion = 'beta' ) try { <# e.g. PolicyId for SecretReferenceValueId '35ea58ec-2a79-471d-8eea-7e28e6cd2722_bdf6c690-05fb-4d02-835d-5a7406c35d58_abe32712-2255-445f-a35e-0c6f143d82ca' is 'bdf6c690-05fb-4d02-835d-5a7406c35d58' #> $SplitSecretReferenceValueId = $SecretReferenceValueId.Split("_") if ($SplitSecretReferenceValueId.Count -eq 3) { $PolicyId = $SplitSecretReferenceValueId[1] } else { return $null } } catch { return $null } $Repository = 'deviceManagement/deviceConfigurations' $Uri = "/{0}/{1}/{2}/getOmaSettingPlainTextValue(secretReferenceValueId='{3}')" -f $APIVersion, $Repository, $PolicyId, $SecretReferenceValueId try { $Result = Invoke-MgGraphRequest -Method GET -Uri $Uri -ErrorAction Stop } catch { $Message = "Error decrypting OmaSetting with SecretReferenceValueId {0}:" -f $SecretReferenceValueId New-M365DSCLogEntry -Message $Message ` -Exception $_ ` -Source $($MyInvocation.MyCommand.Source) ` -TenantId $TenantId ` -Credential $Credential return $null } if (![String]::IsNullOrEmpty($Result.Value)) { return $Result.Value } else { return $null } } function Get-IntuneSettingCatalogPolicySetting { [CmdletBinding()] [OutputType([System.Array])] param( [Parameter(Mandatory = 'true')] [System.Collections.Hashtable] $DSCParams, [Parameter(Mandatory = 'true')] [System.String] $TemplateId ) $global:excludedDefinitionIds = @() $DSCParams.Remove('Identity') | Out-Null $DSCParams.Remove('DisplayName') | Out-Null $DSCParams.Remove('Description') | Out-Null # Prepare setting definitions mapping $settingTemplates = Get-MgBetaDeviceManagementConfigurationPolicyTemplateSettingTemplate ` -DeviceManagementConfigurationPolicyTemplateId $TemplateId ` -ExpandProperty 'SettingDefinitions' ` -All $settingInstances = @() # Iterate over all setting instance templates in the setting template foreach ($settingInstanceTemplate in $settingTemplates.SettingInstanceTemplate) { $settingInstance = @{} $settingDefinition = $settingTemplates.SettingDefinitions | Where-Object { $_.Id -eq $settingInstanceTemplate.SettingDefinitionId -and ` ($_.AdditionalProperties.dependentOn.Count -eq 0 -and $_.AdditionalProperties.options.dependentOn.Count -eq 0) } if ($null -eq $settingDefinition) { continue } if ($settingDefinition -is [System.Array]) { $settingDefinition = $settingDefinition[0] } $settingName = $settingDefinition.Name $settingType = $settingInstanceTemplate.AdditionalProperties.'@odata.type'.Replace('InstanceTemplate', 'Instance') $settingInstance.Add('@odata.type', $settingType) if (-not [string]::IsNullOrEmpty($settingInstanceTemplate.settingInstanceTemplateId)) { $settingInstance.Add('settingInstanceTemplateReference', @{'settingInstanceTemplateId' = $settingInstanceTemplate.settingInstanceTemplateId }) } $settingValueName = $settingType.Replace('#microsoft.graph.deviceManagementConfiguration', '').Replace('Instance', 'Value') $settingValueName = $settingValueName.Substring(0, 1).ToLower() + $settingValueName.Substring(1, $settingValueName.length - 1 ) $settingValueType = $settingInstanceTemplate.AdditionalProperties."$($settingValueName)Template".'@odata.type' if ($null -ne $settingValueType) { $settingValueType = $settingValueType.Replace('ValueTemplate', 'Value') } $settingValueTemplateId = $settingInstanceTemplate.AdditionalProperties."$($settingValueName)Template".settingValueTemplateId # Get all the values in the setting instance $settingValue = Get-IntuneSettingCatalogPolicySettingInstanceValue ` -DSCParams $DSCParams ` -SettingDefinition $settingDefinition ` -SettingTemplates $settingTemplates ` -SettingName $settingName ` -SettingType $settingType ` -SettingValueName $settingValueName ` -SettingValueType $settingValueType ` -SettingValueTemplateId $settingValueTemplateId if ($settingValue.Count -gt 0) { if ($settingValue.Keys -contains 'groupSettingCollectionValue' -and $settingValue.groupSettingCollectionValue.children.Count -eq 0) { continue } $settingInstance += [Hashtable]$settingValue if ($settingInstance.Keys -notcontains 'settingDefinitionId') { $settingInstance.Add('settingDefinitionId', $settingInstanceTemplate.settingDefinitionId) } $settingInstances += @{ '@odata.type' = '#microsoft.graph.deviceManagementConfigurationSetting' 'settingInstance' = $settingInstance } } } return ,$settingInstances } function Get-IntuneSettingCatalogPolicySettingInstanceValue { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param( [Parameter(Mandatory = 'true')] [System.Collections.Hashtable] $DSCParams, [Parameter()] $SettingDefinition, [Parameter()] $SettingTemplates, [Parameter()] [System.String] $SettingType, [Parameter()] [System.String] $SettingName, [Parameter()] [System.String] $SettingValueName, [Parameter()] [System.String] $SettingValueType, [Parameter()] [System.String] $SettingValueTemplateId, [Parameter()] [System.String] $SettingInstanceName = 'MSFT_MicrosoftGraphIntuneSettingsCatalog', [Parameter()] [System.Int32] $Level = 1 ) $settingValuesToReturn = @{} if ($null -eq $global:excludedDefinitionIds) { $global:excludedDefinitionIds = @() } if ($null -eq $global:excludedDscParams) { $global:excludedDscParams = @() } # Depending on the setting type, there is other logic involved switch ($SettingType) { # GroupSettingCollections are a collection of settings without a value of their own { $_ -eq '#microsoft.graph.deviceManagementConfigurationGroupSettingCollectionInstance' -or $_ -eq '#microsoft.graph.deviceManagementConfigurationSettingGroupCollectionDefinition' } { $groupSettingCollectionValue = @() $groupSettingCollectionDefinitionChildren = @() $templates = $SettingTemplates | Where-Object { $_.settingInstanceTemplate.settingDefinitionId -eq $SettingDefinition.RootDefinitionId } $groupSettingCollectionDefinitionChildren += $templates.SettingDefinitions | Where-Object { ($_.AdditionalProperties.dependentOn.Count -gt 0 -and $_.AdditionalProperties.dependentOn.parentSettingId -contains $SettingDefinition.Id) -or ($_.AdditionalProperties.options.dependentOn.Count -gt 0 -and $_.AdditionalProperties.options.dependentOn.parentSettingId -contains $SettingDefinition.Id) } $instanceCount = 1 if ($Level -ge 2 -and $groupSettingCollectionDefinitionChildren.Count -gt 1) { $SettingInstanceName += $SettingDefinition.Name $cimDSCParams = @() $cimDSCParamsName = "" $DSCParams.GetEnumerator() | Where-Object -FilterScript { $_.Value.CimClass.CimClassName -contains $SettingInstanceName } | Foreach-Object -Process { $cimDSCParams += $_.Value $cimDSCParamsName = $_.Key } $newDSCParams = @{ $cimDSCParamsName = @() } foreach ($instance in $cimDSCParams) { $newInstanceDSCParams = @{} # Preserve CIM instances when converting to hashtable foreach ($property in $instance.CimInstanceProperties) { $newInstanceDSCParams.Add($property.Name, $property.Value) } $newDSCParams.$cimDSCParamsName += $newInstanceDSCParams } $instanceCount = $newDSCParams.$cimDSCParamsName.Count $DSCParams = @{ $cimDSCParamsName = if ($instanceCount -eq 1) { $newDSCParams.$cimDSCParamsName[0] } else { $newDSCParams.$cimDSCParamsName } } } for ($i = 0; $i -lt $instanceCount; $i++) { $groupSettingCollectionValueChildren = @() $currentDSCParams = if ($instanceCount -eq 1) { if (-not [System.String]::IsNullOrEmpty($cimDSCParamsName)) { $DSCParams.$cimDSCParamsName } else { $DSCParams } } else { if (-not [System.String]::IsNullOrEmpty($cimDSCParamsName)) { $DSCParams.$cimDSCParamsName[$i] } else { $DSCParams[$i] } } if ($null -eq $currentDSCParams) { # Should we continue? Let's try out $currentDSCParams = @{} } foreach ($childDefinition in $groupSettingCollectionDefinitionChildren) { $childSettingName = $childDefinition.Name $childSettingType = $childDefinition.AdditionalProperties.'@odata.type'.Replace('Definition', 'Instance').Replace('SettingGroup', 'GroupSetting') $childSettingValueName = $childSettingType.Replace('#microsoft.graph.deviceManagementConfiguration', '').Replace('Instance', 'Value') $childSettingValueType = "#microsoft.graph.deviceManagementConfiguration$($childSettingValueName)" $childSettingValueName = $childSettingValueName.Substring(0, 1).ToLower() + $childSettingValueName.Substring(1, $childSettingValueName.length - 1 ) $childSettingInstanceTemplate = $SettingTemplates.SettingInstanceTemplate | Where-Object { $_.SettingDefinitionId -eq $childDefinition.Id } $childSettingValueTemplateId = $childSettingInstanceTemplate.AdditionalProperties."$($childSettingValueName)Template".settingValueTemplateId $childSettingValue = Get-IntuneSettingCatalogPolicySettingInstanceValue ` -DSCParams $currentDSCParams ` -SettingDefinition $childDefinition ` -SettingTemplates $SettingTemplates ` -SettingName $childSettingName ` -SettingType $childDefinition.AdditionalProperties.'@odata.type' ` -SettingValueName $childSettingValueName ` -SettingValueType $childSettingValueType ` -SettingValueTemplateId $childSettingValueTemplateId ` -SettingInstanceName $SettingInstanceName ` -Level ($Level + 1) if ($childSettingValue.Keys.Count -gt 0) { if ($childSettingValue.Keys -notcontains 'settingDefinitionId') { $childSettingValue.Add('settingDefinitionId', $childDefinition.Id) } if (-not [string]::IsNullOrEmpty($childSettingInstanceTemplate.settingInstanceTemplateId)) { $childSettingValue.Add('settingInstanceTemplateReference', @{'settingInstanceTemplateId' = $childSettingInstanceTemplate.settingInstanceTemplateId }) } $childSettingValue.Add('@odata.type', $childSettingType) $groupSettingCollectionValueChildren += $childSettingValue } } if ($groupSettingCollectionValueChildren.Count -gt 0) { $groupSettingCollectionValue += @{ children = @($groupSettingCollectionValueChildren) } } } if ($groupSettingCollectionDefinitionChildren.Count -gt 0 -and $groupSettingCollectionValue.Count -gt 0) { $settingValuesToReturn.Add('groupSettingCollectionValue', @($groupSettingCollectionValue)) } } # ChoiceSetting is a choice (e.g. dropdown) setting that, depending on the choice, can have children settings { $_ -eq '#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance' -or $_ -eq '#microsoft.graph.deviceManagementConfigurationChoiceSettingDefinition' } { $choiceSettingValue = @{} $choiceSettingValueChildren = @() $choiceSettingDefinitionChildren = @() # Choice settings almost always have child settings, so we need to fetch those if ($null -ne $SettingDefinition) { $templates = $SettingTemplates | Where-Object { $_.settingInstanceTemplate.settingDefinitionId -eq $SettingDefinition.RootDefinitionId } $choiceSettingDefinitionChildren += $templates.SettingDefinitions | Where-Object { ($_.AdditionalProperties.dependentOn.Count -gt 0 -and $_.AdditionalProperties.dependentOn.parentSettingId.Contains($SettingDefinition.Id)) -or ($_.AdditionalProperties.options.dependentOn.Count -gt 0 -and $_.AdditionalProperties.options.dependentOn.parentSettingId.Contains($SettingDefinition.Id)) } } foreach ($childDefinition in $choiceSettingDefinitionChildren) { $childSettingName = $childDefinition.Name $childSettingType = $childDefinition.AdditionalProperties.'@odata.type'.Replace('Definition', 'Instance') $childSettingValueName = $childSettingType.Replace('#microsoft.graph.deviceManagementConfiguration', '').Replace('Instance', 'Value') $childSettingValueType = "#microsoft.graph.deviceManagementConfiguration$($childSettingValueName)" $childSettingValueName = $childSettingValueName.Substring(0, 1).ToLower() + $childSettingValueName.Substring(1, $childSettingValueName.Length - 1 ) $childSettingInstanceTemplate = $SettingTemplates.SettingInstanceTemplate | Where-Object { $_.SettingDefinitionId -eq $childDefinition.Id } $childSettingValueTemplateId = $childSettingInstanceTemplate.AdditionalProperties."$($childSettingValueName)Template".settingValueTemplateId $childSettingValue = Get-IntuneSettingCatalogPolicySettingInstanceValue ` -DSCParams $DSCParams ` -SettingDefinition $childDefinition ` -SettingTemplates $SettingTemplates ` -SettingName $childSettingName ` -SettingType $childDefinition.AdditionalProperties.'@odata.type' ` -SettingValueName $childSettingValueName ` -SettingValueType $childSettingValueType ` -SettingValueTemplateId $childSettingValueTemplateId ` -SettingInstanceName $SettingInstanceName if ($childSettingValue.Keys.Count -gt 0) { if ($childSettingValue.Keys -notcontains 'settingDefinitionId') { $childSettingValue.Add('settingDefinitionId', $childDefinition.Id) } if (-not [string]::IsNullOrEmpty($childSettingInstanceTemplate.settingInstanceTemplateId)) { $childSettingValue.Add('settingInstanceTemplateReference', @{'settingInstanceTemplateId' = $childSettingInstanceTemplate.settingInstanceTemplateId }) } $childSettingValue.Add('@odata.type', $childSettingType) $choiceSettingValueChildren += $childSettingValue } } # Depending on the children count, we add the children to the choice setting or an empty array since the children property is required if ($choiceSettingDefinitionChildren.Count -gt 0) { $choiceSettingValue.Add('children', $choiceSettingValueChildren) } else { $choiceSettingValue.Add('children', @()) } $valueResult = Get-IntuneSettingCatalogPolicySettingDSCValue ` -SettingName $SettingName ` -SettingValueType $SettingValueType ` -SettingTemplates $SettingTemplates ` -SettingDefinition $SettingDefinition ` -DSCParams $DSCParams $value = $valueResult.Value # If there is a value in the DSC params, we add that to the choice setting if ($null -ne $value) { $value = $value $choiceSettingValue.Add('value', $value) $odataType = $SettingType.Replace('Definition', 'Value').Replace('Instance', 'Value') $choiceSettingValue.Add('@odata.type', $odataType) if (-not [string]::IsNullOrEmpty($SettingValueTemplateId)) { $choiceSettingValue.Add('settingValueTemplateReference', @{'settingValueTemplateId' = $SettingValueTemplateId }) } } # If there are children or a value is configured, we add the choice setting to the return values if ($choiceSettingValue.Children.Count -gt 0 -or $null -ne $choiceSettingValue.value) { $settingValuesToReturn.Add('choiceSettingValue', $choiceSettingValue) } } # ChoiceSettingCollection is a collection of ChoiceSettings { $_ -eq '#microsoft.graph.deviceManagementConfigurationChoiceSettingCollectionInstance' -or $_ -eq '#microsoft.graph.deviceManagementConfigurationChoiceSettingCollectionDefinition' } { $choiceSettingValueCollection = @() $valueResult = Get-IntuneSettingCatalogPolicySettingDSCValue ` -SettingName $SettingName ` -SettingValueType $SettingValueType ` -SettingTemplates $SettingTemplates ` -SettingDefinition $SettingDefinition ` -DSCParams $DSCParams $values = $valueResult.Value if ($null -ne $values) { # We iterate over all the values in the DSC params and add them to the choice setting collection foreach ($value in $values) { $choiceSettingValueCollection += @{ value = $value children = @() '@odata.type' = '#microsoft.graph.deviceManagementConfigurationChoiceSettingValue' } } $settingValuesToReturn.Add('choiceSettingCollectionValue', $choiceSettingValueCollection) } } # SimpleSettingCollections are collections of simple settings, e.g. strings or integers { $_ -eq '#microsoft.graph.deviceManagementConfigurationSimpleSettingCollectionInstance' -or $_ -eq '#microsoft.graph.deviceManagementConfigurationSimpleSettingCollectionDefinition' } { $valuesResult = Get-IntuneSettingCatalogPolicySettingDSCValue ` -SettingName $SettingName ` -SettingValueType $SettingValueType ` -SettingTemplates $SettingTemplates ` -SettingDefinition $SettingDefinition ` -DSCParams $DSCParams if ($null -eq $valuesResult) { return $null } $values = $valuesResult.Value $SettingValueType = $valuesResult.SettingDefinition.AdditionalProperties.valueDefinition.'@odata.type'.Replace('Definition', '') $settingValueCollection = @() foreach ($v in $values) { $settingValueCollection += @{ value = $v '@odata.type' = $SettingValueType } } if ($settingValueCollection.Count -gt 0) { $settingValuesToReturn.Add($SettingValueName, $settingValueCollection) } } # For all other types, e.g. Integer or String, we add the value directly Default { $valueResult = Get-IntuneSettingCatalogPolicySettingDSCValue ` -SettingName $SettingName ` -SettingValueType $SettingValueType ` -SettingTemplates $SettingTemplates ` -SettingDefinition $SettingDefinition ` -DSCParams $DSCParams $value = $valueResult.Value $SettingValueType = $valueResult.SettingValueType $SettingDefinition = $valueResult.SettingDefinition if ($null -eq $value) { return $null } $settingValue = @{} if (-not [string]::IsNullOrEmpty($SettingValueType)) { $settingValue.Add('@odata.type', $SettingValueType) } if (-not [string]::IsNullOrEmpty($settingValueTemplateId)) { $settingValue.Add('settingValueTemplateReference', @{'settingValueTemplateId' = $settingValueTemplateId }) } $settingValue.Add('value', $value) $settingValuesToReturn.Add($SettingValueName, $settingValue) $settingValuesToReturn.Add('settingDefinitionId', $SettingDefinition.Id) } } return $settingValuesToReturn } function Get-IntuneSettingCatalogPolicySettingDSCValue { param ( [Parameter(Mandatory = $true)] [System.String] $SettingName, [Parameter()] [System.String] $SettingValueType = "", [Parameter(Mandatory = $true)] [System.Array] $SettingTemplates, [Parameter()] $SettingDefinition, [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $DSCParams ) # Go over all the values that have not yet been processed foreach ($key in ($DSCParams.Keys | Where-Object { $_ -notin $global:excludedDscParams })) { $matchCombined = $false $matchesId = $false $matchesOffsetUri = $false $settingDefinitions = $SettingTemplates.SettingDefinitions ` | Where-Object -FilterScript { $_.Name -eq $key } # Edge case where the same setting is defined twice in the template, with the same name and id if ($settingDefinitions.Count -eq 2) { if ($settingDefinitions[0].Id -eq $settingDefinitions[1].Id -and ` $settingDefinitions[0].Name -eq $settingDefinitions[1].Name) { $settingDefinitions = $settingDefinitions[0] } } $name = $settingDefinitions.Name if ($name.Count -ne 1) { # Key might have been combined with parent setting, try to split it if ($key -like "*_*") { $parentSettingName = $key.Split('_')[0] $parentDefinition = $SettingTemplates.SettingDefinitions | Where-Object -FilterScript { $_.Name -eq $parentSettingName } # If no parent definition is found, it might have been combined with the OffsetUri if ($null -eq $parentDefinition) { $definition = Get-SettingDefinitionFromNameWithParentFromOffsetUri -OffsetUriName $key -SettingDefinitions $SettingTemplates.SettingDefinitions if ($null -ne $definition) { $offsetUriFound = $true if ($SettingDefinition.Id -eq $definition.Id) { $matchesOffsetUri = $true } } } $childDefinition = $SettingTemplates.SettingDefinitions | Where-Object -FilterScript { $_.Name -eq $SettingName -and (($_.AdditionalProperties.dependentOn.Count -gt 0 -and $_.AdditionalProperties.dependentOn.parentSettingId -contains $parentDefinition.Id) -or ($_.AdditionalProperties.options.dependentOn.Count -gt 0 -and $_.AdditionalProperties.options.dependentOn.parentSettingId -contains $parentDefinition.Id) ) } if ($null -ne $parentDefinition -and $null -ne $childDefinition -and $childDefinition.Id -eq $SettingDefinition.Id) { # Parent was combined with child setting. Since there can be multiple settings with the same Name, we need to check the Id as well if ($SettingDefinition.Id -eq $childDefinition.Id) { # Only exclude the combined setting if it is not part of a group setting collection (which could be of a separate CIM type) if ($parentDefinition.AdditionalProperties.'@odata.type' -ne '#microsoft.graph.deviceManagementConfigurationSettingGroupCollectionDefinition') { $global:excludedDscParams += $key } $matchCombined = $true } } } if (-not $matchCombined -and -not $offsetUriFound) { # Parent was not combined, look for the combination of name and id $SettingTemplates.SettingDefinitions | ForEach-Object { if ($_.Id -notin $global:excludedDefinitionIds -and $_.Name -eq $SettingName -and $_.Id -like "*$key") { $global:excludedDefinitionIds += $_.Id $global:excludedDscParams += $key $matchesId = $true $SettingDefinition = $_ } } } } # If there is exactly one setting with the name, the setting is combined or the id matches, we get the DSC value and update the real setting value type if (($name.Count -eq 1 -and $SettingName -eq $key) -or $matchCombined -or $matchesId -or $matchesOffsetUri) { $isArray = $false if ($SettingValueType -like "*Simple*") { if ($DSCParams[$key] -is [System.String]) { $SettingValueType = "#microsoft.graph.deviceManagementConfigurationStringSettingValue" } elseif ($DSCParams[$key] -is [System.Int32]) { $SettingValueType = "#microsoft.graph.deviceManagementConfigurationIntegerSettingValue" } elseif ($DSCParams[$key] -is [System.String[]]) { $SettingValueType = "#microsoft.graph.deviceManagementConfigurationStringSettingValue" $isArray = $true } elseif ($DSCParams[$key] -is [System.Int32[]]) { $SettingValueType = "#microsoft.graph.deviceManagementConfigurationIntegerSettingValue" $isArray = $true } } if ($SettingValueType -like "*Simple*" -or $SettingValueType -in @("#microsoft.graph.deviceManagementConfigurationIntegerSettingValue", "#microsoft.graph.deviceManagementConfigurationStringSettingValue")) { return @{ SettingDefinition = $SettingDefinition SettingValueType = $SettingValueType Value = if ($isArray) { ,$DSCParams[$key] } else { $DSCParams[$key] } } } elseif ($SettingValueType -like "*ChoiceSettingCollection*") { $values = @() foreach ($value in $DSCParams[$key]) { $values += "$($SettingDefinition.Id)_$value" } return @{ Value = $values } } else { return @{ SettingDefinition = $SettingDefinition SettingValueType = $SettingValueType Value = "$($SettingDefinition.Id)_$($DSCParams[$key])" } } break } } } function Get-SettingDefinitionFromNameWithParentFromOffsetUri { param( [Parameter(Mandatory = $true)] [System.String] $OffsetUriName, [Parameter(Mandatory = $true)] [System.Array] $SettingDefinitions ) $offsetUriParts = [System.Collections.ArrayList]::new() $SettingDefinitions | ForEach-Object { $splittedOffsetUri = $_.OffsetUri.Split('/') # Remove first element since it is always empty $splittedOffsetUri = $splittedOffsetUri[1..($splittedOffsetUri.Length - 1)] foreach ($part in $splittedOffsetUri) { if (-not $offsetUriParts.Contains($part)) { $offsetUriParts.Add($part) | Out-Null } } } $settingName = $OffsetUriName $offsetUriPrefix = "" foreach ($part in $offsetUriParts) { if ($settingName -like "*$($part)_*") { $settingName = $settingName.Replace("$($part)_", "") # Add wildcards to match removed parts with invalid characters $offsetUriPrefix += "*$($part)*" } } $filteredDefinitions = $SettingDefinitions | Where-Object -FilterScript { $_.Name -eq $settingName -and $_.OffsetUri -like "*$offsetUriPrefix*" } if ($filteredDefinitions.Count -eq 1) { return $filteredDefinitions } else { $settingsWithSameName = $filteredDefinitions foreach ($definition in $filteredDefinitions) { $skip = 0 $newSettingName = $settingName do { $previousSettingName = $newSettingName $newSettingName = Get-SettingDefinitionNameWithParentFromOffsetUri -OffsetUri $definition.OffsetUri -SettingName $newSettingName -Skip $skip $combinationMatchesWithOffsetUri = @() $settingsWithSameName | ForEach-Object { $newName = Get-SettingDefinitionNameWithParentFromOffsetUri -OffsetUri $_.OffsetUri -SettingName $previousSettingName -Skip $skip if ($newName -eq $newSettingName) { $combinationMatchesWithOffsetUri += $_ } } $settingsWithSameName = $combinationMatchesWithOffsetUri $skip++ } while ($combinationMatchesWithOffsetUri.Count -gt 1) if ($newSettingName -eq $OffsetUriName) { return $definition } } } } function Get-ParentSettingDefinition { param( [Parameter(Mandatory = $true)] $SettingDefinition, [Parameter(Mandatory = $true)] $AllSettingDefinitions ) $parentSetting = $null if ($SettingDefinition.AdditionalProperties.dependentOn.parentSettingId.Count -gt 0) { $parentSetting = $AllSettingDefinitions | Where-Object -FilterScript { $_.Id -eq ($SettingDefinition.AdditionalProperties.dependentOn.parentSettingId | Select-Object -Unique -First 1) } } elseif ($SettingDefinition.AdditionalProperties.options.dependentOn.parentSettingId.Count -gt 0) { $parentSetting = $AllSettingDefinitions | Where-Object -FilterScript { $_.Id -eq ($SettingDefinition.AdditionalProperties.options.dependentOn.parentSettingId | Select-Object -Unique -First 1) } } $parentSetting } <# This function also exists in M365DSCResourceGenerator.psm1. Changes here must be added there as well for compatibility. #> function Get-SettingDefinitionNameWithParentFromOffsetUri { param ( [Parameter(Mandatory = $true)] [System.String] $OffsetUri, [Parameter(Mandatory = $true)] [System.String] $SettingName, [Parameter(Mandatory = $false)] [System.Int32] $Skip = 0 ) # If the last part of the OffsetUri is the same as the setting name or it contains invalid characters, we traverse up until we reach the first element # Invalid characters are { and } which are used in the OffsetUri to indicate a variable $splittedOffsetUri = $OffsetUri.Split("/") if ([string]::IsNullOrEmpty($splittedOffsetUri[0])) { $splittedOffsetUri = $splittedOffsetUri[1..($splittedOffsetUri.Length - 1)] } $splittedOffsetUri = $splittedOffsetUri[0..($splittedOffsetUri.Length - 1 - $Skip)] $traversed = $false while (-not $traversed -and $splittedOffsetUri.Length -gt 1) # Prevent adding the first element of the OffsetUri { $traversed = $true if ($splittedOffsetUri[-1] -eq $SettingName -or $splittedOffsetUri[-1] -match "[\{\}]" -or $SettingName.StartsWith($splittedOffsetUri[-1])) { $splittedOffsetUri = $splittedOffsetUri[0..($splittedOffsetUri.Length - 2)] $traversed = $false } } if ($splittedOffsetUri.Length -gt 1) { $splittedOffsetUri[-1] + "_" + $SettingName } else { $SettingName } } function Export-IntuneSettingCatalogPolicySettings { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param( [Parameter( Mandatory = $true, ParameterSetName = 'Start' )] $Settings, [Parameter( Mandatory = $true )] [System.Collections.Hashtable]$ReturnHashtable, [Parameter( Mandatory = $true, ParameterSetName = 'Setting' )] $SettingInstance, [Parameter( Mandatory = $true, ParameterSetName = 'Setting' )] $SettingDefinitions, [Parameter( Mandatory = $true, ParameterSetName = 'Setting' )] [System.Array] $AllSettingDefinitions, [Parameter( ParameterSetName = 'Setting' )] [switch]$IsRoot ) if ($PSCmdlet.ParameterSetName -eq 'Start') { foreach ($setting in $Settings) { Export-IntuneSettingCatalogPolicySettings -SettingInstance $setting.SettingInstance -SettingDefinitions $setting.SettingDefinitions -ReturnHashtable $ReturnHashtable -AllSettingDefinitions $Settings.SettingDefinitions -IsRoot } return $ReturnHashtable } $addToParameters = $true $settingDefinition = $SettingDefinitions | Where-Object -FilterScript { $_.Id -eq $SettingInstance.settingDefinitionId } $settingName = $settingDefinition.Name # Check if the name is unique $settingsWithSameName = @($AllSettingDefinitions | Where-Object -FilterScript { $_.Name -eq $settingName }) if ($settingsWithSameName.Count -gt 1) { $parentSetting = Get-ParentSettingDefinition -SettingDefinition $settingDefinition -AllSettingDefinitions $AllSettingDefinitions if ($null -ne $parentSetting) { $combinationMatchesWithParent = $settingsWithSameName | Where-Object -FilterScript { "$($parentSetting.Name)_$($_.Name)" -eq "$($parentSetting.Name)_$settingName" } # If the combination of parent setting and setting name is unique, add the parent setting name to the setting name if ($combinationMatchesWithParent.Count -eq 1) { $settingName = $($parentSetting.Name) + "_" + $settingName } # If the combination of parent setting and setting name is still not unique, do it with the OffsetUri of the current setting else { $skip = 0 $breakCounter = 0 $newSettingName = $settingName do { $previousSettingName = $newSettingName $newSettingName = Get-SettingDefinitionNameWithParentFromOffsetUri -OffsetUri $settingDefinition.OffsetUri -SettingName $newSettingName -Skip $skip $combinationMatchesWithOffsetUri = @() $settingsWithSameName | ForEach-Object { $newName = Get-SettingDefinitionNameWithParentFromOffsetUri -OffsetUri $_.OffsetUri -SettingName $previousSettingName -Skip $skip if ($newName -eq $newSettingName) { $combinationMatchesWithOffsetUri += $_ } } $settingsWithSameName = $combinationMatchesWithOffsetUri $skip++ $breakCounter++ } while ($combinationMatchesWithOffsetUri.Count -gt 1 -and $breakCounter -lt 8) if ($breakCounter -lt 8) { $settingName = $newSettingName } else { # Alternative way if no unique setting name can be found $parentSettingIdProperty = $parentSetting.Id.Split('_')[-1] $parentSettingIdWithoutProperty = $parentSetting.Id.Replace("_$parentSettingIdProperty", "") # We can't use the entire setting here, because the child setting id does not have to come after the parent setting id $settingName = $settingDefinition.Id.Replace($parentSettingIdWithoutProperty + "_", "").Replace($parentSettingIdProperty + "_", "") } } } # When there is no parent, we can't use the parent setting name to make the setting name unique # Instead, we traverse up the OffsetUri. Since no parent setting can only happen at the root level, the result # of Get-SettingDefinitionNameWithParentFromOffsetUri is absolute and cannot change. There cannot be multiple settings with the same name # in the same level of OffsetUri if ($null -eq $parentSetting) { $settingName = Get-SettingDefinitionNameWithParentFromOffsetUri -OffsetUri $settingDefinition.OffsetUri -SettingName $settingName } } $odataType = if ($IsRoot) { $SettingInstance.AdditionalProperties.'@odata.type' } else { $SettingInstance.'@odata.type' } switch ($odataType) { '#microsoft.graph.deviceManagementConfigurationSimpleSettingInstance' { $settingValue = if ($IsRoot) { $SettingInstance.AdditionalProperties.simpleSettingValue.value } else { $SettingInstance.simpleSettingValue.value } } '#microsoft.graph.deviceManagementConfigurationChoiceSettingInstance' { $settingValue = if ($IsRoot) { $SettingInstance.AdditionalProperties.choiceSettingValue.value } else { $SettingInstance.choiceSettingValue.value } $settingValue = $settingValue.Split('_') | Select-Object -Last 1 $childSettings = if ($IsRoot) { $SettingInstance.AdditionalProperties.choiceSettingValue.children } else { $SettingInstance.choiceSettingValue.children } foreach ($childSetting in $childSettings) { Export-IntuneSettingCatalogPolicySettings -SettingInstance $childSetting -SettingDefinitions $SettingDefinitions -ReturnHashtable $ReturnHashtable -AllSettingDefinitions $AllSettingDefinitions } } '#microsoft.graph.deviceManagementConfigurationChoiceSettingCollectionInstance' { $values = @() $childValues = if ($IsRoot) { $SettingInstance.AdditionalProperties.choiceSettingCollectionValue.value } else { $SettingInstance.choiceSettingCollectionValue.value } foreach ($value in $childValues) { $values += $value.Split('_') | Select-Object -Last 1 } $settingValue = $values } '#microsoft.graph.deviceManagementConfigurationGroupSettingCollectionInstance' { $groupSettingCollectionValue = if ($IsRoot) { $SettingInstance.AdditionalProperties.groupSettingCollectionValue } else { $SettingInstance.groupSettingCollectionValue } $childSettingDefinitions = $SettingDefinitions | Where-Object -FilterScript { $settingDefinition.AdditionalProperties.childIds -contains $_.Id } if ($settingDefinition.AdditionalProperties.maximumCount -gt 1 -and $childSettingDefinitions.Count -eq 1) { # Skip GroupSettingCollection with only one child, go straight to the child property foreach ($child in $groupSettingCollectionValue) { $childInstances = $child.children foreach ($childInstance in $childInstances) { Export-IntuneSettingCatalogPolicySettings -SettingInstance $childInstance -SettingDefinitions $SettingDefinitions -ReturnHashtable $ReturnHashtable -AllSettingDefinitions $AllSettingDefinitions } } $addToParameters = $false } elseif (-not $IsRoot -and $childSettingDefinitions.Count -gt 1) { $childValue = $null $parentSettingDefinition = $SettingDefinitions | Where-Object -FilterScript { $_.Id -eq $settingDefinition.AdditionalProperties.dependentOn.parentSettingId } if ($settingDefinition.AdditionalProperties.maximumCount -gt 1 -or $parentSettingDefinition.AdditionalProperties.maximumCount -gt 1) { $childValue = @() } foreach ($child in $groupSettingCollectionValue) { $childHashtable = @{} foreach ($childInstance in $child.children) { Export-IntuneSettingCatalogPolicySettings -SettingInstance $childInstance -SettingDefinitions $SettingDefinitions -ReturnHashtable $childHashtable -AllSettingDefinitions $AllSettingDefinitions } $childValue += $childHashtable } $settingValue = if ($null -eq $childValue) { $childHashtable } else { ,$childValue } } else { $childSettings = $groupSettingCollectionValue.children foreach ($value in $childSettings) { Export-IntuneSettingCatalogPolicySettings -SettingInstance $value -SettingDefinitions $SettingDefinitions -ReturnHashtable $ReturnHashtable -AllSettingDefinitions $AllSettingDefinitions $addToParameters = $false } } } '#microsoft.graph.deviceManagementConfigurationSimpleSettingCollectionInstance' { $values = @() $childValues = if ($IsRoot) { $SettingInstance.AdditionalProperties.simpleSettingCollectionValue.value } else { $SettingInstance.simpleSettingCollectionValue.value } foreach ($value in $childValues) { $values += $value } $settingValue = $values } Default { $settingValue = $SettingInstance.value } } if ($addToParameters) { if (-not $ReturnHashtable.ContainsKey($settingName)) { $ReturnHashtable.Add($settingName, $settingValue) } else { # Only happens when it's a GroupCollection(Collection) with multiple entries $ReturnHashtable[$settingName] = @($ReturnHashtable[$settingName]) + $settingValue } } } function Update-IntuneDeviceConfigurationPolicy { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = 'true')] [System.String] $DeviceConfigurationPolicyId, [Parameter()] [System.String] $Name, [Parameter()] [System.String] $Description, [Parameter()] [System.String] $Platforms, [Parameter()] [System.String] $Technologies, [Parameter()] [System.String] $TemplateReferenceId, [Parameter()] [Array] $Settings ) try { $Uri = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies/$DeviceConfigurationPolicyId" $policy = @{ 'name' = $Name 'description' = $Description 'platforms' = $Platforms 'templateReference' = @{'templateId' = $TemplateReferenceId } 'technologies' = $Technologies 'settings' = $Settings } $body = $policy | ConvertTo-Json -Depth 20 #write-verbose -Message $body Invoke-MgGraphRequest -Method PUT -Uri $Uri -Body $body -ErrorAction Stop } catch { New-M365DSCLogEntry -Message 'Error updating data:' ` -Exception $_ ` -Source $($MyInvocation.MyCommand.Source) ` -TenantId $TenantId ` -Credential $Credential throw } } function Get-ComplexFunctionsFromFilterQuery { [CmdletBinding()] [OutputType([System.Array])] param ( [string]$FilterQuery ) $complexFunctionsRegex = "startswith\((.*?),\s*'(.*?)'\)|endswith\((.*?),\s*'(.*?)'\)|contains\((.*?),\s*'(.*?)'\)" [array]$complexFunctions = [regex]::Matches($FilterQuery, $complexFunctionsRegex) | ForEach-Object { $_.Value } return $complexFunctions } function Remove-ComplexFunctionsFromFilterQuery { [CmdletBinding()] [OutputType([System.String])] param ( [string]$FilterQuery ) $complexFunctionsRegex = "startswith\((.*?),\s*'(.*?)'\)|endswith\((.*?),\s*'(.*?)'\)|contains\((.*?),\s*'(.*?)'\)" $basicFilterQuery = [regex]::Replace($FilterQuery, $complexFunctionsRegex, "").Trim() $basicFilterQuery = $basicFilterQuery -replace "^and\s","" -replace "\sand$","" -replace "\sand\s+", " and " -replace "\sor\s+", " or " return $basicFilterQuery } function Find-GraphDataUsingComplexFunctions { [CmdletBinding()] [OutputType([System.Array])] param ( [array]$Policies, [array]$ComplexFunctions ) foreach ($function in $ComplexFunctions) { if ($function -match "startswith\((.*?),\s*'(.*?)'") { $property = $matches[1] $value = $matches[2] $Policies = $Policies | Where-Object { $_.$property -like "$value*" } } elseif ($function -match "endswith\((.*?),\s*'(.*?)'") { $property = $matches[1] $value = $matches[2] $Policies = $Policies | Where-Object { $_.$property -like "*$value" } } elseif ($function -match "contains\((.*?),\s*'(.*?)'") { $property = $matches[1] $value = $matches[2] $Policies = $Policies | Where-Object { $_.$property -like "*$value*" } } } return $Policies } |