Modules/M365DSCCheckProperties.psm1
<#
.Description This function checks if properties of existing resources are up to date. Creates a report about missing or outdated properties of existing resources and a list of missing resources. .Functionality Internal #> function Get-PropertyReport { param ( [Parameter(Mandatory = $true)] [System.String] $DestinationFolder, [Parameter()] [System.Management.Automation.PSCredential] $Credential ) # list of cmdlet parameters to be ignored $invalidParameters = @("ErrorVariable", ` "ErrorAction", ` "InformationVariable", ` "InformationAction", ` "WarningVariable", ` "WarningAction", ` "OutVariable", ` "OutBuffer", ` "PipelineVariable", ` "Verbose", ` "WhatIf", ` "Debug", "Confirm", "AsJob") # list of M365 DSC resource properties to be ignored $invalidProperties = @("ErrorVariable", ` "ErrorAction", ` "InformationVariable", ` "InformationAction", ` "WarningVariable", ` "WarningAction", ` "OutVariable", ` "OutBuffer", ` "PipelineVariable", ` "Verbose", ` "WhatIf", ` "Debug", "Credential", "ApplicationId", "Ensure", "TenantId", "CertificateThumbprint", "CertificatePath", "CertificatePassword", "IsSingleInstance") # list of M365 workloads to check $workloads = @( @{Name = 'ExchangeOnline'; ModuleName = "ExchangeOnlineManagement"; CommandName = "Get-Mailbox"; Prefix = "EXO"; } @{Name = 'MicrosoftTeams'; ModuleName = "MicrosoftTeams"; Prefix = "Teams"; } @{Name = 'SecurityComplianceCenter'; ModuleName = "ExchangeOnlineManagement"; CommandName = "Set-ComplianceCase"; Prefix = "SC"; } ) # mapping table for resources with names different from cmdlet name $cmdletMapping = @{ CasMailbox = "CASMailboxSettings" Mailbox = "SharedMailbox" MailboxRegionalConfiguration = "MailboxSettings" EXOPerimeterConfig = "PerimeterConfiguration" } $missingResources = @() $report = @() if ($null -eq $Credential) { $Credential = Get-Credential $PSBoundParameters.Add("Credential", $Credential) } $folderPath = Join-Path $PSScriptRoot -ChildPath "../DSCResources" Write-Verbose "Folderpath of DSC resources: $folderPath" foreach ($module in $workloads) { Write-Verbose "Connecting to {$($Module.Name)}" $ConnectionMode = New-M365DSCConnection -Workload ($Module.Name) -InboundParameters $PSBoundParameters Write-Verbose "Getting list of cmdlets of {$($Module.ModuleName)}..." $CurrentModuleName = $Module.ModuleName if ($null -eq $CurrentModuleName -or $Module.CommandName) { Write-Verbose "Loading proxy for $($Module.ModuleName)" $foundModule = Get-Module | Where-Object -FilterScript { $_.ExportedCommands.Values.Name -ccontains $Module.CommandName } $CurrentModuleName = $foundModule.Name Import-Module $CurrentModuleName -Force -Global -ErrorAction SilentlyContinue } else { Import-Module $CurrentModuleName -Force -Global -ErrorAction SilentlyContinue $ConnectionMode = New-M365DSCConnection -Workload $Module.Name -InboundParameters $PSBoundParameters } $cmdlets = Get-Command -CommandType 'Function' -Module $CurrentModuleName $setCmdlets = $cmdlets | Where-Object { $_.Name -like 'Set-*' } Write-Verbose "Found $($setCmdlets.Count) Set-* cmdlets for $($Module.ModuleName) ($($cmdlets.Count) in total)" $i = 1 foreach($cmdlet in $setCmdlets) { Write-Progress -Activity "Checking resources" -Status $cmdlet.Name -PercentComplete (($i / $setCmdlets.Length) * 100) $resourceExists = $false $resourceName = "MSFT_" + $module.Prefix + $cmdlet.Name.split('-')[1] if ($module.ModuleName -eq "MicrosoftTeams" -and $resourceName -like "*TeamsCsTeams*") { $resourceName = $resourceName -replace("TeamsCsTeams","Teams") } if ($module.ModuleName -eq "MicrosoftTeams" -and $resourceName -like "*TeamsCs*") { $resourceName = $resourceName -replace ("TeamsCs", "Teams") } $foundInFiles = Get-ChildItem -Path $folderPath | Where-Object { $_.Name -like $resourceName } if ($null -eq $foundInFiles) { $resourceNameFromMapping = $cmdletMapping[$cmdlet.Name.split('-')[1]] if ($null -ne $resourceNameFromMapping) { $resourceName = "MSFT_" + $module.Prefix + $resourceNameFromMapping $foundInFiles = Get-ChildItem -Path $folderPath | Where-Object { $_.Name -like $resourceName } if ($null -ne $foundInFiles) { $resourceExists = $true } } } else { $resourceExists = $true } if ($resourceExists) { # Get parameter of cmdlet Write-Verbose "Get parameters of cmdlet $($cmdlet.Name)" $targetParameters = @() $resourceParamters = @() $cmdletParameters = (Get-Command $cmdlet.Name).Parameters foreach ($parameter in $cmdletParameters.Keys) { if ($parameter -notin $invalidParameters) { $targetParameters += $parameter } } # Get properties of DSC resource Write-Verbose "Get properties of resource $resourceName" Import-Module $($folderPath + "\" + $resourceName) -Force $resourceProperties = (Get-Command Set-TargetResource -Module $resourceName).Parameters foreach ($property in $resourceProperties.Keys) { if ($property -notin $invalidProperties) { $resourceParamters += $property } } Remove-Module -Name $resourceName -Force -Confirm:$false # Compare properties Write-Verbose "Compare parameters of $resourceName" $difference = Compare-Object -ReferenceObject @($targetParameters | Select-Object) -DifferenceObject @($resourceParamters | Select-Object) -IncludeEqual $missingProperties = ($difference | Where-Object { $_.SideIndicator -eq "<="}).InputObject $addtionalProperties = ($difference | Where-Object { $_.SideIndicator -eq "=>" }).InputObject # Add to report $cmdletResult = [PSCustomObject]@{ "M365DSCResource" = $resourceName "Cmdlet" = $cmdlet.Name "Service" = $module.Name "MissingProperties" = $missingProperties -join('; ') "AdditionalProperties" = $addtionalProperties -join('; ') } $report += $cmdletResult } else { $missingResources += $resourceName Write-Verbose "Resource $resourceName not found." } $i++ } } # Export reports Write-Verbose "Export reports" $report | Export-Csv -NoTypeInformation -Path "$DestinationFolder\M365DSC-Properties-Report.csv" -Delimiter "," $missingResources | Out-File "$DestinationFolder\MissingDSCResources.csv" } Export-ModuleMember -Function @( 'Get-PropertyReport' ) |