Modules/M365DSCDRGUtil.psm1
|
<#
.SYNOPSIS Converts the first character of a string to uppercase. .DESCRIPTION Returns a new string where the first character is converted to uppercase and the remaining characters are unchanged. .PARAMETER Value Specifies the input string to transform. .OUTPUTS System.String #> 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) } <# .SYNOPSIS Converts the first character of a string to lowercase. .DESCRIPTION Returns a new string where the first character is converted to lowercase and the remaining characters are unchanged. .PARAMETER Value Specifies the input string to transform. .OUTPUTS System.String #> 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) } <# .SYNOPSIS Renames complex object parameter keys for DSC and API compatibility. .DESCRIPTION Recursively processes hashtables, CIM instances, and object arrays, normalizes key casing, and applies explicit key mappings such as converting odataType to @odata.type. .PARAMETER Properties Specifies the source properties to process. .PARAMETER KeyMapping Specifies key name mappings to apply during conversion. .OUTPUTS System.Collections.Hashtable System.Object[] #> function Rename-M365DSCCimInstanceParameter { [CmdletBinding()] [OutputType([System.Collections.Hashtable], [System.Object[]])] 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) { $itemType = $item.GetType().FullName if ($itemType -like '*Hashtable*' -or $itemType -like '*CimInstance*' -or $itemType -like '*Object*') { try { $values += Rename-M365DSCCimInstanceParameter -Properties $item -KeyMapping $KeyMapping } catch { Write-Verbose -Message "Error getting values for item {$item}" } } else { $values += $item } } $result = $values return ,$result } #endregion #region Single if ($type -like '*Hashtable') { $result = [System.Collections.Specialized.CollectionsUtil]::CreateCaseInsensitiveHashtable([Hashtable]$Properties) } 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 } <# .SYNOPSIS Converts a complex object to a hashtable representation. .DESCRIPTION Uses Microsoft365DSC conversion helpers to transform CIM instances or arrays of CIM instances into hashtables. .PARAMETER ComplexObject Specifies the complex object or array of complex objects to convert. .OUTPUTS System.Collections.Hashtable System.Collections.Hashtable[] #> function Get-M365DSCDRGComplexTypeToHashtable { [CmdletBinding()] [OutputType([System.Collections.Hashtable], [System.Collections.Hashtable[]])] param( [Parameter()] $ComplexObject ) Initialize-M365DSCDllLoader -ErrorAction Stop if ($null -eq $ComplexObject) { return $null } if ($ComplexObject -is [array]) { return [Microsoft365DSC.Converter.ComplexObjectConverter]::ToHashtableArray($ComplexObject) } return [Microsoft365DSC.Converter.ComplexObjectConverter]::ToHashtable($ComplexObject) } <# 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 #> <# .SYNOPSIS Converts a complex object to a DSC string representation. .DESCRIPTION Converts CIM instances or hashtables to the DSC CIM instance string format, with optional nested type mapping, whitespace control, and array handling. .PARAMETER ComplexObject Specifies the complex object or array to convert. .PARAMETER CIMInstanceName Specifies the CIM instance class name used in the generated DSC representation. .PARAMETER ComplexTypeMapping Specifies optional nested type mappings for specific properties. .PARAMETER Whitespace Specifies the whitespace prefix used when rendering output. .PARAMETER IndentLevel Specifies the indentation level used when rendering output. .PARAMETER IsArray Indicates that the input should be rendered as an array of instances. .OUTPUTS System.String System.String[] #> function Get-M365DSCDRGComplexTypeToString { [CmdletBinding()] [OutputType([System.String], [System.String[]])] 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 } Initialize-M365DSCDllLoader -ErrorAction Stop $typeMappingList = [System.Collections.Generic.List[Microsoft365DSC.Converter.ComplexTypeMapping]]::new() if ($PSBoundParameters.ContainsKey('ComplexTypeMapping') -and $ComplexTypeMapping -ne $null) { foreach ($mapping in $ComplexTypeMapping) { $typeMappingList.Add($mapping) } } $returnValue = [Microsoft365DSC.Converter.ComplexObjectConverter]::ToDscString($ComplexObject, $CIMInstanceName, $typeMappingList, $Whitespace, $IndentLevel, $IsArray) if ($returnValue -is [System.Array]) { return ,$returnValue } return $returnValue } <# .SYNOPSIS Update special characters in a string to be escaped in a DSC configuration. .DESCRIPTION This function updates special characters in a string to be escaped in a DSC configuration. The function replaces the following characters: - 0x201C = “ - 0x201D = ” - 0x201E = „ .PARAMETER String The string to be updated. .OUTPUTS System.String .EXAMPLE PS> Update-M365DSCSpecialCharacters -String 'This is a test string with special characters: „, “, ”' #> function Update-M365DSCSpecialCharacters { [CmdletBinding()] [OutputType([System.String])] param( [Parameter(Mandatory = $true)] [System.String] $String ) Initialize-M365DSCDllLoader -ErrorAction Stop return [Microsoft365DSC.Utilities.Utilities]::UpdateSpecialCharacters($String) } <# .SYNOPSIS Tests whether an object is a CIM instance. .DESCRIPTION Returns true when the provided object is not null and its type name indicates a CIM instance. .PARAMETER Object Specifies the object to test. .OUTPUTS System.Boolean #> function Test-IsCimInstance { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [AllowEmptyString()] [AllowNull()] [System.Object] $Object ) return $null -ne $Object -and $Object.GetType().FullName -like '*CimInstance*' } <# .SYNOPSIS Tests whether an object is a hashtable-like type. .DESCRIPTION Returns true when the provided object is not null and is a hashtable or ordered dictionary. .PARAMETER Object Specifies the object to test. .OUTPUTS System.Boolean #> function Test-IsHashtable { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [AllowEmptyString()] [AllowNull()] [System.Object] $Object ) return $null -ne $Object -and ($Object.GetType().FullName -like '*Hashtable' -or $Object.GetType().FullName -like '*OrderedDictionary') } <# .SYNOPSIS Tests whether an object is an object array. .DESCRIPTION Returns true when the provided object is not null and its type name is Object[]. .PARAMETER Object Specifies the object to test. .OUTPUTS System.Boolean #> function Test-IsObjectArray { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [AllowEmptyString()] [AllowNull()] [System.Object] $Object ) return $null -ne $Object -and $Object.GetType().Name -eq 'Object[]' } <# .SYNOPSIS Tests whether an object can be treated as a complex object array. .DESCRIPTION Returns true when the object is an array of CIM instances or hashtables, or an object array containing those types. .PARAMETER Object Specifies the object to evaluate. .OUTPUTS System.Boolean #> function Test-IsComplexArrayCandidate { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [AllowEmptyString()] [AllowNull()] [System.Object] $Object ) if ($null -eq $Object) { return $false } $typeName = $Object.GetType().FullName if ($typeName -like '*CimInstance[[\]]' -or $typeName -like '*Hashtable[[\]]') { return $true } if ($typeName -like '*Object[[\]]' -and $Object.Count -gt 0) { return ($Object[0].GetType().FullName -like '*CimInstance*' -or $Object[0].GetType().FullName -like '*Hashtable*') } return $false } <# .SYNOPSIS Compares two complex objects for drift. .DESCRIPTION Compares source and target complex objects using the Microsoft365DSC comparer, records drift details in the global drift collection, and returns whether both objects are equivalent. .PARAMETER Source Specifies the current or source object. .PARAMETER Target Specifies the desired or target object. .PARAMETER PropertyName Specifies the property name context used by the comparer. .OUTPUTS System.Boolean #> function Compare-M365DSCComplexObject { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter()] $Source, [Parameter()] $Target, [Parameter(Mandatory = $true)] [System.String] $PropertyName ) if ($null -eq $Global:AllDrifts) { $Global:AllDrifts = @{ DriftInfo = @() CurrentValues = @{} DesiredValues = @{} } } Initialize-M365DSCDllLoader -ErrorAction Stop $tuple = [Microsoft365DSC.Compare.ComplexObjectComparer]::Compare($Source, $Target, $PropertyName, $null) if ($tuple.Item1.Count -gt 0) { $Global:AllDrifts.DriftInfo += $tuple.Item1 } return $tuple.Item2 } <# .SYNOPSIS Writes detected configuration drifts to the event log. .DESCRIPTION Masks authentication parameters, builds a drift event payload from desired and current values, and writes the event to the Microsoft365DSC event source when drift information is present. .PARAMETER Drifts Specifies the drift object that contains drift details. .PARAMETER ResourceName Specifies the DSC resource name associated with the drift. .PARAMETER TenantName Specifies the tenant identifier or display name included in the event payload. .PARAMETER CurrentValues Specifies current resource values to include in the event payload. .PARAMETER DesiredValues Specifies desired resource values to include in the event payload. #> function Write-M365DSCDriftsToEventLog { [CmdletBinding()] param( [Parameter()] [System.Collections.Hashtable] $Drifts, [Parameter()] [System.String] $ResourceName, [Parameter()] [System.String] $TenantName, [Parameter(Mandatory = $true)] [HashTable] $CurrentValues, [Parameter(Mandatory = $true)] [Object] $DesiredValues ) $CurrentValues = Set-M365DSCAuthenticationParameterMask -BoundParameters $CurrentValues $DesiredValues = Set-M365DSCAuthenticationParameterMask -BoundParameters $DesiredValues # If ExistingDrifts is null, then this is the main call and not a recursive one. Write to the Event log. if ($null -ne $Drifts -and $Drifts.DriftInfo.Length -gt 0) { if ($PSEdition -eq 'Desktop' -or $IsWindows) { # Get LCMState $LCMState = $null try { if (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { $LCMInfo = Get-DscLocalConfigurationManager -ErrorAction Stop if ($LCMInfo.LCMStateDetail -eq 'LCM is performing a consistency check.' -or ` $LCMInfo.LCMStateDetail -eq 'LCM exécute une vérification de cohérence.' -or ` $LCMInfo.LCMStateDetail -eq 'LCM führt gerade eine Konsistenzüberprüfung durch.') { $LCMState = 'ConsistencyCheck' } elseif ($LCMInfo.LCMStateDetail -eq 'LCM is testing node against the configuration.') { $LCMState = 'ManualTestDSCConfiguration' } elseif ($LCMInfo.LCMStateDetail -eq 'LCM is applying a new configuration.' -or ` $LCMInfo.LCMStateDetail -eq 'LCM applique une nouvelle configuration.') { $LCMState = 'Initial' } } else { $LCMState = 'Unauthorized' } } catch { Write-Verbose -Message $_.Exception } } if (-not $ResourceName.StartsWith('MSFT_')) { $ResourceName = 'MSFT_' + $ResourceName } $EventMessage = [System.Text.StringBuilder]::new() $EventMessage.Append("<M365DSCEvent>`r`n") | Out-Null $EventMessage.Append(" <ConfigurationDrift Source=`"$ResourceName`" TenantId=`"$TenantName`"") | Out-Null if (-not [System.String]::IsNullOrEmpty($LCMState)) { $EventMessage.Append(" LCMState=`"" + $LCMState + "`"") | Out-Null } $EventMessage.Append(">`r`n") | Out-Null $EventMessage.Append(" <ParametersNotInDesiredState>`r`n") | Out-Null foreach ($drift in $Drifts.DriftInfo) { $EventMessage.Append(" <Param Name=`"$($drift.PropertyName.Replace('..', '.'))`"><CurrentValue>$($drift.CurrentValue)</CurrentValue><DesiredValue>$($drift.DesiredValue)</DesiredValue></Param>`r`n") | Out-Null } $EventMessage.Append(" </ParametersNotInDesiredState>`r`n") | Out-Null $EventMessage.Append(" </ConfigurationDrift>`r`n") | Out-Null $EventMessage.Append(" <DesiredValues>`r`n") | Out-Null foreach ($Key in $DesiredValues.Keys) { $Value = $DesiredValues.$Key if ([System.String]::IsNullOrEmpty($Value)) { $Value = "`$null" } $EventMessage.Append(" <Param Name =`"$($key)`">$Value</Param>`r`n") | Out-Null } $EventMessage.Append(" </DesiredValues>`r`n") | Out-Null $EventMessage.Append(" <CurrentValues>`r`n") | Out-Null foreach ($Key in $CurrentValues.Keys) { $Value = $CurrentValues.$Key if ([System.String]::IsNullOrEmpty($Value)) { $Value = "`$null" } $EventMessage.Append(" <Param Name =`"$key`">$Value</Param>`r`n") | Out-Null } $EventMessage.Append(" </CurrentValues>`r`n") | Out-Null $EventMessage.Append('</M365DSCEvent>') | Out-Null Write-Verbose -Message $EventMessage.ToString() Add-M365DSCEvent -Message $EventMessage.ToString() -EventType 'Drift' -EntryType 'Warning' ` -EventID 1 -Source $ResourceName } } <# .SYNOPSIS Normalizes complex objects into hashtables. .DESCRIPTION Converts complex CIM instances into normalized hashtables. Supports single-level conversion and optional exclusion of unchanged properties. .PARAMETER ComplexObject Specifies the complex object to normalize. .PARAMETER SingleLevel Converts only top-level properties and lowercases the first character of each property name. .PARAMETER ExcludeUnchangedProperties Excludes properties that were not modified when SingleLevel conversion is used. .OUTPUTS System.Collections.Hashtable System.Collections.Hashtable[] #> function Convert-M365DSCDRGComplexTypeToHashtable { [CmdletBinding()] [OutputType([System.Collections.Hashtable], [System.Collections.Hashtable[]])] param( [Parameter(Mandatory = $true)] [AllowNull()] $ComplexObject, [Parameter()] [switch] $SingleLevel, [Parameter()] [switch] $ExcludeUnchangedProperties ) if ($null -eq $ComplexObject) { return @{} } if ($SingleLevel) { $returnObject = @{} $keys = $ComplexObject.CimInstanceProperties | Where-Object -Property Name -NE 'PSComputerName' foreach ($key in $keys) { if ($ExcludeUnchangedProperties -and -not $key.IsValueModified) { continue } $propertyName = $key.Name[0].ToString().ToLower() + $key.Name.Substring(1, $key.Name.Length - 1) $propertyValue = $ComplexObject.$($key.Name) $returnObject.Add($propertyName, $propertyValue) } return $returnObject } Initialize-M365DSCDllLoader -ErrorAction Stop return [Microsoft365DSC.Converter.ObjectNormalizer]::Normalize($ComplexObject) } |