diff --git a/.github/workflows/PublishToGallery.yml b/.github/workflows/PublishToGallery.yml index e1684c36c3..ef6f59f2cf 100644 --- a/.github/workflows/PublishToGallery.yml +++ b/.github/workflows/PublishToGallery.yml @@ -33,6 +33,10 @@ jobs: Write-Verbose -Message $_ } } + - name: Build Dll Files + shell: pwsh + run: | + & ./Utilities/Build-DllFiles.ps1 -Configuration Release - name: Publish shell: powershell env: diff --git a/.github/workflows/Unit Tests.yml b/.github/workflows/Unit Tests.yml index 4dd5c6eff1..45bcb7806a 100644 --- a/.github/workflows/Unit Tests.yml +++ b/.github/workflows/Unit Tests.yml @@ -26,6 +26,10 @@ jobs: Install-PSResource -Name PSDesiredStateConfiguration -Scope AllUsers -TrustRepository Install-PSResource -Name Pester -Scope AllUsers -TrustRepository [System.Environment]::SetEnvironmentVariable('M365DSCTelemetryEnabled', $false, [System.EnvironmentVariableTarget]::Machine); + - name: Build Dll Files + shell: pwsh + run: | + & ./Utilities/Build-DllFiles.ps1 -Configuration Release - name: Run Quality Checks shell: pwsh run: | diff --git a/.gitignore b/.gitignore index c67ffdaf11..193a3f63b8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ .vs .psproj .sln +Modules/Microsoft365DSC/Dependencies/Assemblies/*.dll +Modules/Microsoft365DSC/Dependencies/Assemblies/*.pdb +Modules/Microsoft365DSC/Dependencies/Assemblies/*.xml Modules/Microsoft365DSC/DscResource.Tests Modules/Microsoft365DSC/DscResource.Tests/* node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md index a78cee4096..540da27476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,32 @@ # UNRELEASED +* AADConditionalAccessPolicy + * Fixed an issue where arrays could contain empty strings. +* AADPasswordRuleSettings + * Fixed an issue where `BannedPasswordList` could be null. +* AADPIMGroupSetting + * Fixed an issue where typed variables could lead to an exception. * AADUser * Fixed an export issue where a user was deleted during a long-running job. FIXES [#5703](https://github.com/microsoft/Microsoft365DSC/issues/5703) +* EXOIRMConfiguration + * Changed the parameters `LicensingLocation` and `RMSOnlineKeySharingLocation` + to `System.String` instead of an implicit conversion to `System.Uri`. +* EXOMigration + * Fixed an issue where `UserEmails` could contain empty strings. * FabricAdmintenantsettings * Refreshed the property list. FIXES [#6866](https://github.com/microsoft/Microsoft365DSC/issues/6866) +* IntuneDeviceEnrollmentStatusPageWindows10 + * Fixed an issue where `SelectedMobileAppNames` could contain empty strings + during the execution of `Test-TargetResoure`. * O365OrgSettings * Changed how errors are handled to fail instead of returning false drifts. FIXES [#6787](https://github.com/microsoft/Microsoft365DSC/issues/6787) +* SCPolicyConfig + * Fixed an issue where the sub-property `JustificationText` could contain + empty strings. * TeamsDialInConferencingTenantSettings * Fixed an issue where the properties `MigrateServiceNumbersOnCrossForestMove` and `UseUniqueConferenceIds` were not rendered correctly in the @@ -21,6 +38,7 @@ an error if the module was installed in Windows PowerShell but the update attempt was performed in PowerShell 7. * DEPENDENCIES + * Removed dependency on `PSDesiredStateConfiguration`. * Updated MSCloudLoginAssistant to version 1.1.58. # 1.26.128.1 @@ -76,15 +94,21 @@ * M365DSCPermissions * Changed the output of `Get-M365DSCCompiledPermissionList` to show the required Read and Update permissions for `Roles` and `RoleGroups`. -* MISC - * Updated the structure of all EXO settings.json files that contain the - `Roles` and `RoleGroups` properties. +* M365DSCTelemetryEngine + * Added a function to test if telemetry is enabled. * M365DSCUtil * Added the output of the drift event to the screen in Verbose mode. FIXES [#6666](https://github.com/microsoft/Microsoft365DSC/issues/6666) * Added the parameter `-WithStatistics` to `Export-M365DSCConfiguration`. * Fixed an issue where the module is not being updated if installed with `Install-PSResource` because the filter condition was incorrect. +* MISC + * Added more performance improvements for hot code paths. + * Fixed issues with mismatched property types in resource tests. + * Refactored parts of the core engine with C#. + * Replaced `Get-(Pwsh)DscResource` with a custom implementation. + * Updated the structure of all EXO settings.json files that contain the + `Roles` and `RoleGroups` properties. * DEPENDENCIES * Fixed a case typo in `RequiredVersion` of a dependency. FIXES [#6815](https://github.com/microsoft/Microsoft365DSC/issues/6815) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADAdministrativeUnit/MSFT_AADAdministrativeUnit.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAdministrativeUnit/MSFT_AADAdministrativeUnit.psm1 index 0b6363c5a4..7378b1d660 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADAdministrativeUnit/MSFT_AADAdministrativeUnit.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAdministrativeUnit/MSFT_AADAdministrativeUnit.psm1 @@ -610,12 +610,12 @@ function Set-TargetResource $currentMembers = @() foreach ($member in $backCurrentMembers) { - $currentMembers += [pscustomobject]@{Type = $member.Type; Identity = $member.Identity } + $currentMembers += @{Type = $member.Type; Identity = $member.Identity } } $desiredMembers = @() foreach ($member in $requestedMembers) { - $desiredMembers += [pscustomobject]@{Type = $member.Type; Identity = $member.Identity } + $desiredMembers += @{Type = $member.Type; Identity = $member.Identity } } $membersDiff = Compare-Object -ReferenceObject $currentMembers -DifferenceObject $desiredMembers -Property Identity, Type foreach ($diff in $membersDiff) @@ -685,7 +685,7 @@ function Set-TargetResource $compareCurrentScopedRoleMembersValue = @() foreach ($roleMember in $currentScopedRoleMembersValue) { - $compareCurrentScopedRoleMembersValue += [pscustomobject]@{ + $compareCurrentScopedRoleMembersValue += @{ RoleName = $roleMember.RoleName Identity = $roleMember.RoleMemberInfo.Identity Type = $roleMember.RoleMemberInfo.Type @@ -694,7 +694,7 @@ function Set-TargetResource $compareDesiredScopedRoleMembersValue = @() foreach ($roleMember in $desiredScopedRoleMembersValue) { - $compareDesiredScopedRoleMembersValue += [pscustomobject]@{ + $compareDesiredScopedRoleMembersValue += @{ RoleName = $roleMember.RoleName Identity = $roleMember.RoleMemberInfo.Identity Type = $roleMember.RoleMemberInfo.Type diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 index c06c697ee9..7363572aaf 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 @@ -1914,7 +1914,7 @@ function Export-TargetResource function Get-M365DSCAzureADAppPermissions { [CmdletBinding()] - [OutputType([PSCustomObject])] + [OutputType([System.Collections.Hashtable[]])] param ( [Parameter(Mandatory = $true)] diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADConditionalAccessPolicy/MSFT_AADConditionalAccessPolicy.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADConditionalAccessPolicy/MSFT_AADConditionalAccessPolicy.psm1 index e36351d12c..031cc9007f 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADConditionalAccessPolicy/MSFT_AADConditionalAccessPolicy.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADConditionalAccessPolicy/MSFT_AADConditionalAccessPolicy.psm1 @@ -695,13 +695,13 @@ function Get-TargetResource DisplayName = $Policy.DisplayName Id = $Policy.Id State = $Policy.State - IncludeApplications = [System.String[]](@() + $Policy.Conditions.Applications.IncludeApplications) + IncludeApplications = Get-M365DSCArrayFromProperty -PropertyValue $Policy.Conditions.Applications.IncludeApplications -ElementType ([System.String]) #no translation of Application GUIDs, return empty string array if undefined - ExcludeApplications = [System.String[]](@() + $Policy.Conditions.Applications.ExcludeApplications) + ExcludeApplications = [System.String[]]($Policy.Conditions.Applications.ExcludeApplications) ApplicationsFilter = $Policy.Conditions.Applications.ApplicationFilter.Rule ApplicationsFilterMode = $Policy.Conditions.Applications.ApplicationFilter.Mode #no translation of GUIDs, return empty string array if undefined - IncludeUserActions = [System.String[]](@() + $Policy.Conditions.Applications.IncludeUserActions) + IncludeUserActions = [System.String[]]($Policy.Conditions.Applications.IncludeUserActions) #no translation needed, return empty string array if undefined IncludeUsers = $IncludeUsers ExcludeUsers = $ExcludeUsers @@ -711,20 +711,20 @@ function Get-TargetResource ExcludeRoles = $ExcludeRoles IncludeGuestOrExternalUserTypes = [System.String[]]$IncludeGuestOrExternalUserTypes IncludeExternalTenantsMembershipKind = [System.String]$Policy.Conditions.Users.IncludeGuestsOrExternalUsers.ExternalTenants.MembershipKind - IncludeExternalTenantsMembers = [System.String[]](@() + $Policy.Conditions.Users.IncludeGuestsOrExternalUsers.ExternalTenants.AdditionalProperties.members) + IncludeExternalTenantsMembers = Get-M365DSCArrayFromProperty -PropertyValue $Policy.Conditions.Users.IncludeGuestsOrExternalUsers.ExternalTenants.AdditionalProperties.members -ElementType ([System.String]) ExcludeGuestOrExternalUserTypes = [System.String[]]$ExcludeGuestOrExternalUserTypes ExcludeExternalTenantsMembershipKind = [System.String]$Policy.Conditions.Users.ExcludeGuestsOrExternalUsers.ExternalTenants.MembershipKind - ExcludeExternalTenantsMembers = [System.String[]](@() + $Policy.Conditions.Users.ExcludeGuestsOrExternalUsers.ExternalTenants.AdditionalProperties.members) + ExcludeExternalTenantsMembers = Get-M365DSCArrayFromProperty -PropertyValue $Policy.Conditions.Users.ExcludeGuestsOrExternalUsers.ExternalTenants.AdditionalProperties.members -ElementType ([System.String]) IncludeServicePrincipals = $Policy.Conditions.ClientApplications.IncludeServicePrincipals ExcludeServicePrincipals = $Policy.Conditions.ClientApplications.ExcludeServicePrincipals ServicePrincipalFilterMode = $Policy.Conditions.ClientApplications.ServicePrincipalFilter.Mode ServicePrincipalFilterRule = $Policy.Conditions.ClientApplications.ServicePrincipalFilter.Rule - IncludePlatforms = [System.String[]](@() + $Policy.Conditions.Platforms.IncludePlatforms) + IncludePlatforms = Get-M365DSCArrayFromProperty -PropertyValue $Policy.Conditions.Platforms.IncludePlatforms -ElementType ([System.String]) #no translation needed, return empty string array if undefined - ExcludePlatforms = [System.String[]](@() + $Policy.Conditions.Platforms.ExcludePlatforms) + ExcludePlatforms = Get-M365DSCArrayFromProperty -PropertyValue $Policy.Conditions.Platforms.ExcludePlatforms -ElementType ([System.String]) #no translation needed, return empty string array if undefined IncludeLocations = $IncludeLocations ExcludeLocations = $ExcludeLocations @@ -734,16 +734,16 @@ function Get-TargetResource #no translation or conversion needed DeviceFilterRule = [System.String]$Policy.Conditions.Devices.DeviceFilter.Rule #no translation or conversion needed - UserRiskLevels = [System.String[]](@() + $Policy.Conditions.UserRiskLevels) + UserRiskLevels = Get-M365DSCArrayFromProperty -PropertyValue $Policy.Conditions.UserRiskLevels -ElementType ([System.String]) #no translation needed, return empty string array if undefined - SignInRiskLevels = [System.String[]](@() + $Policy.Conditions.SignInRiskLevels) + SignInRiskLevels = Get-M365DSCArrayFromProperty -PropertyValue $Policy.Conditions.SignInRiskLevels -ElementType ([System.String]) #no translation needed, return empty string array if undefined - ClientAppTypes = [System.String[]](@() + $Policy.Conditions.ClientAppTypes) + ClientAppTypes = Get-M365DSCArrayFromProperty -PropertyValue $Policy.Conditions.ClientAppTypes -ElementType ([System.String]) #no translation needed, return empty string array if undefined GrantControlOperator = $Policy.GrantControls.Operator #no translation or conversion needed - BuiltInControls = [System.String[]](@() + $Policy.GrantControls.BuiltInControls) - CustomAuthenticationFactors = [System.String[]](@() + $Policy.GrantControls.CustomAuthenticationFactors) + BuiltInControls = Get-M365DSCArrayFromProperty -PropertyValue $Policy.GrantControls.BuiltInControls -ElementType ([System.String]) + CustomAuthenticationFactors = Get-M365DSCArrayFromProperty -PropertyValue $Policy.GrantControls.CustomAuthenticationFactors -ElementType ([System.String]) #no translation needed, return empty string array if undefined ApplicationEnforcedRestrictionsIsEnabled = $false -or $Policy.SessionControls.ApplicationEnforcedRestrictions.IsEnabled #make false if undefined, true if true @@ -770,7 +770,7 @@ function Get-TargetResource TransferMethods = [System.String]$Policy.Conditions.AuthenticationFlows.TransferMethods ProtocolFlows = $ProtocolFlowsValue #no translation needed, return empty string array if undefined - ServicePrincipalRiskLevels = [System.String[]](@() + $Policy.Conditions.ServicePrincipalRiskLevels) + ServicePrincipalRiskLevels = Get-M365DSCArrayFromProperty -PropertyValue $Policy.Conditions.ServicePrincipalRiskLevels -ElementType ([System.String]) #Standard part TermsOfUse = $termOfUseName InsiderRiskLevels = $InsiderRiskLevelsValue diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 index 61254d6d3c..4b97dc0b20 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroup/MSFT_AADGroup.psm1 @@ -1355,7 +1355,7 @@ function Export-TargetResource function Get-M365DSCAzureADGroupLicenses { [CmdletBinding()] - [OutputType([PSCustomObject])] + [OutputType([System.Collections.Hashtable[]])] param( [Parameter(Mandatory = $true)] $AssignedLicenses diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroupEligibilityScheduleSettings/MSFT_AADGroupEligibilityScheduleSettings.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroupEligibilityScheduleSettings/MSFT_AADGroupEligibilityScheduleSettings.psm1 index e4bceae38f..3447ef632b 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroupEligibilityScheduleSettings/MSFT_AADGroupEligibilityScheduleSettings.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroupEligibilityScheduleSettings/MSFT_AADGroupEligibilityScheduleSettings.psm1 @@ -707,7 +707,7 @@ function Export-TargetResource function Get-M365DSCRoleManagementPolicyRuleObject { [CmdletBinding()] - [OutputType([PSCustomObject])] + [OutputType([System.Collections.Hashtable[]])] param( [Parameter()] $Rule diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADPIMGroupSetting/MSFT_AADPIMGroupSetting.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADPIMGroupSetting/MSFT_AADPIMGroupSetting.psm1 index 68f96232ce..dea2df56e6 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADPIMGroupSetting/MSFT_AADPIMGroupSetting.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADPIMGroupSetting/MSFT_AADPIMGroupSetting.psm1 @@ -276,33 +276,33 @@ function Get-TargetResource if ($Script:ExportMode) { - $ActivationMaxDuration = ($role | Where-Object { $_.Id -eq 'Expiration_EndUser_Assignment' }).maximumDuration - $ActivationReqJustification = (($role | Where-Object { $_.Id -eq 'Enablement_EndUser_Assignment' }).enabledRules) -contains 'Justification' - $ActivationReqTicket = (($role | Where-Object { $_.Id -eq 'Enablement_EndUser_Assignment' }).enabledRules) -contains 'Ticketing' - $ActivationReqMFA = (($role | Where-Object { $_.Id -eq 'Enablement_EndUser_Assignment' }).enabledRules) -contains 'MultiFactorAuthentication' - $AuthenticationContext = ($role | Where-Object { $_.Id -eq 'AuthenticationContext_EndUser_Assignment' }) - $ApprovaltoActivate = (($role | Where-Object { $_.Id -eq 'Approval_EndUser_Assignment' }).setting.isApprovalRequired) - $ActivateApprovers = (($role | Where-Object { $_.Id -eq 'Approval_EndUser_Assignment' }).setting.approvalStages.primaryApprovers.id) + $ActivationMaxDurationValue = ($role | Where-Object { $_.Id -eq 'Expiration_EndUser_Assignment' }).maximumDuration + $ActivationReqJustificationValue = (($role | Where-Object { $_.Id -eq 'Enablement_EndUser_Assignment' }).enabledRules) -contains 'Justification' + $ActivationReqTicketValue = (($role | Where-Object { $_.Id -eq 'Enablement_EndUser_Assignment' }).enabledRules) -contains 'Ticketing' + $ActivationReqMFAValue = (($role | Where-Object { $_.Id -eq 'Enablement_EndUser_Assignment' }).enabledRules) -contains 'MultiFactorAuthentication' + $AuthenticationContextValue = ($role | Where-Object { $_.Id -eq 'AuthenticationContext_EndUser_Assignment' }) + $ApprovaltoActivateValue = (($role | Where-Object { $_.Id -eq 'Approval_EndUser_Assignment' }).setting.isApprovalRequired) + $ActivateApproversValue = (($role | Where-Object { $_.Id -eq 'Approval_EndUser_Assignment' }).setting.approvalStages.primaryApprovers.id) } else { - $ActivationMaxDuration = ($role | Where-Object { $_.Id -eq 'Expiration_EndUser_Assignment' }).AdditionalProperties.maximumDuration - $ActivationReqJustification = (($role | Where-Object { $_.Id -eq 'Enablement_EndUser_Assignment' }).AdditionalProperties.enabledRules) -contains 'Justification' - $ActivationReqTicket = (($role | Where-Object { $_.Id -eq 'Enablement_EndUser_Assignment' }).AdditionalProperties.enabledRules) -contains 'Ticketing' - $ActivationReqMFA = (($role | Where-Object { $_.Id -eq 'Enablement_EndUser_Assignment' }).AdditionalProperties.enabledRules) -contains 'MultiFactorAuthentication' - $AuthenticationContext = ($role | Where-Object { $_.Id -eq 'AuthenticationContext_EndUser_Assignment' }).AdditionalProperties - $ApprovaltoActivate = (($role | Where-Object { $_.Id -eq 'Approval_EndUser_Assignment' }).AdditionalProperties.setting.isApprovalRequired) - [array]$ActivateApprovers = (($role | Where-Object { $_.Id -eq 'Approval_EndUser_Assignment' }).AdditionalProperties.setting.approvalStages.primaryApprovers) + $ActivationMaxDurationValue = ($role | Where-Object { $_.Id -eq 'Expiration_EndUser_Assignment' }).AdditionalProperties.maximumDuration + $ActivationReqJustificationValue = (($role | Where-Object { $_.Id -eq 'Enablement_EndUser_Assignment' }).AdditionalProperties.enabledRules) -contains 'Justification' + $ActivationReqTicketValue = (($role | Where-Object { $_.Id -eq 'Enablement_EndUser_Assignment' }).AdditionalProperties.enabledRules) -contains 'Ticketing' + $ActivationReqMFAValue = (($role | Where-Object { $_.Id -eq 'Enablement_EndUser_Assignment' }).AdditionalProperties.enabledRules) -contains 'MultiFactorAuthentication' + $AuthenticationContextValue = ($role | Where-Object { $_.Id -eq 'AuthenticationContext_EndUser_Assignment' }).AdditionalProperties + $ApprovaltoActivateValue = (($role | Where-Object { $_.Id -eq 'Approval_EndUser_Assignment' }).AdditionalProperties.setting.isApprovalRequired) + [array]$ActivateApproversValue = (($role | Where-Object { $_.Id -eq 'Approval_EndUser_Assignment' }).AdditionalProperties.setting.approvalStages.primaryApprovers) } - $AuthenticationContextRequired = $AuthenticationContext.isEnabled - if ($AuthenticationContextRequired) + $AuthenticationContextRequiredValue = $AuthenticationContextValue.isEnabled + if ($AuthenticationContextRequiredValue) { - $AuthenticationContextId = $AuthenticationContext.claimValue - $AuthenticationContextName = (Get-MgBetaIdentityConditionalAccessAuthenticationContextClassReference -AuthenticationContextClassReferenceId $AuthenticationContextId).DisplayName + $AuthenticationContextIdValue = $AuthenticationContextValue.claimValue + $AuthenticationContextNameValue = (Get-MgBetaIdentityConditionalAccessAuthenticationContextClassReference -AuthenticationContextClassReferenceId $AuthenticationContextIdValue).DisplayName } [string[]]$ActivateApprover = @() - foreach ($Item in $ActivateApprovers.id) + foreach ($Item in $ActivateApproversValue.id) { try { @@ -325,73 +325,73 @@ function Get-TargetResource if ($Script:ExportMode) { - $PermanentEligibleAssignmentisExpirationRequired = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Eligibility' }).isExpirationRequired - $ExpireEligibleAssignment = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Eligibility' }).maximumDuration - $PermanentActiveAssignmentisExpirationRequired = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Assignment' }).isExpirationRequired - $ExpireActiveAssignment = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Assignment' }).maximumDuration - $AssignmentReqMFA = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Assignment' }).enabledRules) -contains 'MultiFactorAuthentication' - $AssignmentReqJustification = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Assignment' }).enabledRules) -contains 'Justification' - $ElegibilityAssignmentReqMFA = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Eligibility' }).enabledRules) -contains 'MultiFactorAuthentication' - $ElegibilityAssignmentReqJustification = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Eligibility' }).enabledRules) -contains 'Justification' - $EligibleAlertNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Eligibility' }).isDefaultRecipientsEnabled - [string[]]$EligibleAlertNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Eligibility' }).notificationRecipients - $EligibleAlertNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Eligibility' }).notificationLevel) -contains ('Critical') - $EligibleAssigneeNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Eligibility' }).isDefaultRecipientsEnabled - [string[]]$EligibleAssigneeNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Eligibility' }).notificationRecipients - $EligibleAssigneeNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Eligibility' }).notificationLevel) -contains ('Critical') - $EligibleApproveNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Eligibility' }).isDefaultRecipientsEnabled - [string[]]$EligibleApproveNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Eligibility' }).notificationRecipients - $EligibleApproveNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Eligibility' }).notificationLevel) -contains ('Critical') - $ActiveAlertNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Assignment' }).isDefaultRecipientsEnabled - [string[]]$ActiveAlertNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Assignment' }).notificationRecipients - $ActiveAlertNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Assignment' }).notificationLevel) -contains ('Critical') - $ActiveAssigneeNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Assignment' }).isDefaultRecipientsEnabled - [string[]]$ActiveAssigneeNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Assignment' }).notificationRecipients - $ActiveAssigneeNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Assignment' }).notificationLevel) -contains ('Critical') - $ActiveApproveNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Assignment' }).isDefaultRecipientsEnabled - [string[]]$ActiveApproveNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Assignment' }).notificationRecipients - $ActiveApproveNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Assignment' }).notificationLevel) -contains ('Critical') - $EligibleAssignmentAlertNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Admin_EndUser_Assignment' }).isDefaultRecipientsEnabled - [string[]]$EligibleAssignmentAlertNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Admin_EndUser_Assignment' }).notificationRecipients - $EligibleAssignmentAlertNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Admin_EndUser_Assignment' }).notificationLevel) -contains ('Critical') - $EligibleAssignmentAssigneeNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_EndUser_Assignment' }).isDefaultRecipientsEnabled - [string[]]$EligibleAssignmentAssigneeNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_EndUser_Assignment' }).notificationRecipients - $EligibleAssignmentAssigneeNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Requestor_EndUser_Assignment' }).notificationLevel) -contains ('Critical') + $PermanentEligibleAssignmentisExpirationRequiredValue = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Eligibility' }).isExpirationRequired + $ExpireEligibleAssignmentValue = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Eligibility' }).maximumDuration + $PermanentActiveAssignmentisExpirationRequiredValue = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Assignment' }).isExpirationRequired + $ExpireActiveAssignmentValue = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Assignment' }).maximumDuration + $AssignmentReqMFAValue = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Assignment' }).enabledRules) -contains 'MultiFactorAuthentication' + $AssignmentReqJustificationValue = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Assignment' }).enabledRules) -contains 'Justification' + $ElegibilityAssignmentReqMFAValue = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Eligibility' }).enabledRules) -contains 'MultiFactorAuthentication' + $ElegibilityAssignmentReqJustificationValue = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Eligibility' }).enabledRules) -contains 'Justification' + $EligibleAlertNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Eligibility' }).isDefaultRecipientsEnabled + [string[]]$EligibleAlertNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Eligibility' }).notificationRecipients + $EligibleAlertNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Eligibility' }).notificationLevel) -contains ('Critical') + $EligibleAssigneeNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Eligibility' }).isDefaultRecipientsEnabled + [string[]]$EligibleAssigneeNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Eligibility' }).notificationRecipients + $EligibleAssigneeNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Eligibility' }).notificationLevel) -contains ('Critical') + $EligibleApproveNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Eligibility' }).isDefaultRecipientsEnabled + [string[]]$EligibleApproveNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Eligibility' }).notificationRecipients + $EligibleApproveNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Eligibility' }).notificationLevel) -contains ('Critical') + $ActiveAlertNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Assignment' }).isDefaultRecipientsEnabled + [string[]]$ActiveAlertNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Assignment' }).notificationRecipients + $ActiveAlertNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Assignment' }).notificationLevel) -contains ('Critical') + $ActiveAssigneeNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Assignment' }).isDefaultRecipientsEnabled + [string[]]$ActiveAssigneeNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Assignment' }).notificationRecipients + $ActiveAssigneeNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Assignment' }).notificationLevel) -contains ('Critical') + $ActiveApproveNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Assignment' }).isDefaultRecipientsEnabled + [string[]]$ActiveApproveNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Assignment' }).notificationRecipients + $ActiveApproveNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Assignment' }).notificationLevel) -contains ('Critical') + $EligibleAssignmentAlertNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Admin_EndUser_Assignment' }).isDefaultRecipientsEnabled + [string[]]$EligibleAssignmentAlertNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Admin_EndUser_Assignment' }).notificationRecipients + $EligibleAssignmentAlertNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Admin_EndUser_Assignment' }).notificationLevel) -contains ('Critical') + $EligibleAssignmentAssigneeNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_EndUser_Assignment' }).isDefaultRecipientsEnabled + [string[]]$EligibleAssignmentAssigneeNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_EndUser_Assignment' }).notificationRecipients + $EligibleAssignmentAssigneeNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Requestor_EndUser_Assignment' }).notificationLevel) -contains ('Critical') } else { - $PermanentEligibleAssignmentisExpirationRequired = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Eligibility' }).AdditionalProperties.isExpirationRequired - $ExpireEligibleAssignment = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Eligibility' }).AdditionalProperties.maximumDuration - $PermanentActiveAssignmentisExpirationRequired = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Assignment' }).AdditionalProperties.isExpirationRequired - $ExpireActiveAssignment = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Assignment' }).AdditionalProperties.maximumDuration - $AssignmentReqMFA = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Assignment' }).AdditionalProperties.enabledRules) -contains 'MultiFactorAuthentication' - $AssignmentReqJustification = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Assignment' }).AdditionalProperties.enabledRules) -contains 'Justification' - $ElegibilityAssignmentReqMFA = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Eligibility' }).AdditionalProperties.enabledRules) -contains 'MultiFactorAuthentication' - $ElegibilityAssignmentReqJustification = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Eligibility' }).AdditionalProperties.enabledRules) -contains 'Justification' - $EligibleAlertNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Eligibility' }).AdditionalProperties.isDefaultRecipientsEnabled - [string[]]$EligibleAlertNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Eligibility' }).AdditionalProperties.notificationRecipients - $EligibleAlertNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Eligibility' }).AdditionalProperties.notificationLevel) -contains ('Critical') - $EligibleAssigneeNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Eligibility' }).AdditionalProperties.isDefaultRecipientsEnabled - [string[]]$EligibleAssigneeNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Eligibility' }).AdditionalProperties.notificationRecipients - $EligibleAssigneeNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Eligibility' }).AdditionalProperties.notificationLevel) -contains ('Critical') - $EligibleApproveNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Eligibility' }).AdditionalProperties.isDefaultRecipientsEnabled - [string[]]$EligibleApproveNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Eligibility' }).AdditionalProperties.notificationRecipients - $EligibleApproveNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Eligibility' }).AdditionalProperties.notificationLevel) -contains ('Critical') - $ActiveAlertNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Assignment' }).AdditionalProperties.isDefaultRecipientsEnabled - [string[]]$ActiveAlertNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Assignment' }).AdditionalProperties.notificationRecipients - $ActiveAlertNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Assignment' }).AdditionalProperties.notificationLevel) -contains ('Critical') - $ActiveAssigneeNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Assignment' }).AdditionalProperties.isDefaultRecipientsEnabled - [string[]]$ActiveAssigneeNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Assignment' }).AdditionalProperties.notificationRecipients - $ActiveAssigneeNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Assignment' }).AdditionalProperties.notificationLevel) -contains ('Critical') - $ActiveApproveNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Assignment' }).AdditionalProperties.isDefaultRecipientsEnabled - [string[]]$ActiveApproveNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Assignment' }).AdditionalProperties.notificationRecipients - $ActiveApproveNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Assignment' }).AdditionalProperties.notificationLevel) -contains ('Critical') - $EligibleAssignmentAlertNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Admin_EndUser_Assignment' }).AdditionalProperties.isDefaultRecipientsEnabled - [string[]]$EligibleAssignmentAlertNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Admin_EndUser_Assignment' }).AdditionalProperties.notificationRecipients - $EligibleAssignmentAlertNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Admin_EndUser_Assignment' }).AdditionalProperties.notificationLevel) -contains ('Critical') - $EligibleAssignmentAssigneeNotificationDefaultRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_EndUser_Assignment' }).AdditionalProperties.isDefaultRecipientsEnabled - [string[]]$EligibleAssignmentAssigneeNotificationAdditionalRecipient = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_EndUser_Assignment' }).AdditionalProperties.notificationRecipients - $EligibleAssignmentAssigneeNotificationOnlyCritical = (($role | Where-Object { $_.Id -eq 'Notification_Requestor_EndUser_Assignment' }).AdditionalProperties.notificationLevel) -contains ('Critical') + $PermanentEligibleAssignmentisExpirationRequiredValue = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Eligibility' }).AdditionalProperties.isExpirationRequired + $ExpireEligibleAssignmentValue = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Eligibility' }).AdditionalProperties.maximumDuration + $PermanentActiveAssignmentisExpirationRequiredValue = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Assignment' }).AdditionalProperties.isExpirationRequired + $ExpireActiveAssignmentValue = ($role | Where-Object { $_.Id -eq 'Expiration_Admin_Assignment' }).AdditionalProperties.maximumDuration + $AssignmentReqMFAValue = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Assignment' }).AdditionalProperties.enabledRules) -contains 'MultiFactorAuthentication' + $AssignmentReqJustificationValue = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Assignment' }).AdditionalProperties.enabledRules) -contains 'Justification' + $ElegibilityAssignmentReqMFAValue = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Eligibility' }).AdditionalProperties.enabledRules) -contains 'MultiFactorAuthentication' + $ElegibilityAssignmentReqJustificationValue = (($role | Where-Object { $_.Id -eq 'Enablement_Admin_Eligibility' }).AdditionalProperties.enabledRules) -contains 'Justification' + $EligibleAlertNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Eligibility' }).AdditionalProperties.isDefaultRecipientsEnabled + [string[]]$EligibleAlertNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Eligibility' }).AdditionalProperties.notificationRecipients + $EligibleAlertNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Eligibility' }).AdditionalProperties.notificationLevel) -contains ('Critical') + $EligibleAssigneeNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Eligibility' }).AdditionalProperties.isDefaultRecipientsEnabled + [string[]]$EligibleAssigneeNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Eligibility' }).AdditionalProperties.notificationRecipients + $EligibleAssigneeNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Eligibility' }).AdditionalProperties.notificationLevel) -contains ('Critical') + $EligibleApproveNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Eligibility' }).AdditionalProperties.isDefaultRecipientsEnabled + [string[]]$EligibleApproveNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Eligibility' }).AdditionalProperties.notificationRecipients + $EligibleApproveNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Eligibility' }).AdditionalProperties.notificationLevel) -contains ('Critical') + $ActiveAlertNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Assignment' }).AdditionalProperties.isDefaultRecipientsEnabled + [string[]]$ActiveAlertNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Assignment' }).AdditionalProperties.notificationRecipients + $ActiveAlertNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Admin_Admin_Assignment' }).AdditionalProperties.notificationLevel) -contains ('Critical') + $ActiveAssigneeNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Assignment' }).AdditionalProperties.isDefaultRecipientsEnabled + [string[]]$ActiveAssigneeNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Assignment' }).AdditionalProperties.notificationRecipients + $ActiveAssigneeNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Requestor_Admin_Assignment' }).AdditionalProperties.notificationLevel) -contains ('Critical') + $ActiveApproveNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Assignment' }).AdditionalProperties.isDefaultRecipientsEnabled + [string[]]$ActiveApproveNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Assignment' }).AdditionalProperties.notificationRecipients + $ActiveApproveNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Approver_Admin_Assignment' }).AdditionalProperties.notificationLevel) -contains ('Critical') + $EligibleAssignmentAlertNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Admin_EndUser_Assignment' }).AdditionalProperties.isDefaultRecipientsEnabled + [string[]]$EligibleAssignmentAlertNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Admin_EndUser_Assignment' }).AdditionalProperties.notificationRecipients + $EligibleAssignmentAlertNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Admin_EndUser_Assignment' }).AdditionalProperties.notificationLevel) -contains ('Critical') + $EligibleAssignmentAssigneeNotificationDefaultRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_EndUser_Assignment' }).AdditionalProperties.isDefaultRecipientsEnabled + [string[]]$EligibleAssignmentAssigneeNotificationAdditionalRecipientValue = ($role | Where-Object { $_.Id -eq 'Notification_Requestor_EndUser_Assignment' }).AdditionalProperties.notificationRecipients + $EligibleAssignmentAssigneeNotificationOnlyCriticalValue = (($role | Where-Object { $_.Id -eq 'Notification_Requestor_EndUser_Assignment' }).AdditionalProperties.notificationLevel) -contains ('Critical') } try @@ -401,47 +401,47 @@ function Get-TargetResource Id = $Id DisplayName = $DisplayName RoleDefinitionId = $RoleDefinitionId - ActivationMaxDuration = $ActivationMaxDuration - ActivationReqJustification = $ActivationReqJustification - ActivationReqTicket = $ActivationReqTicket - ActivationReqMFA = $ActivationReqMFA - ApprovaltoActivate = $ApprovaltoActivate + ActivationMaxDuration = $ActivationMaxDurationValue + ActivationReqJustification = $ActivationReqJustificationValue + ActivationReqTicket = $ActivationReqTicketValue + ActivationReqMFA = $ActivationReqMFAValue + ApprovaltoActivate = $ApprovaltoActivateValue ActivateApprover = [System.String[]]$ActivateApprover - PermanentEligibleAssignmentisExpirationRequired = $PermanentEligibleAssignmentisExpirationRequired - ExpireEligibleAssignment = $ExpireEligibleAssignment - PermanentActiveAssignmentisExpirationRequired = $PermanentActiveAssignmentisExpirationRequired - ExpireActiveAssignment = $ExpireActiveAssignment - AssignmentReqMFA = $AssignmentReqMFA - AssignmentReqJustification = $AssignmentReqJustification - ElegibilityAssignmentReqMFA = $ElegibilityAssignmentReqMFA - ElegibilityAssignmentReqJustification = $ElegibilityAssignmentReqJustification - EligibleAlertNotificationDefaultRecipient = $EligibleAlertNotificationDefaultRecipient - EligibleAlertNotificationAdditionalRecipient = [System.String[]]$EligibleAlertNotificationAdditionalRecipient - EligibleAlertNotificationOnlyCritical = $EligibleAlertNotificationOnlyCritical - EligibleAssigneeNotificationDefaultRecipient = $EligibleAssigneeNotificationDefaultRecipient - EligibleAssigneeNotificationAdditionalRecipient = [System.String[]]$EligibleAssigneeNotificationAdditionalRecipient - EligibleAssigneeNotificationOnlyCritical = $EligibleAssigneeNotificationOnlyCritical - EligibleApproveNotificationDefaultRecipient = $EligibleApproveNotificationDefaultRecipient - EligibleApproveNotificationAdditionalRecipient = [System.String[]]$EligibleApproveNotificationAdditionalRecipient - EligibleApproveNotificationOnlyCritical = $EligibleApproveNotificationOnlyCritical - ActiveAlertNotificationDefaultRecipient = $ActiveAlertNotificationDefaultRecipient - ActiveAlertNotificationAdditionalRecipient = [System.String[]]$ActiveAlertNotificationAdditionalRecipient - ActiveAlertNotificationOnlyCritical = $ActiveAlertNotificationOnlyCritical - ActiveAssigneeNotificationDefaultRecipient = $ActiveAssigneeNotificationDefaultRecipient - ActiveAssigneeNotificationAdditionalRecipient = [System.String[]]$ActiveAssigneeNotificationAdditionalRecipient - ActiveAssigneeNotificationOnlyCritical = $ActiveAssigneeNotificationOnlyCritical - ActiveApproveNotificationDefaultRecipient = $ActiveApproveNotificationDefaultRecipient - ActiveApproveNotificationAdditionalRecipient = [System.String[]]$ActiveApproveNotificationAdditionalRecipient - ActiveApproveNotificationOnlyCritical = $ActiveApproveNotificationOnlyCritical - EligibleAssignmentAlertNotificationDefaultRecipient = $EligibleAssignmentAlertNotificationDefaultRecipient - EligibleAssignmentAlertNotificationAdditionalRecipient = [System.String[]]$EligibleAssignmentAlertNotificationAdditionalRecipient - EligibleAssignmentAlertNotificationOnlyCritical = $EligibleAssignmentAlertNotificationOnlyCritical - EligibleAssignmentAssigneeNotificationDefaultRecipient = $EligibleAssignmentAssigneeNotificationDefaultRecipient - EligibleAssignmentAssigneeNotificationAdditionalRecipient = [System.String[]]$EligibleAssignmentAssigneeNotificationAdditionalRecipient - EligibleAssignmentAssigneeNotificationOnlyCritical = $EligibleAssignmentAssigneeNotificationOnlyCritical - AuthenticationContextRequired = $AuthenticationContextRequired - AuthenticationContextId = $AuthenticationContextId - AuthenticationContextName = $AuthenticationContextName + PermanentEligibleAssignmentisExpirationRequired = $PermanentEligibleAssignmentisExpirationRequiredValue + ExpireEligibleAssignment = $ExpireEligibleAssignmentValue + PermanentActiveAssignmentisExpirationRequired = $PermanentActiveAssignmentisExpirationRequiredValue + ExpireActiveAssignment = $ExpireActiveAssignmentValue + AssignmentReqMFA = $AssignmentReqMFAValue + AssignmentReqJustification = $AssignmentReqJustificationValue + ElegibilityAssignmentReqMFA = $ElegibilityAssignmentReqMFAValue + ElegibilityAssignmentReqJustification = $ElegibilityAssignmentReqJustificationValue + EligibleAlertNotificationDefaultRecipient = $EligibleAlertNotificationDefaultRecipientValue + EligibleAlertNotificationAdditionalRecipient = [System.String[]]$EligibleAlertNotificationAdditionalRecipientValue + EligibleAlertNotificationOnlyCritical = $EligibleAlertNotificationOnlyCriticalValue + EligibleAssigneeNotificationDefaultRecipient = $EligibleAssigneeNotificationDefaultRecipientValue + EligibleAssigneeNotificationAdditionalRecipient = [System.String[]]$EligibleAssigneeNotificationAdditionalRecipientValue + EligibleAssigneeNotificationOnlyCritical = $EligibleAssigneeNotificationOnlyCriticalValue + EligibleApproveNotificationDefaultRecipient = $EligibleApproveNotificationDefaultRecipientValue + EligibleApproveNotificationAdditionalRecipient = [System.String[]]$EligibleApproveNotificationAdditionalRecipientValue + EligibleApproveNotificationOnlyCritical = $EligibleApproveNotificationOnlyCriticalValue + ActiveAlertNotificationDefaultRecipient = $ActiveAlertNotificationDefaultRecipientValue + ActiveAlertNotificationAdditionalRecipient = [System.String[]]$ActiveAlertNotificationAdditionalRecipientValue + ActiveAlertNotificationOnlyCritical = $ActiveAlertNotificationOnlyCriticalValue + ActiveAssigneeNotificationDefaultRecipient = $ActiveAssigneeNotificationDefaultRecipientValue + ActiveAssigneeNotificationAdditionalRecipient = [System.String[]]$ActiveAssigneeNotificationAdditionalRecipientValue + ActiveAssigneeNotificationOnlyCritical = $ActiveAssigneeNotificationOnlyCriticalValue + ActiveApproveNotificationDefaultRecipient = $ActiveApproveNotificationDefaultRecipientValue + ActiveApproveNotificationAdditionalRecipient = [System.String[]]$ActiveApproveNotificationAdditionalRecipientValue + ActiveApproveNotificationOnlyCritical = $ActiveApproveNotificationOnlyCriticalValue + EligibleAssignmentAlertNotificationDefaultRecipient = $EligibleAssignmentAlertNotificationDefaultRecipientValue + EligibleAssignmentAlertNotificationAdditionalRecipient = [System.String[]]$EligibleAssignmentAlertNotificationAdditionalRecipientValue + EligibleAssignmentAlertNotificationOnlyCritical = $EligibleAssignmentAlertNotificationOnlyCriticalValue + EligibleAssignmentAssigneeNotificationDefaultRecipient = $EligibleAssignmentAssigneeNotificationDefaultRecipientValue + EligibleAssignmentAssigneeNotificationAdditionalRecipient = [System.String[]]$EligibleAssignmentAssigneeNotificationAdditionalRecipientValue + EligibleAssignmentAssigneeNotificationOnlyCritical = $EligibleAssignmentAssigneeNotificationOnlyCriticalValue + AuthenticationContextRequired = $AuthenticationContextRequiredValue + AuthenticationContextId = $AuthenticationContextIdValue + AuthenticationContextName = $AuthenticationContextNameValue Ensure = 'Present' ApplicationId = $ApplicationId TenantId = $TenantId diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADPasswordRuleSettings/MSFT_AADPasswordRuleSettings.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADPasswordRuleSettings/MSFT_AADPasswordRuleSettings.psm1 index 4d91b675b6..15679f2c9e 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADPasswordRuleSettings/MSFT_AADPasswordRuleSettings.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADPasswordRuleSettings/MSFT_AADPasswordRuleSettings.psm1 @@ -107,6 +107,12 @@ function Get-TargetResource $valueLockoutThreshold = $Policy.Values | Where-Object -FilterScript { $_.Name -eq 'LockoutThreshold' } $valueBannedPasswordList = $Policy.Values | Where-Object -FilterScript { $_.Name -eq 'BannedPasswordList' } + $bannedPasswordListArray = @() + # Splitting a null value results in an array with one empty string + if ($valueBannedPasswordList.Value.Count -gt 0 -and -not [System.String]::IsNullOrEmpty($valueBannedPasswordList.Value[0])) + { + $bannedPasswordListArray = $valueBannedPasswordList.Value -split "`t" # list is tab-delimited + } $result = @{ IsSingleInstance = 'Yes' BannedPasswordCheckOnPremisesMode = $valueBannedPasswordCheckOnPremisesMode.Value @@ -114,7 +120,7 @@ function Get-TargetResource EnableBannedPasswordCheck = [Boolean]::Parse($valueEnableBannedPasswordCheck.Value) LockoutDurationInSeconds = $valueLockoutDurationInSeconds.Value LockoutThreshold = $valueLockoutThreshold.Value - BannedPasswordList = $valueBannedPasswordList.Value -split "`t" # list is tab-delimited + BannedPasswordList = $bannedPasswordListArray Ensure = 'Present' ApplicationId = $ApplicationId TenantId = $TenantId diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADVerifiedIdAuthority/MSFT_AADVerifiedIdAuthority.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADVerifiedIdAuthority/MSFT_AADVerifiedIdAuthority.psm1 index f47ea19a30..18d62b1eb4 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADVerifiedIdAuthority/MSFT_AADVerifiedIdAuthority.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADVerifiedIdAuthority/MSFT_AADVerifiedIdAuthority.psm1 @@ -454,7 +454,7 @@ function Export-TargetResource function Get-M365DSCVerifiedIdAuthorityObject { [CmdletBinding()] - [OutputType([PSCustomObject])] + [OutputType([System.Collections.Hashtable])] param( [Parameter()] $Authority @@ -489,7 +489,7 @@ function Get-M365DSCVerifiedIdAuthorityObject function Invoke-M365DSCVerifiedIdWebRequest { - [OutputType([PSCustomObject])] + [OutputType([System.Collections.Hashtable])] [CmdletBinding()] param( [Parameter(Mandatory = $true)] diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADVerifiedIdAuthorityContract/MSFT_AADVerifiedIdAuthorityContract.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADVerifiedIdAuthorityContract/MSFT_AADVerifiedIdAuthorityContract.psm1 index 8d1196cbe3..531168507a 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADVerifiedIdAuthorityContract/MSFT_AADVerifiedIdAuthorityContract.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADVerifiedIdAuthorityContract/MSFT_AADVerifiedIdAuthorityContract.psm1 @@ -798,7 +798,7 @@ function Get-M365DSCVerifiedIdAuthorityContractObject function Get-M365DSCVerifiedIdAuthorityObject { [CmdletBinding()] - [OutputType([PSCustomObject])] + [OutputType([System.Collections.Hashtable])] param( [Parameter()] $Authority @@ -833,7 +833,7 @@ function Get-M365DSCVerifiedIdAuthorityObject function Invoke-M365DSCVerifiedIdWebRequest { - [OutputType([PSCustomObject])] + [OutputType([System.Collections.Hashtable])] [CmdletBinding()] param( [Parameter(Mandatory = $true)] diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_EXOIRMConfiguration/MSFT_EXOIRMConfiguration.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_EXOIRMConfiguration/MSFT_EXOIRMConfiguration.psm1 index 7526559b5a..91c75dab24 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_EXOIRMConfiguration/MSFT_EXOIRMConfiguration.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_EXOIRMConfiguration/MSFT_EXOIRMConfiguration.psm1 @@ -40,7 +40,7 @@ function Get-TargetResource $JournalReportDecryptionEnabled, [Parameter()] - [System.Uri[]] + [System.String[]] $LicensingLocation, [Parameter()] @@ -48,7 +48,7 @@ function Get-TargetResource $RejectIfRecipientHasNoRights, [Parameter()] - [System.Uri] + [System.String] $RMSOnlineKeySharingLocation, [Parameter()] @@ -236,7 +236,7 @@ function Set-TargetResource $JournalReportDecryptionEnabled, [Parameter()] - [System.Uri[]] + [System.String[]] $LicensingLocation, [Parameter()] @@ -244,7 +244,7 @@ function Set-TargetResource $RejectIfRecipientHasNoRights, [Parameter()] - [System.Uri] + [System.String] $RMSOnlineKeySharingLocation, [Parameter()] @@ -374,7 +374,7 @@ function Test-TargetResource $JournalReportDecryptionEnabled, [Parameter()] - [System.Uri[]] + [System.String[]] $LicensingLocation, [Parameter()] @@ -382,7 +382,7 @@ function Test-TargetResource $RejectIfRecipientHasNoRights, [Parameter()] - [System.Uri] + [System.String] $RMSOnlineKeySharingLocation, [Parameter()] diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_EXOMigration/MSFT_EXOMigration.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_EXOMigration/MSFT_EXOMigration.psm1 index 1dfdade295..3d511e11c1 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_EXOMigration/MSFT_EXOMigration.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_EXOMigration/MSFT_EXOMigration.psm1 @@ -129,7 +129,11 @@ function Get-TargetResource Write-Verbose -Message "Migration Batch with Identity $Identity found" $Users = Get-MigrationUser -BatchId $Identity - $UserEmails = $Users | ForEach-Object { $_.Identity } + $UserEmails = @() + foreach ($user in $Users) + { + $UserEmails += $user.Identity + } $results = @{ Identity = $Identity @@ -344,7 +348,7 @@ function Set-TargetResource $csvFilePath = "$env:TEMP\MigrationUsers.csv" # Convert each item in the array to a custom object with an EmailAddress property - $csvContent = $MigrationUsers | ForEach-Object { [PSCustomObject]@{EmailAddress = $_ } } + $csvContent = $MigrationUsers | ForEach-Object { @{EmailAddress = $_ } } # Export to CSV with the header "EmailAddress" $csvContent | Export-Csv -Path $csvFilePath -NoTypeInformation -Force diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_FabricAdminTenantSettings/MSFT_FabricAdminTenantSettings.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_FabricAdminTenantSettings/MSFT_FabricAdminTenantSettings.psm1 index d3820e26e1..be0cc4a6f1 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_FabricAdminTenantSettings/MSFT_FabricAdminTenantSettings.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_FabricAdminTenantSettings/MSFT_FabricAdminTenantSettings.psm1 @@ -2595,7 +2595,7 @@ function Export-TargetResource function Get-M365DSCFabricTenantSettingObject { [CmdletBinding()] - [OutputType([PSCustomObject])] + [OutputType([System.Collections.Hashtable])] param( [Parameter()] $Setting diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCompliancePolicyAndroidDeviceOwner/MSFT_IntuneDeviceCompliancePolicyAndroidDeviceOwner.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCompliancePolicyAndroidDeviceOwner/MSFT_IntuneDeviceCompliancePolicyAndroidDeviceOwner.psm1 index 28cb848ac5..71d38ea2bf 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCompliancePolicyAndroidDeviceOwner/MSFT_IntuneDeviceCompliancePolicyAndroidDeviceOwner.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCompliancePolicyAndroidDeviceOwner/MSFT_IntuneDeviceCompliancePolicyAndroidDeviceOwner.psm1 @@ -219,7 +219,6 @@ function Get-TargetResource $scheduledActionsForRuleHashTable = @{} $psCustomObject.PsObject.Properties | ForEach-Object { $scheduledActionsForRuleHashTable[$_.Name] = $_.Value - } $hashtable = @{} $complexScheduledActionsForRule = @() diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceEnrollmentStatusPageWindows10/MSFT_IntuneDeviceEnrollmentStatusPageWindows10.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceEnrollmentStatusPageWindows10/MSFT_IntuneDeviceEnrollmentStatusPageWindows10.psm1 index 6608b9b939..bbb809a9dd 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceEnrollmentStatusPageWindows10/MSFT_IntuneDeviceEnrollmentStatusPageWindows10.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceEnrollmentStatusPageWindows10/MSFT_IntuneDeviceEnrollmentStatusPageWindows10.psm1 @@ -632,7 +632,13 @@ function Test-TargetResource if ($PSBoundParameters.ContainsKey('SelectedMobileAppIds') -eq $true -and $PSBoundParameters.ContainsKey('SelectedMobileAppNames') -eq $false) { Write-Verbose -Message 'Converting SelectedMobileAppIds to SelectedMobileAppNames' - $PSBoundParameters.SelectedMobileAppNames = $SelectedMobileAppIds | ForEach-Object { (Get-MgBetaDeviceAppManagementMobileApp -MobileAppId $_).DisplayName } + $resolvedNames = @() + foreach ($appId in $SelectedMobileAppIds) + { + $mobileEntry = Get-MgBetaDeviceAppManagementMobileApp -MobileAppId $appId + $resolvedNames += $mobileEntry.DisplayName + } + $PSBoundParameters.SelectedMobileAppNames = $resolvedNames } $PSBoundParameters.Remove('SelectedMobileAppIds') | Out-Null diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneSettingCatalogCustomPolicyWindows10/MSFT_IntuneSettingCatalogCustomPolicyWindows10.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneSettingCatalogCustomPolicyWindows10/MSFT_IntuneSettingCatalogCustomPolicyWindows10.psm1 index 34bcc33b41..4a8d622315 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneSettingCatalogCustomPolicyWindows10/MSFT_IntuneSettingCatalogCustomPolicyWindows10.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneSettingCatalogCustomPolicyWindows10/MSFT_IntuneSettingCatalogCustomPolicyWindows10.psm1 @@ -599,11 +599,6 @@ function Export-TargetResource if ($null -ne $Results.Settings) { $complexMapping = @( - @{ - Name = 'Settings' - CimInstanceName = 'MicrosoftGraphDeviceManagementConfigurationSetting' - IsRequired = $False - } @{ Name = 'SettingInstance' CimInstanceName = 'MicrosoftGraphDeviceManagementConfigurationSettingInstance' @@ -658,9 +653,10 @@ function Export-TargetResource $complexTypeStringResult = Get-M365DSCDRGComplexTypeToString ` -ComplexObject $Results.Settings ` -CIMInstanceName 'MicrosoftGraphdeviceManagementConfigurationSetting' ` - -ComplexTypeMapping $complexMapping + -ComplexTypeMapping $complexMapping ` + -IsArray:$true - if (-Not [String]::IsNullOrWhiteSpace($complexTypeStringResult)) + if (-not [String]::IsNullOrWhiteSpace($complexTypeStringResult)) { $Results.Settings = $complexTypeStringResult } diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_PPTenantIsolationSettings/MSFT_PPTenantIsolationSettings.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_PPTenantIsolationSettings/MSFT_PPTenantIsolationSettings.psm1 index 8b102891d2..4c38cc5f05 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_PPTenantIsolationSettings/MSFT_PPTenantIsolationSettings.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_PPTenantIsolationSettings/MSFT_PPTenantIsolationSettings.psm1 @@ -324,7 +324,7 @@ function Set-TargetResource $ruleTenantId = Get-M365TenantId -TenantName $rule.TenantName Write-Verbose -Message "Found TenantName {$($rule.TenantName)}" - $direction = [PSCustomObject]@{ + $direction = @{ inbound = $false outbound = $false } @@ -351,7 +351,7 @@ function Set-TargetResource } } - $newRule = [PSCustomObject]@{ + $newRule = @{ tenantId = $ruleTenantId tenantDisplayName = '' direction = $direction diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_SCComplianceTag/MSFT_SCComplianceTag.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_SCComplianceTag/MSFT_SCComplianceTag.psm1 index f9255023a0..2f5a7998d8 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_SCComplianceTag/MSFT_SCComplianceTag.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_SCComplianceTag/MSFT_SCComplianceTag.psm1 @@ -558,7 +558,7 @@ function Export-TargetResource function Get-SCFilePlanPropertyObject { [CmdletBinding()] - [OutputType([PSCustomObject])] + [OutputType([System.Collections.Hashtable])] param ( [Parameter()] @@ -570,7 +570,7 @@ function Get-SCFilePlanPropertyObject return $null } - $result = [PSCustomObject]@{ + $result = @{ Settings = @( @{Key = 'FilePlanPropertyDepartment'; Value = $properties.FilePlanPropertyDepartment }, @{Key = 'FilePlanPropertyCategory'; Value = $properties.FilePlanPropertyCategory }, diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_SCPolicyConfig/MSFT_SCPolicyConfig.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_SCPolicyConfig/MSFT_SCPolicyConfig.psm1 index 25a295633f..1573055327 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_SCPolicyConfig/MSFT_SCPolicyConfig.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_SCPolicyConfig/MSFT_SCPolicyConfig.psm1 @@ -386,7 +386,7 @@ function Get-TargetResource $current = [ordered]@{ Id = $entity.Id Enable = [Boolean]$entity.Enable - justificationText = $entity.justificationText + justificationText = $entity.justificationText | Select-Object -First 1 # Contains only one value } $BusinessJustificationListValue += $current } diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_SCSensitivityLabel/MSFT_SCSensitivityLabel.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_SCSensitivityLabel/MSFT_SCSensitivityLabel.psm1 index 4ab94729ab..a7c9a5aad8 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_SCSensitivityLabel/MSFT_SCSensitivityLabel.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_SCSensitivityLabel/MSFT_SCSensitivityLabel.psm1 @@ -1,84 +1,84 @@ Confirm-M365DSCModuleDependency -ModuleName 'MSFT_SCSensitivityLabel' $allTrainableClassifiers = @( - [PSCustomObject]@{ Name = 'Actuary reports'; Id = 'b27df2ee-fd14-4ce9-b02f-4070a5d68132' } - [PSCustomObject]@{ Name = 'Agreements'; Id = '7f12e403-5335-4da8-a91e-6c2210b7a2b1' } - [PSCustomObject]@{ Name = 'Asset Management'; Id = '716fb550-90cd-493b-b29b-ceed41ee8a6f' } - [PSCustomObject]@{ Name = 'Bank statement'; Id = 'f426bd16-e42e-4397-824b-f17dedc5bb1c' } - [PSCustomObject]@{ Name = 'Budget'; Id = '6f207592-f71e-4b4f-8c07-ebc4bd4965b9' } - [PSCustomObject]@{ Name = 'Business Context'; Id = '08b772df-bf93-457f-be23-b5cbf02005fd' } - [PSCustomObject]@{ Name = 'Business plan'; Id = '693f8221-ae4e-4612-80f5-746efee167c3' } - [PSCustomObject]@{ Name = 'Completion Certificates'; Id = 'b2580781-286b-4ad2-ab47-84e84ff331e5' } - [PSCustomObject]@{ Name = 'Compliance policies'; Id = 'fdad8089-651b-4877-8b66-be105b2e57da' } - [PSCustomObject]@{ Name = 'Construction specifications'; Id = 'bfde18ef-b4b9-4f30-9965-ef8d00861a2c' } - [PSCustomObject]@{ Name = 'Control System and SCADA files'; Id = '59f1f471-687d-453b-a73e-0b0e9f350812' } - [PSCustomObject]@{ Name = 'Corporate Sabotage'; Id = 'd88960c3-6101-43d9-9250-8c43c71d638a' } - [PSCustomObject]@{ Name = 'Credit Report'; Id = '07ce7d30-690a-4a1c-a331-8df9c944f1ab' } - [PSCustomObject]@{ Name = 'Customer Complaints'; Id = '8137d8fc-fb7a-40db-9009-284f962fde96' } - [PSCustomObject]@{ Name = 'Customer Files'; Id = 'fdff9df2-03ba-4372-be97-82c0d2515118' } - [PSCustomObject]@{ Name = 'Discrimination'; Id = 'a65c4ab6-a155-11eb-921c-6c0b84aa8ea5' } - [PSCustomObject]@{ Name = 'Employee disciplinary action files'; Id = '769d56c1-e737-4fc1-8673-8c99bbe24a07' } - [PSCustomObject]@{ Name = 'Employee Insurance files'; Id = 'fa982a9f-9454-4885-a2bf-94a155df2f33' } - [PSCustomObject]@{ Name = 'Employee Pension Records'; Id = 'f9ae0bbc-a1e0-4b7e-a96a-eb60b26b4434' } - [PSCustomObject]@{ Name = 'Employee Stocks and Financial Bond Records'; Id = 'a67b2b59-c5f0-4c66-a6c4-ca6973adfd94' } - [PSCustomObject]@{ Name = 'Employment Agreement'; Id = '2a2baab7-b82c-4166-bbe4-55f9d3fd1129' } - [PSCustomObject]@{ Name = 'Enterprise Risk Management'; Id = 'eed09aae-6f32-47c7-9c99-9d17bad48783' } - [PSCustomObject]@{ Name = 'Environmental permits and clearances'; Id = '1b7d3e51-0ecf-41bd-9794-966c94a889ba' } - [PSCustomObject]@{ Name = 'Facility Permits'; Id = '914c5379-9d05-47cb-98f0-f5a2be059b5a' } - [PSCustomObject]@{ Name = 'factory Incident Investigation reports'; Id = '86186144-d507-4603-bac7-50b56ba05c70' } - [PSCustomObject]@{ Name = 'Finance'; Id = '1771481d-a337-4dbf-8e64-af8da0cc3ee9' } - [PSCustomObject]@{ Name = 'Finance policies and procedures'; Id = '6556c5eb-0819-4618-ba2e-59925925655e' } - [PSCustomObject]@{ Name = 'Financial Audit Reports'; Id = 'b04b2a4e-22f8-4024-8adc-e2caaad1c2e2' } - [PSCustomObject]@{ Name = 'Financial statement'; Id = 'c31bfef9-8045-4a35-88a3-74b8681615c2' } - [PSCustomObject]@{ Name = 'Freight Documents'; Id = '785917ed-db01-43c7-8153-8a6fc393efa3' } - [PSCustomObject]@{ Name = 'Garnishment'; Id = '65e827c3-f8e8-4bc8-b08c-c31e3132b832' } - [PSCustomObject]@{ Name = 'Gifts \u0026 entertainment'; Id = '3b3d817a-9190-465b-af2d-9e856f894059' } - [PSCustomObject]@{ Name = 'Health/Medical forms'; Id = '7cc60f30-9e96-4d51-b26f-3d7a9df56338' } - [PSCustomObject]@{ Name = 'Healthcare'; Id = 'dcbada08-65bf-4561-b140-25d8fee4d143' } - [PSCustomObject]@{ Name = 'HR'; Id = '11631f87-7ffe-4052-b173-abda16b231f3' } - [PSCustomObject]@{ Name = 'Invoice'; Id = 'bf7df7c3-fce4-4ffd-ab90-26f6463f3a00' } - [PSCustomObject]@{ Name = 'IP'; Id = '495fad07-d6e4-4da4-9c64-5b9b109a5f59' } - [PSCustomObject]@{ Name = 'IT'; Id = '77a140be-c29f-4155-9dc4-c3e247e47560' } - [PSCustomObject]@{ Name = 'IT Infra and Network Security Documents'; Id = 'bc55de38-cb72-43e6-952f-8422f584f229' } - [PSCustomObject]@{ Name = 'Lease Deeds'; Id = '841f54ad-3e31-4ddd-aea0-e7f0cd6b3d18' } - [PSCustomObject]@{ Name = 'Legal Affairs'; Id = 'ba38aa0f-8c86-4c73-87db-95147a0f4420' } - [PSCustomObject]@{ Name = 'Legal Agreements'; Id = 'bee9cefb-88bd-410f-ab3e-67cab21cef46' } - [PSCustomObject]@{ Name = 'Letter of Credits'; Id = 'fd85acd5-59dd-49b2-a4c3-df7075885a82' } - [PSCustomObject]@{ Name = 'License agreement'; Id = 'b399eb17-c9c4-4205-951b-43f38eb8dffe' } - [PSCustomObject]@{ Name = 'Loan agreements and offer letters'; Id = '5771fa57-34a1-48b3-93df-778b304daa54' } - [PSCustomObject]@{ Name = 'M&A Files'; Id = 'eeffbf7c-fd04-40ef-a156-b37bf61832f7' } - [PSCustomObject]@{ Name = 'Manufacturing batch records'; Id = '834b2353-509a-4605-b4f1-fc2172a0d97c' } - [PSCustomObject]@{ Name = 'Marketing Collaterals'; Id = 'fcaa6d2a-601c-4bdc-947e-af1178a646ac' } - [PSCustomObject]@{ Name = 'Meeting notes'; Id = 'e7ff9a9e-4689-4192-b927-e6c6bdf099fc' } - [PSCustomObject]@{ Name = 'Money laundering'; Id = 'adbbb20e-b175-46e7-8ba2-cf3f3179d0ed' } - [PSCustomObject]@{ Name = 'MoU Files (Memorandum of understanding)'; Id = 'cb37c277-4b88-49c6-81fb-2eeca8c52bb9' } - [PSCustomObject]@{ Name = 'Network Design files'; Id = '12587d70-9596-4c21-b09f-f1abe9d6ca13' } - [PSCustomObject]@{ Name = 'Non disclosure agreement'; Id = '8dfd10db-0c72-4be4-a4f2-f615fe7aeb1c' } - [PSCustomObject]@{ Name = 'OSHA records'; Id = 'b11b771e-7dd1-4434-873a-d648a16e969e' } - [PSCustomObject]@{ Name = 'Paystub'; Id = '31c11384-2d64-4635-9335-018295c64268' } - [PSCustomObject]@{ Name = 'Personal Financial Information'; Id = '6901c616-5857-432f-b3da-f5234fa1d342' } - [PSCustomObject]@{ Name = 'Procurement'; Id = '8fa64a47-6e77-4b4c-91a5-0f67525cebf5' } - [PSCustomObject]@{ Name = 'Profanity'; Id = '4b0aa61d-37dc-4596-a1f1-fc5a5b21d56b' } - [PSCustomObject]@{ Name = 'Project documents'; Id = 'e062df90-816c-47ca-8913-db647510d3b5' } - [PSCustomObject]@{ Name = 'Quality assurance files'; Id = '97b1e0d3-7788-4dd4-bb18-48ea77796743' } - [PSCustomObject]@{ Name = 'Quotation'; Id = '3882e681-c437-42d8-ac75-1f9b7481fe13' } - [PSCustomObject]@{ Name = 'Regulatory Collusion'; Id = '911b7815-6883-4022-a882-9cbe9462f114' } - [PSCustomObject]@{ Name = 'Resume'; Id = '14b2da41-0427-47e9-a11b-c924e1d05689' } - [PSCustomObject]@{ Name = 'Safety Records'; Id = '938fb100-5b1f-4bbb-aba7-73d9c89d086f' } - [PSCustomObject]@{ Name = 'Sales and revenue'; Id = '9d6b864d-28c6-4be3-a9d0-cd40434a847f' } - [PSCustomObject]@{ Name = 'Software Product Development Files'; Id = '813aa6d8-0727-48d8-acb7-06e1819ee339' } - [PSCustomObject]@{ Name = 'Source code'; Id = '8aef6743-61aa-44b9-9ae5-3bb3d77df535' } - [PSCustomObject]@{ Name = 'Standard Operating Procedures and Manuals'; Id = '32f23ad4-2ca1-4495-8048-8dc567891644' } - [PSCustomObject]@{ Name = 'Statement of Accounts'; Id = 'fe3676a6-0f5d-4990-bb46-9b2b31d7746a' } - [PSCustomObject]@{ Name = 'Statement of Work'; Id = '611c95f9-b1ef-4253-8b36-d8ae19d02fb0' } - [PSCustomObject]@{ Name = 'Stock manipulation'; Id = '1140cd79-ad87-4043-a562-c768acacc6ba' } - [PSCustomObject]@{ Name = 'Strategic planning documents'; Id = '9332b317-2ca4-413a-b983-92a1bd88c6f3' } - [PSCustomObject]@{ Name = 'Targeted Harassment'; Id = 'a02ddb8e-3c93-44ac-87c1-2f682b1cb78e' } - [PSCustomObject]@{ Name = 'Tax'; Id = '9722b51a-f920-4a81-8390-b188a0692840' } - [PSCustomObject]@{ Name = 'Threat'; Id = 'ef2edb64-6982-4648-b0ad-c0d8a861501b' } - [PSCustomObject]@{ Name = 'Unauthorized disclosure'; Id = '839aecf8-c67b-4270-8aaf-378127b23b7f' } - [PSCustomObject]@{ Name = 'Wire transfer'; Id = '05fc5ed0-58ef-4306-b65c-11b0a43895c2' } - [PSCustomObject]@{ Name = 'Work Schedules'; Id = '25bb9d2d-a5b5-45b1-882e-b2581a183873' } + @{ Name = 'Actuary reports'; Id = 'b27df2ee-fd14-4ce9-b02f-4070a5d68132' } + @{ Name = 'Agreements'; Id = '7f12e403-5335-4da8-a91e-6c2210b7a2b1' } + @{ Name = 'Asset Management'; Id = '716fb550-90cd-493b-b29b-ceed41ee8a6f' } + @{ Name = 'Bank statement'; Id = 'f426bd16-e42e-4397-824b-f17dedc5bb1c' } + @{ Name = 'Budget'; Id = '6f207592-f71e-4b4f-8c07-ebc4bd4965b9' } + @{ Name = 'Business Context'; Id = '08b772df-bf93-457f-be23-b5cbf02005fd' } + @{ Name = 'Business plan'; Id = '693f8221-ae4e-4612-80f5-746efee167c3' } + @{ Name = 'Completion Certificates'; Id = 'b2580781-286b-4ad2-ab47-84e84ff331e5' } + @{ Name = 'Compliance policies'; Id = 'fdad8089-651b-4877-8b66-be105b2e57da' } + @{ Name = 'Construction specifications'; Id = 'bfde18ef-b4b9-4f30-9965-ef8d00861a2c' } + @{ Name = 'Control System and SCADA files'; Id = '59f1f471-687d-453b-a73e-0b0e9f350812' } + @{ Name = 'Corporate Sabotage'; Id = 'd88960c3-6101-43d9-9250-8c43c71d638a' } + @{ Name = 'Credit Report'; Id = '07ce7d30-690a-4a1c-a331-8df9c944f1ab' } + @{ Name = 'Customer Complaints'; Id = '8137d8fc-fb7a-40db-9009-284f962fde96' } + @{ Name = 'Customer Files'; Id = 'fdff9df2-03ba-4372-be97-82c0d2515118' } + @{ Name = 'Discrimination'; Id = 'a65c4ab6-a155-11eb-921c-6c0b84aa8ea5' } + @{ Name = 'Employee disciplinary action files'; Id = '769d56c1-e737-4fc1-8673-8c99bbe24a07' } + @{ Name = 'Employee Insurance files'; Id = 'fa982a9f-9454-4885-a2bf-94a155df2f33' } + @{ Name = 'Employee Pension Records'; Id = 'f9ae0bbc-a1e0-4b7e-a96a-eb60b26b4434' } + @{ Name = 'Employee Stocks and Financial Bond Records'; Id = 'a67b2b59-c5f0-4c66-a6c4-ca6973adfd94' } + @{ Name = 'Employment Agreement'; Id = '2a2baab7-b82c-4166-bbe4-55f9d3fd1129' } + @{ Name = 'Enterprise Risk Management'; Id = 'eed09aae-6f32-47c7-9c99-9d17bad48783' } + @{ Name = 'Environmental permits and clearances'; Id = '1b7d3e51-0ecf-41bd-9794-966c94a889ba' } + @{ Name = 'Facility Permits'; Id = '914c5379-9d05-47cb-98f0-f5a2be059b5a' } + @{ Name = 'factory Incident Investigation reports'; Id = '86186144-d507-4603-bac7-50b56ba05c70' } + @{ Name = 'Finance'; Id = '1771481d-a337-4dbf-8e64-af8da0cc3ee9' } + @{ Name = 'Finance policies and procedures'; Id = '6556c5eb-0819-4618-ba2e-59925925655e' } + @{ Name = 'Financial Audit Reports'; Id = 'b04b2a4e-22f8-4024-8adc-e2caaad1c2e2' } + @{ Name = 'Financial statement'; Id = 'c31bfef9-8045-4a35-88a3-74b8681615c2' } + @{ Name = 'Freight Documents'; Id = '785917ed-db01-43c7-8153-8a6fc393efa3' } + @{ Name = 'Garnishment'; Id = '65e827c3-f8e8-4bc8-b08c-c31e3132b832' } + @{ Name = 'Gifts \u0026 entertainment'; Id = '3b3d817a-9190-465b-af2d-9e856f894059' } + @{ Name = 'Health/Medical forms'; Id = '7cc60f30-9e96-4d51-b26f-3d7a9df56338' } + @{ Name = 'Healthcare'; Id = 'dcbada08-65bf-4561-b140-25d8fee4d143' } + @{ Name = 'HR'; Id = '11631f87-7ffe-4052-b173-abda16b231f3' } + @{ Name = 'Invoice'; Id = 'bf7df7c3-fce4-4ffd-ab90-26f6463f3a00' } + @{ Name = 'IP'; Id = '495fad07-d6e4-4da4-9c64-5b9b109a5f59' } + @{ Name = 'IT'; Id = '77a140be-c29f-4155-9dc4-c3e247e47560' } + @{ Name = 'IT Infra and Network Security Documents'; Id = 'bc55de38-cb72-43e6-952f-8422f584f229' } + @{ Name = 'Lease Deeds'; Id = '841f54ad-3e31-4ddd-aea0-e7f0cd6b3d18' } + @{ Name = 'Legal Affairs'; Id = 'ba38aa0f-8c86-4c73-87db-95147a0f4420' } + @{ Name = 'Legal Agreements'; Id = 'bee9cefb-88bd-410f-ab3e-67cab21cef46' } + @{ Name = 'Letter of Credits'; Id = 'fd85acd5-59dd-49b2-a4c3-df7075885a82' } + @{ Name = 'License agreement'; Id = 'b399eb17-c9c4-4205-951b-43f38eb8dffe' } + @{ Name = 'Loan agreements and offer letters'; Id = '5771fa57-34a1-48b3-93df-778b304daa54' } + @{ Name = 'M&A Files'; Id = 'eeffbf7c-fd04-40ef-a156-b37bf61832f7' } + @{ Name = 'Manufacturing batch records'; Id = '834b2353-509a-4605-b4f1-fc2172a0d97c' } + @{ Name = 'Marketing Collaterals'; Id = 'fcaa6d2a-601c-4bdc-947e-af1178a646ac' } + @{ Name = 'Meeting notes'; Id = 'e7ff9a9e-4689-4192-b927-e6c6bdf099fc' } + @{ Name = 'Money laundering'; Id = 'adbbb20e-b175-46e7-8ba2-cf3f3179d0ed' } + @{ Name = 'MoU Files (Memorandum of understanding)'; Id = 'cb37c277-4b88-49c6-81fb-2eeca8c52bb9' } + @{ Name = 'Network Design files'; Id = '12587d70-9596-4c21-b09f-f1abe9d6ca13' } + @{ Name = 'Non disclosure agreement'; Id = '8dfd10db-0c72-4be4-a4f2-f615fe7aeb1c' } + @{ Name = 'OSHA records'; Id = 'b11b771e-7dd1-4434-873a-d648a16e969e' } + @{ Name = 'Paystub'; Id = '31c11384-2d64-4635-9335-018295c64268' } + @{ Name = 'Personal Financial Information'; Id = '6901c616-5857-432f-b3da-f5234fa1d342' } + @{ Name = 'Procurement'; Id = '8fa64a47-6e77-4b4c-91a5-0f67525cebf5' } + @{ Name = 'Profanity'; Id = '4b0aa61d-37dc-4596-a1f1-fc5a5b21d56b' } + @{ Name = 'Project documents'; Id = 'e062df90-816c-47ca-8913-db647510d3b5' } + @{ Name = 'Quality assurance files'; Id = '97b1e0d3-7788-4dd4-bb18-48ea77796743' } + @{ Name = 'Quotation'; Id = '3882e681-c437-42d8-ac75-1f9b7481fe13' } + @{ Name = 'Regulatory Collusion'; Id = '911b7815-6883-4022-a882-9cbe9462f114' } + @{ Name = 'Resume'; Id = '14b2da41-0427-47e9-a11b-c924e1d05689' } + @{ Name = 'Safety Records'; Id = '938fb100-5b1f-4bbb-aba7-73d9c89d086f' } + @{ Name = 'Sales and revenue'; Id = '9d6b864d-28c6-4be3-a9d0-cd40434a847f' } + @{ Name = 'Software Product Development Files'; Id = '813aa6d8-0727-48d8-acb7-06e1819ee339' } + @{ Name = 'Source code'; Id = '8aef6743-61aa-44b9-9ae5-3bb3d77df535' } + @{ Name = 'Standard Operating Procedures and Manuals'; Id = '32f23ad4-2ca1-4495-8048-8dc567891644' } + @{ Name = 'Statement of Accounts'; Id = 'fe3676a6-0f5d-4990-bb46-9b2b31d7746a' } + @{ Name = 'Statement of Work'; Id = '611c95f9-b1ef-4253-8b36-d8ae19d02fb0' } + @{ Name = 'Stock manipulation'; Id = '1140cd79-ad87-4043-a562-c768acacc6ba' } + @{ Name = 'Strategic planning documents'; Id = '9332b317-2ca4-413a-b983-92a1bd88c6f3' } + @{ Name = 'Targeted Harassment'; Id = 'a02ddb8e-3c93-44ac-87c1-2f682b1cb78e' } + @{ Name = 'Tax'; Id = '9722b51a-f920-4a81-8390-b188a0692840' } + @{ Name = 'Threat'; Id = 'ef2edb64-6982-4648-b0ad-c0d8a861501b' } + @{ Name = 'Unauthorized disclosure'; Id = '839aecf8-c67b-4270-8aaf-378127b23b7f' } + @{ Name = 'Wire transfer'; Id = '05fc5ed0-58ef-4306-b65c-11b0a43895c2' } + @{ Name = 'Work Schedules'; Id = '25bb9d2d-a5b5-45b1-882e-b2581a183873' } ) function Get-TargetResource diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsTenantDialPlan/MSFT_TeamsTenantDialPlan.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsTenantDialPlan/MSFT_TeamsTenantDialPlan.psm1 index 90301399ea..c15b796675 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsTenantDialPlan/MSFT_TeamsTenantDialPlan.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsTenantDialPlan/MSFT_TeamsTenantDialPlan.psm1 @@ -562,7 +562,7 @@ function Get-M365DSCVoiceNormalizationRulesDifference function Get-M365DSCNormalizationRules { [CmdletBinding()] - [OutputType([PSCustomObject])] + [OutputType([System.Collections.Hashtable[]])] param ( [Parameter(Mandatory = $true)] diff --git a/Modules/Microsoft365DSC/Dependencies/Assemblies/.gitkeep b/Modules/Microsoft365DSC/Dependencies/Assemblies/.gitkeep new file mode 100644 index 0000000000..61cbea64cc --- /dev/null +++ b/Modules/Microsoft365DSC/Dependencies/Assemblies/.gitkeep @@ -0,0 +1 @@ +# This directory contains compiled C# assemblies. diff --git a/Modules/Microsoft365DSC/Dependencies/Manifest.psd1 b/Modules/Microsoft365DSC/Dependencies/Manifest.psd1 index 7455c78ea7..da4f7cf1a1 100644 --- a/Modules/Microsoft365DSC/Dependencies/Manifest.psd1 +++ b/Modules/Microsoft365DSC/Dependencies/Manifest.psd1 @@ -141,13 +141,6 @@ # https://github.com/ykuijs/M365DSC_CICD/issues/53 #DependsOn = @('Microsoft.Graph.Authentication') }, - @{ - ModuleName = 'PSDesiredStateConfiguration' - RequiredVersion = '2.0.7' - PowerShellCore = $true - ExplicitLoading = $true - Prefix = 'Pwsh' - }, @{ ModuleName = 'ReverseDSC' RequiredVersion = '2.0.0.31' diff --git a/Modules/Microsoft365DSC/Import-M365DSCDllLoaderModule.ps1 b/Modules/Microsoft365DSC/Import-M365DSCDllLoaderModule.ps1 new file mode 100644 index 0000000000..1397fa1734 --- /dev/null +++ b/Modules/Microsoft365DSC/Import-M365DSCDllLoaderModule.ps1 @@ -0,0 +1,3 @@ +# Because modules are imported in alphabetical order using NestedModules in the module manifest, +# we need to import the DLL Loader module before everything else with ScriptsToProcess +Import-Module -Name "$PSScriptRoot/Modules/M365DSCDllLoader.psm1" -Global -Force diff --git a/Modules/Microsoft365DSC/Microsoft365DSC.psd1 b/Modules/Microsoft365DSC/Microsoft365DSC.psd1 index 8c17be59b2..fce80aa108 100644 --- a/Modules/Microsoft365DSC/Microsoft365DSC.psd1 +++ b/Modules/Microsoft365DSC/Microsoft365DSC.psd1 @@ -56,6 +56,7 @@ # Script files (.ps1) that are run in the caller's environment prior to importing this module. ScriptsToProcess = @( + 'Import-M365DSCDllLoaderModule.ps1', 'Update-MaximumFunctionCount.ps1' ) @@ -72,6 +73,7 @@ 'Modules/M365DSCDocGenerator.psm1', 'Modules/M365DSCErrorHandler.psm1', 'Modules/M365DSCLogEngine.psm1', + 'Modules/M365DSCModuleMgmt.psm1', 'Modules/M365DSCPermissions.psm1', 'Modules/M365DSCReport.psm1', 'Modules/M365DSCReverse.psm1', diff --git a/Modules/Microsoft365DSC/Modules/M365DSCCompare.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCCompare.psm1 index 891e9db7f3..d7f7dab395 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCCompare.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCCompare.psm1 @@ -2,6 +2,12 @@ .SYNOPSIS This module contains the comparison logic for M365DSC. #> + +$Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' + +# Automatically initialize accelerator on module import +Initialize-M365DSCDllLoader -ErrorAction SilentlyContinue + function Compare-M365DSCResourceState { [CmdletBinding()] @@ -50,7 +56,14 @@ function Compare-M365DSCResourceState { $schemaPath = Join-Path -Path $currentPath -ChildPath '..\SchemaDefinition.json' $schemaJSON = Get-Content $schemaPath -Raw - $Script:M365DSCSchema = ConvertFrom-Json $schemaJSON + if ($Script:IsPowerShellCore) + { + $Script:M365DSCSchema = ConvertFrom-Json $schemaJSON -AsHashtable + } + else + { + $Script:M365DSCSchema = ConvertFrom-Json $schemaJSON + } $Script:ResourceDefinitionCache = @{} foreach ($schemaEntry in $Script:M365DSCSchema) @@ -59,7 +72,6 @@ function Compare-M365DSCResourceState } } $resourceDefinition = $Script:ResourceDefinitionCache["MSFT_$ResourceName"] - $resourceKeys = $resourceDefinition.Parameters.Where({ $_.Option -eq 'Key' }) # Create a cache for resource property lookups to improve performance $Script:ResourcePropertyCache = @{} @@ -94,16 +106,14 @@ function Compare-M365DSCResourceState $null = $ValuesToCheck.Remove('Identity') # Remove the key parameters from the comparison - foreach ($keyToRemove in $resourceKeys) - { - $null = $ValuesToCheck.Remove($keyToRemove.Name) - } - # Remove PSCredential object from the list of properties to be evaluated - $credentialProperties = $resourceDefinition.Parameters.Where({ $_.CIMType -eq 'MSFT_Credential' }) - foreach ($property in $credentialProperties) + $resourceParameters = $resourceDefinition.Parameters + foreach ($resourceParameter in $resourceParameters) { - $null = $ValuesToCheck.Remove($property.Name) + if ($resourceParameter.CIMType -eq 'PSCredential' -or $resourceParameter.Option -eq 'Key') + { + $null = $ValuesToCheck.Remove($resourceParameter.Name) + } } # Remove the ExcludedProperties from the list of properties to be evaluated @@ -152,14 +162,14 @@ function Compare-M365DSCResourceState } $testResult = $true - if ($testTargetResource -and -not $skipEvaluation) + if (-not $skipEvaluation) { # Compare Cim instances # Create property lookup hashtable for this resource type if not already cached if (-not $Script:ResourcePropertyCache.ContainsKey($ResourceName)) { $propertyLookup = @{} - foreach ($prop in $resourceDefinition.Parameters) + foreach ($prop in $resourceParameters) { $propertyLookup[$prop.Name] = $prop } @@ -176,11 +186,10 @@ function Compare-M365DSCResourceState if ($null -ne $source -and ($source.GetType().Name -like '*CimInstance*' -or $parameterDefinition.CIMType -like "MSFT_*")) { Write-Verbose -Message "Comparing complex object property $key of resource $ResourceName" - $CIMProperty = $parameterDefinition - $CIMName = $CIMProperty.CIMType.Replace('[]', '') - $CIMDefinition = $Script:M365DSCSchema.Where({ $_.ClassName -eq $CIMName }) + $CIMName = $parameterDefinition.CIMType.Replace('[]', '') + $CIMDefinition = [Microsoft365DSC.Utilities.Utilities]::FilterCimClassesByName($Script:M365DSCSchema, $CIMName) # Can potentially be a single PSObject, therefore not using Where() - $CIMPrimaryKeys = $CIMDefinition.Parameters | Where-Object { $_.Option -eq 'Required' } + $CIMPrimaryKeys = $CIMDefinition.Parameters | Where-Object Option -EQ 'Required' $targetObjects = @{} if ($source.GetType().Name -in @('CimInstance[]', 'Object[]')) @@ -188,6 +197,17 @@ function Compare-M365DSCResourceState $targetObjects = @() } + if ($CIMName -like "*Intune*PolicyAssignments" -and -not $CIMName -eq "MSFT_IntuneDeviceRemediationPolicyAssignments") + { + if (($source.Count -gt 0 -and $source[0].dataType -notin @("#microsoft.graph.allLicensedUsersAssignmentTarget","#microsoft.graph.allDevicesAssignmentTarget")) -or ` + ($target.Count -gt 0 -and $target[0].dataType -notin @("#microsoft.graph.allLicensedUsersAssignmentTarget","#microsoft.graph.allDevicesAssignmentTarget"))) + { + $CIMPrimaryKeys += @{ + Name = 'groupDisplayName' + } + } + } + # Filter all target objects that match the primary keys of the source object(s) $target = $target | Where-Object -FilterScript { $match = $true @@ -204,6 +224,13 @@ function Compare-M365DSCResourceState return $match } + # For cases where $nullreturn is returned from a resource, the properties may + # contain or be CimInstances that need to be converted to hashtables first for comparison + if (($null -ne $target -and $target.GetType().Name -like 'CimInstance*') -or ` + ($target -is [array] -and $target.Count -gt 0 -and $target[0].GetType().Name -like 'CimInstance*')) + { + $target = Convert-M365DSCDRGComplexTypeToHashtable -ComplexObject $target + } foreach ($targetObject in $target) { foreach ($primaryKey in $CIMPrimaryKeys.Name) diff --git a/Modules/Microsoft365DSC/Modules/M365DSCDRGUtil.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCDRGUtil.psm1 index b1890a8f3c..133d7c46e2 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCDRGUtil.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCDRGUtil.psm1 @@ -1,3 +1,6 @@ +# Automatically initialize accelerator on module import +Initialize-M365DSCDllLoader -ErrorAction SilentlyContinue + function Get-StringFirstCharacterToUpper { [CmdletBinding()] @@ -71,7 +74,7 @@ function Rename-M365DSCCimInstanceParameter #region Single if ($type -like '*Hashtable') { - $result = ([Hashtable]$Properties).Clone() + $result = [System.Collections.Specialized.CollectionsUtil]::CreateCaseInsensitiveHashtable([Hashtable]$Properties) } if ($type -like '*CimInstance*' -or $type -like '*Hashtable*' -or $type -like '*Object*') @@ -127,96 +130,12 @@ function Get-M365DSCDRGComplexTypeToHashtable return $null } - if ($ComplexObject.GetType().FullName -like '*[[\]]') + if ($ComplexObject -is [array]) { - $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 ,[System.Collections.Hashtable[]]$results + return [Microsoft365DSC.Converter.ComplexObjectConverter]::ToHashtableArray($ComplexObject) } - if ($ComplexObject.GetType().FullName -like '*Dictionary*') - { - $results = @{} - - $ComplexObject = [hashtable]$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 $results - } - - $results = @{} - if ($ComplexObject.GetType().Fullname -like '*hashtable') - { - $keys = $ComplexObject.Keys - } - else - { - $keys = $ComplexObject | Get-Member | Where-Object -FilterScript { $_.MemberType -eq 'Property' -or $_.MemberType -eq 'NoteProperty' } - } - - 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 -or $hash.GetType().FullName -like '*[[\]]')) - { - if ($ComplexObject.$keyName.GetType().FullName -like '*[[\]]') - { - $results.Add($keyName, @($hash)) - } - else - { - $results.Add($keyName, $hash) - } - } - } - else - { - $results.Add($keyName, $ComplexObject.$keyName) - } - } - } - return $results + return [Microsoft365DSC.Converter.ComplexObjectConverter]::ToHashtable($ComplexObject) } <# @@ -247,7 +166,7 @@ function Get-M365DSCDRGComplexTypeToHashtable function Get-M365DSCDRGComplexTypeToString { [CmdletBinding()] - [OutputType([System.String])] + [OutputType([System.String], [System.String[]])] param( [Parameter()] $ComplexObject, @@ -278,224 +197,13 @@ function Get-M365DSCDRGComplexTypeToString return $null } - $indent = '' - for ($i = 0; $i -lt $IndentLevel; $i++) + $returnValue = [Microsoft365DSC.Converter.ComplexObjectConverter]::ToDscString($ComplexObject, $CIMInstanceName, $ComplexTypeMapping, $Whitespace, $IndentLevel, $IsArray) + if ($returnValue -is [System.Array]) { - $indent += ' ' + return ,$returnValue } - #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 - } - - # Add an indented new line after the last item in the array - if ($currentProperty.Count -gt 0) - { - $currentProperty[-1] += "`r`n" + $indent - } - - #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 - - $keys = $ComplexObject.Keys | Sort-Object - if ($ComplexObject.Keys.Count -eq 0) - { - $properties = $ComplexObject | Get-Member -MemberType Properties - if ($null -eq $properties) - { - return $null - } - else - { - $keys = $properties.Name - } - } - - foreach ($key in $keys) - { - if ($null -ne $ComplexObject.$key) - { - $keyNotNull++ - if ($ComplexObject.$key.GetType().FullName -like 'Microsoft.Graph.PowerShell.Models.*' -or $key -in $ComplexTypeMapping.Name) - { - $itemValue = $ComplexObject[$key] - if ([System.String]::IsNullOrEmpty($itemValue)) - { - $itemValue = $ComplexObject.$key - } - $hashPropertyType = $itemValue.GetType().Name.ToLower() - - $IsArray = $false - if ($itemValue.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 = $itemValue - } - else - { - $hashProperty = Get-M365DSCDRGComplexTypeToHashtable -ComplexObject $itemValue - } - - 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 $itemValue.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 - if (-not $currentProperty.EndsWith("`r`n")) - { - $currentProperty += "`r`n" - } - } - $IndentLevel-- - } - else - { - $nestedPropertyString = Get-M365DSCDRGComplexTypeToString ` - -ComplexObject $hashProperty ` - -CIMInstanceName $hashPropertyType ` - -IndentLevel $IndentLevel ` - -ComplexTypeMapping $ComplexTypeMapping - if ([string]::IsNullOrWhiteSpace($nestedPropertyString)) - { - $nestedPropertyString = "`$null`r`n" - } - $currentProperty += $nestedPropertyString + "`r`n" - } - if ($IsArray) - { - if ($ComplexObject.$key.Count -gt 0) - { - $currentProperty += $indent - $currentProperty += ')' - $currentProperty += "`r`n" - } - } - $IsArray = $PSBoundParameters.IsArray - } - else - { - $currentValue = $ComplexObject[$key] - if ([System.String]::IsNullOrEmpty($currentValue)) - { - $currentValue = $ComplexObject.$key - } - if (-not [System.String]::IsNullOrEmpty($currentValue) -and $currentValue.GetType().Name -ne 'Dictionary`2') - { - if ($currentValue.GetType().Name -eq 'String') - { - $currentValue = $currentValue.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 -and -not $currentProperty.EndsWith("`r`n")) - { - $currentProperty += "`r`n" - } - - $currentProperty += "$indent}" - $emptyCIM = $currentProperty.Replace(' ', '').Replace("`r`n", '') - if ($emptyCIM -eq "MSFT_$CIMInstanceName{}") - { - $currentProperty = [string]::Empty - } - - return $currentProperty + return $returnValue } <# @@ -525,101 +233,7 @@ function Update-M365DSCSpecialCharacters $String ) - $String = $String.Replace("$([char]0x201C)", "``$([char]0x201C)") - $String = $String.Replace("$([char]0x201D)", "``$([char]0x201D)") - $String = $String.Replace("$([char]0x201E)", "``$([char]0x201E)") - - return $String -} - -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' - } - - $newString = $Value.Replace('`', '``').Replace('$', '`$') - $newString = Update-M365DSCSpecialCharacters -String $newString - $newString = $newString.Replace('"', '`"') - $returnValue = $Space + $Key + ' = "' + $newString + """`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' - { - $item = $item.Replace('`', '``').Replace('$', '`$').Replace('"', '`"') - $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 + return [Microsoft365DSC.Utilities.Utilities]::UpdateSpecialCharacters($String) } function Test-IsCimInstance @@ -715,432 +329,17 @@ function Compare-M365DSCComplexObject [Parameter(Mandatory = $true)] [System.String] - $PropertyName, - - [Parameter()] - [System.String[]] - $PrimaryKeys, - - [Parameter()] - [switch] - $NoDriftReport + $PropertyName ) - # Compare two arbitrary objects iteratively (no recursion). Returns $true if identical (no drift). - # This function will append potential drifts to $Global:PotentialDrifts if $NoDriftReport is $true, otherwise will append to $Global:AllDrifts.DriftInfo on real drifts. - function ComparePairIterative { - param - ( - [Parameter()] - $Left, - - [Parameter()] - $Right, - - [Parameter()] - [System.String] - $PropName, - - [Parameter()] - [switch] - $LocalNoDriftReport - ) - - # Use a stack of frames. Each frame describes a comparison that needs processing. - # Frame fields: - # Left, Right, PropName, Stage, KeysEnumerator, TargetKeys, ArrayState - $workStack = [System.Collections.Stack]::new() - - $workStack.Push(@{ - Left = $Left - Right = $Right - PropName = $PropName - # Stage describes what to do: 'compare' for top-level handling - Stage = 'compare' - }) - - # result means: if we encounter an unrecoverable drift we return $false - $result = $true - - while ($workStack.Count -gt 0 -and $result) { - $frame = $workStack.Pop() - $l = $frame.Left - $r = $frame.Right - $p = $frame.PropName - - # Both null => identical for this frame - if ($null -eq $l -and $null -eq $r) - { - continue - } - - # One null and the other not => drift - if (($null -eq $l) -xor ($null -eq $r)) - { - $sourceValue = if ($null -eq $l) { 'Desired value is null' } else { 'Desired value is NOT null' } - $targetValue = if ($null -eq $r) { 'Current value is null' } else { 'Current value is NOT null' } - - Write-Verbose -Message "Configuration drift - Complex object: {$sourceValue$targetValue}" - $drift = @{ - PropertyName = $p - CurrentValue = $targetValue - DesiredValue = $sourceValue - } - - if (-not $LocalNoDriftReport) - { - $Global:AllDrifts.DriftInfo += $drift - } - else - { - $Global:PotentialDrifts += $drift - } - - $result = $false - break - } - - # If left is an array of complex objects, handle array logic (order-insensitive) - if (Test-IsComplexArrayCandidate -Object $l) { - # If counts differ, record drift (original did that) - if ($l.Count -ne $r.Count) - { - Write-Verbose -Message "Configuration drift - The complex array have different number of items: Source {$($l.Count)}, Target {$($r.Count)}" - $Global:AllDrifts.DriftInfo += @{ - PropertyName = $p - CurrentValue = "Current value has {$($r.Count)} items" - DesiredValue = "Desired value has {$($l.Count)} items" - } - $result = $false - break - } - - # Intune special-case: original did type-specific handling - if ((Test-IsCimInstance $l[0]) -and ` - ($l[0].CimClass.CimClassName -eq 'MSFT_DeviceManagementConfigurationPolicyAssignments' -or ` - $l[0].CimClass.CimClassName -eq 'MSFT_DeviceManagementMobileAppAssignment' -or ` - ($l[0].CimClass.CimClassName -like 'MSFT_Intune*Assignments' -and ` - $l[0].CimClass.CimClassName -ne 'MSFT_IntuneDeviceRemediationPolicyAssignments'))) - { - $compareResult = Compare-M365DSCIntunePolicyAssignment -Source @($l) -Target @($r) - if (-not $compareResult) { - Write-Verbose -Message "Configuration drift - Intune Policy Assignment: $p" - $Global:AllDrifts.DriftInfo += @{ - PropertyName = $p - CurrentValue = $r - DesiredValue = $l - } - $result = $false - } - continue - } - - # For arrays: we must find for each source element a matching distinct target element - # We'll keep a boolean array for consumed target elements - $consumed = [bool[]]::CreateInstance([bool], $r.Count) - for ($i = 0; $i -lt $l.Count; $i++) - { - $srcItem = $l[$i] - $found = $false - $lastCompareResult = $null - - for ($j = 0; $j -lt $r.Count; $j++) - { - if ($consumed[$j]) - { - continue - } - $tgtItem = $r[$j] - - # snapshot potential drifts count so we can rollback/preserve them according to outcome - if ($null -eq $Global:PotentialDrifts) - { - $potentialStart = 0 - } - else - { - $potentialStart = $Global:PotentialDrifts.Count - } - - # Compare srcItem vs tgtItem using a *fresh* iterative compare that records potential drifts - $pairEqual = ComparePairIterativeInner -Left $srcItem -Right $tgtItem -PropName ("$p[$i]") -LocalNoDriftReport:$true - - $lastCompareResult = $pairEqual - - if ($pairEqual) - { - # Consume this target element - $consumed[$j] = $true - # Remove any potential drifts produced during this successful attempt - if ($Global:PotentialDrifts.Count -gt $potentialStart) - { - # Delete the appended entries from potential drifts (they were false alarms) - $Global:PotentialDrifts = $Global:PotentialDrifts[0..($potentialStart-1)] - } - $found = $true - break - } - else - { - # Attempt failed: if there were potential drifts appended during attempt, promote last to AllDrifts (original logic) - if ($Global:PotentialDrifts.Count -gt $potentialStart) - { - $lastIndex = $Global:PotentialDrifts.Count - 1 - if ($null -ne $Global:PotentialDrifts[$lastIndex]) - { - $Global:AllDrifts.DriftInfo += $Global:PotentialDrifts[$lastIndex] - } - # reset potential drifts - $Global:PotentialDrifts = @() - } - # try next candidate - } - } - - if (-not $found) - { - Write-Verbose -Message 'Configuration drift - The complex array items are not identical' - # If no attempts happened (r was empty) or lastCompareResult is $null, record AllDrifts as original did - if ($null -eq $lastCompareResult) - { - $Global:AllDrifts.DriftInfo += @{ - PropertyName = ("$p[$i]") - CurrentValue = $r - DesiredValue = $l - } - } - $result = $false - break - } - } - - # After finishing array matching loop, continue to next frame - continue - } - - # Now handle non-array (single) complex objects or simple objects - # Build keys for Left (source) - if (Test-IsCimInstance -Object $l) - { - $keys = @() - $l.CimInstanceProperties | ForEach-Object { - if ($_.Name -notin @('PSComputerName', 'CimClass', 'CimInstanceProperties', 'CimSystemProperties') -and $_.IsValueModified) - { - $keys += $_.Name - } - } - } - else - { - # hashtable or ordered dictionary - $keys = $l.Keys | Where-Object { $_ -ne 'PSComputerName' } - } - - # Determine keys for Right (target) - if (Test-IsCimInstance -Object $r) - { - $targetKeys = @() - $r.CimInstanceProperties | ForEach-Object { - if ($_.Name -notin @('PSComputerName', 'CimClass', 'CimInstanceProperties', 'CimSystemProperties') -and $_.IsValueModified) - { - $targetKeys += $_.Name - } - } - } - elseif (Test-IsHashtable -Object $r) - { - $targetKeys = $r.Keys | Where-Object { $_ -ne 'PSComputerName' } - } - else - { - # Fallback, possibly Microsoft Graph model -> convert - $r = Get-M365DSCDRGComplexTypeToHashtable -ComplexObject $r - $targetKeys = $r.Keys | Where-Object { $_ -ne 'PSComputerName' } - } - - foreach ($key in $keys) { - # Check presence in target - $keyExistsInTarget = ( - ($r.GetType().Name -eq 'Hashtable' -and $r.ContainsKey($key)) -or ` - ($r.GetType().Name -eq 'OrderedDictionary' -and $r.Contains($key)) -or ` - ($r.GetType().Name -eq 'CIMInstance' -and $null -ne $r.$key) - ) - - if (-not $keyExistsInTarget) - { - continue - } - - $sourceValue = $l.$key - $targetValue = $null - if ($key -in $targetKeys) - { - $targetValue = $r.$key - } - - # One null and the other not => drift - if (($null -eq $sourceValue) -xor ($null -eq $targetValue)) - { - if ($null -eq $sourceValue) { $sv = 'null' } else { $sv = $sourceValue } - if ($null -eq $targetValue) { $tv = 'null' } else { $tv = $targetValue } - Write-Verbose -Message "Configuration drift - key: $key" - Write-Verbose -Message "Source {$sv}" - Write-Verbose -Message "Target {$tv}" - $drift = @{ - PropertyName = $p + "." + $key - CurrentValue = $targetValue - DesiredValue = $sourceValue - } - if (-not $LocalNoDriftReport) - { - $Global:AllDrifts.DriftInfo += $drift - } - else - { - $Global:PotentialDrifts += $drift - } - $result = $false - break - } - - if ($null -ne $sourceValue -and $null -ne $targetValue) { - # complex nested types - if ((Test-IsCimInstance -Object $sourceValue) -or (Test-IsHashtable -Object $sourceValue) -or $sourceValue.GetType().FullName -like "*OrderedDictionary*" -or (Test-IsObjectArray -Object $sourceValue)) { - # Intune assignment special-case - if ((Test-IsCimInstance -Object $sourceValue) -and ( - $sourceValue.CimClass.CimClassName -eq 'MSFT_DeviceManagementConfigurationPolicyAssignments' -or - $sourceValue.CimClass.CimClassName -eq 'MSFT_DeviceManagementMobileAppAssignment' -or - $sourceValue.CimClass.CimClassName -like 'MSFT_Intune*Assignments' - )) { - $compareResult = Compare-M365DSCIntunePolicyAssignment -Source @($sourceValue) -Target @($targetValue) - if (-not $compareResult) - { - Write-Verbose -Message "Configuration drift - Intune Policy Assignment key: $key" - $Global:AllDrifts.DriftInfo += @{ - PropertyName = ($p + "." + $key) - CurrentValue = $targetValue - DesiredValue = $sourceValue - } - $result = $false - break - } - else - { - continue - } - } - else - { - # push a new frame to compare nested complex objects - $workStack.Push(@{ - Left = $sourceValue - Right = $targetValue - PropName = ($p + "." + $key) - Stage = 'compare' - }) - continue - } - } - - # Simple types: do comparisons similar to original - $referenceObject = $targetValue - $differenceObject = $sourceValue - - $sourceType = ($sourceValue.GetType()).Name - $targetType = ($targetValue.GetType()).Name - - $compareResult = $null - - if ($targetType -like '*Date*') - { - try - { - $compareResult = ([DateTime]$sourceValue) -eq ([DateTime]$targetValue) - } - catch - { - $compareResult = $null - } - } - elseif ($targetType -eq 'String') - { - if (-not [System.String]::IsNullOrEmpty($referenceObject)) - { - $referenceObject = $referenceObject.Replace("`r`n", "`n") - } - if (-not [System.String]::IsNullOrEmpty($differenceObject) -and $sourceType -eq 'String') - { - $differenceObject = $differenceObject.Replace("`r`n", "`n") - } - - $ordinalComparison = [System.String]::Equals($referenceObject, $differenceObject, [System.StringComparison]::OrdinalIgnoreCase) - if (-not $ordinalComparison) { $compareResult = $false } else { $compareResult = $true } - } - else - { - $diff = Compare-Object -ReferenceObject $referenceObject -DifferenceObject $differenceObject -PassThru - $compareResult = $diff.Count -eq 0 - } - - if ($null -ne $compareResult -and -not $compareResult) - { - Write-Verbose -Message "Configuration drift - simple object key: $key" - Write-Verbose -Message "Source {$sourceValue}" - Write-Verbose -Message "Target {$targetValue}" - $drift = @{ - PropertyName = ($p + "." + $key) - CurrentValue = $targetValue - DesiredValue = $sourceValue - } - if (-not $LocalNoDriftReport) - { - $Global:AllDrifts.DriftInfo += $drift - } - else - { - $Global:PotentialDrifts += $drift - } - $result = $false - break - } - } # end both non-null branch - } # end foreach key - } # end while stack - - return $result - } # end ComparePairIterative - - # - # Inner worker used for attempts when matching array elements. - # Important: this is an inner isolated iterator that behaves exactly like ComparePairIterative but is referenced by name so we can call it repeatedly. - # - function ComparePairIterativeInner { - param( - [Parameter()] - $Left, - - [Parameter()] - $Right, - - [Parameter()] - [System.String] - $PropName, - - [Parameter()] - [switch] - $LocalNoDriftReport - ) - - return (ComparePairIterative -Left $Left -Right $Right -PropName $PropName -LocalNoDriftReport:$LocalNoDriftReport) + $tuple = [Microsoft365DSC.Compare.ComplexObjectComparer]::Compare($Source, $Target, $PropertyName) + if ($tuple.Item1.Count -gt 0) + { + $Global:AllDrifts.DriftInfo += $tuple.Item1 } - - # Start the top-level comparison using the iterative comparator - $final = ComparePairIterative -Left $Source -Right $Target -PropName $PropertyName -LocalNoDriftReport:$NoDriftReport - - return $final + return $tuple.Item2 } - function Write-M365DSCDriftsToEventLog { [CmdletBinding()] @@ -1313,7 +512,9 @@ function Convert-M365DSCDRGComplexTypeToHashtable if ($null -ne $hashComplexObject) { + $results = $hashComplexObject.Clone() + if ($SingleLevel) { return $results diff --git a/Modules/Microsoft365DSC/Modules/M365DSCDllLoader.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCDllLoader.psm1 new file mode 100644 index 0000000000..6d00e4faeb --- /dev/null +++ b/Modules/Microsoft365DSC/Modules/M365DSCDllLoader.psm1 @@ -0,0 +1,134 @@ +<# +.SYNOPSIS + Loads and initializes the Microsoft365DSC C# dll files. + +.DESCRIPTION + This module loads all of the Microsoft365DSC.*.dll at module import time. + It provides fail-fast validation of .NET Framework version requirements and + exports initialization status for diagnostics. + +.NOTES + The dll loader requires .NET Framework 4.7.2 or higher. + The DLLs are located in the Dependencies/Assemblies directory relative to the module root. +#> + +$Script:AcceleratorLoaded = $false +$Script:AcceleratorAssembly = $null +$Script:AcceleratorLoadError = $null + +$Script:ConverterLoaded = $false +$Script:ConverterAssembly = $null +$Script:ConverterLoadError = $null + +<# +.SYNOPSIS + Initializes the Microsoft365DSC C# dll files. + +.DESCRIPTION + Validates .NET Framework version requirements and loads the compiled C# dll files. + This function is called automatically during module import but can be invoked manually + for troubleshooting or reloading scenarios. + +.PARAMETER Force + Forces reloading of the dll files even if already loaded. + +.EXAMPLE + PS> Initialize-M365DSCDllLoader + Loads the dll files with standard version checks. +.EXAMPLE + PS> Initialize-M365DSCDllLoader -Force + Forces reload of the dll files. + +.OUTPUTS + System.Collections.Hashtable with keys: Loaded (bool), Assembly (Reflection.Assembly), Error (string) +#> +function Initialize-M365DSCDllLoader +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param() + + try + { + # Validate .NET Framework version (4.7.2 = Release 461808+) + $netFrameworkVersion = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full' -ErrorAction Stop + $releaseKey = $netFrameworkVersion.Release + + if ($releaseKey -lt 461808) + { + $versionString = $netFrameworkVersion.Version + $errorMessage = ".NET Framework 4.7.2 or higher is required for Microsoft365DSC C# dll files. Current version: $versionString (Release: $releaseKey). Please install .NET Framework 4.7.2+ from https://dotnet.microsoft.com/download/dotnet-framework" + + Write-Error -Message $errorMessage -ErrorAction Stop + } + + Write-Verbose ".NET Framework version check passed (Release: $releaseKey)" + + # Locate the accelerator DLL + $moduleRoot = Split-Path -Path $PSScriptRoot -Parent + $dllsToLoad = @( + 'Microsoft365DSC.Compare.dll' + 'Microsoft365DSC.Converter.dll' + 'Microsoft365DSC.Utilities.dll' + ) + + foreach ($dllName in $dllsToLoad) + { + $dllPath = Join-Path -Path $moduleRoot -ChildPath "Dependencies\Assemblies\$dllName" + if (-not (Test-Path -Path $dllPath)) + { + $errorMessage = "$dllName not found at: $dllPath. Please run Utilities/Build-DllFiles.ps1 to build the dll file." + Write-Warning $errorMessage + + $Script:AcceleratorLoaded = $false + $Script:AcceleratorLoadError = $errorMessage + + return @{ + Loaded = $false + Assembly = $null + Error = $errorMessage + } + } + + Write-Verbose "Loading dll from: $dllPath" + } + + # Load the assembly + $loadedAssemblies = @() + foreach ($dllName in $dllsToLoad) + { + $dllPath = Join-Path -Path $moduleRoot -ChildPath "Dependencies\Assemblies\$dllName" + $loadedAssemblies += Add-Type -Path $dllPath -PassThru -ErrorAction Stop + } + + # Verify expected types are available + $expectedTypes = @( + 'Microsoft365DSC.Compare.ComplexObjectComparer' + 'Microsoft365DSC.Converter.ComplexObjectConverter' + 'Microsoft365DSC.Converter.SimpleObjectConverter' + 'Microsoft365DSC.Utilities.Utilities' + ) + + foreach ($typeName in $expectedTypes) + { + $currentAssemblies = [AppDomain]::CurrentDomain.GetAssemblies().Where({ $_.FullName -like "Microsoft365DSC.*" }) + $type = $currentAssemblies | ForEach-Object { $_.GetTypes() } | Where-Object { $_.FullName -eq $typeName } + if ($null -eq $type) + { + Write-Warning "Expected type not found in assembly: $typeName" + } + else + { + Write-Verbose "Verified accelerator type: $typeName" + } + } + + Write-Verbose -Message "Microsoft365DSC C# dll files loaded successfully." + } + catch + { + throw "Failed to initialize Microsoft365DSC C# dll files: $($_.Exception.Message)" + } +} + +Export-ModuleMember -Function Initialize-M365DSCDllLoader diff --git a/Modules/Microsoft365DSC/Modules/M365DSCModuleMgmt.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCModuleMgmt.psm1 new file mode 100644 index 0000000000..8f5a901912 --- /dev/null +++ b/Modules/Microsoft365DSC/Modules/M365DSCModuleMgmt.psm1 @@ -0,0 +1,822 @@ +$Script:IsPowerShellCore = $PSVersionTable.PSEdition -eq 'Core' +$Script:IsPsResourceGetAvailable = $null -ne (Get-Module -Name Microsoft.PowerShell.PSResourceGet -ListAvailable) +$Script:M365DSCDependenciesValidated = $false +if ($null -eq $Script:M365DSCDependencies) +{ + $Script:M365DSCDependencies = [System.Collections.Generic.Dictionary[System.String, System.Object]]::new([System.StringComparer]::OrdinalIgnoreCase) + $dependencies = (Import-PowerShellDataFile "$PSScriptRoot/../Dependencies/Manifest.psd1").Dependencies + foreach ($dependency in $dependencies) + { + # TODO: Review again once ModuleFast can work with additional properties + # https://github.com/microsoft/Microsoft365DSC/pull/6726 + # https://github.com/ykuijs/M365DSC_CICD/issues/53 + if ($dependency.ModuleName -eq 'PnP.PowerShell') + { + $dependency.DependsOn = @('Microsoft.Graph.Authentication') + } + $Script:M365DSCDependencies.Add($dependency.ModuleName, $dependency) + } + + $commandToModuleMap = @{} + $Script:M365DSCResourceSettings = [System.Collections.Generic.Dictionary[System.String, System.Object]]::new([System.StringComparer]::OrdinalIgnoreCase) + foreach ($file in (Get-ChildItem -Path "$PSScriptRoot/../DSCResources" -Filter 'settings.json' -Recurse)) { + Write-Verbose -Message "Processing settings.json file at path: $($file.FullName)" + $jsonContent = [System.IO.File]::ReadAllText($file.FullName) | ConvertFrom-Json + foreach ($commandMap in $jsonContent.commands) { + $commandToModuleMap[$commandMap.module] += @($commandMap.cmdlets) + } + $directoryName = (Split-Path -Path $file.DirectoryName -Leaf).Replace('MSFT_', '') + $Script:M365DSCResourceSettings.Add($directoryName, @{ + requiredModules = $jsonContent.requiredModules + commands = $jsonContent.commands + mode = $jsonContent.mode + }) + } + + Write-Verbose -Message "Loading current configuration from config.json" + $Script:M365DSCValidatedDependencies = [System.Collections.Generic.List[System.String]]::new($Script:M365DSCDependencies.Count) + $configAsPsCustomObject = Get-Content -Path "$PSScriptRoot/../config.json" | ConvertFrom-Json + $configAsHashtable = @{} + foreach ($property in $configAsPsCustomObject.PSObject.Properties) + { + $configAsHashtable.Add($property.Name, $property.Value) + } + $Script:CurrentConfiguration = $configAsHashtable + $globalRequiredModules = $Script:CurrentConfiguration.requiredModules + foreach ($entry in $commandToModuleMap.GetEnumerator()) + { + $sortedFunctions = @($globalRequiredModules.$($entry.Key)) + @($entry.Value) | Sort-Object -Unique + $Script:M365DSCDependencies[$entry.Key].Commands = $sortedFunctions + } + $Script:M365DSCRequiredModules = @($globalRequiredModules.psobject.Properties.Name) +} + +function Get-M365DSCResourceSettings +{ + [CmdletBinding()] + param() + + return $Script:M365DSCResourceSettings +} + +function Get-M365DSCRequiredModules +{ + [CmdletBinding()] + param() + + return $Script:M365DSCRequiredModules +} + +function Get-M365DSCModuleConfiguration +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param() + + return $Script:CurrentConfiguration.Clone() +} + +function Set-M365DSCModuleConfiguration +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Key, + + [Parameter(Mandatory = $true)] + [AllowEmptyCollection()] + [AllowEmptyString()] + [AllowNull()] + [System.Object] + $Value + ) + + $Script:CurrentConfiguration.$Key = $Value +} + +<# +.DESCRIPTION + This function checks if all M365DSC dependencies are present + +.FUNCTIONALITY + Internal +#> +function Confirm-M365DSCDependencies +{ + [CmdletBinding()] + param() + + if (-not $Script:M365DSCDependenciesValidated -and ($null -eq $Global:M365DSCSkipDependenciesValidation -or -not $Global:M365DSCSkipDependenciesValidation)) + { + Write-Verbose -Message 'Dependencies were not already validated.' + + Test-CodePage + $result = Update-M365DSCDependencies -ValidateOnly + + if ($result.Length -gt 0) + { + $ErrorMessage = "The following dependencies need updating:`r`n" + foreach ($invalidDependency in $result) + { + $ErrorMessage += ' * ' + $invalidDependency.ModuleName + "`r`n" + } + $ErrorMessage += 'Please run Update-M365DSCDependencies as Administrator. ' + $Script:M365DSCDependenciesValidated = $false + Add-M365DSCEvent -Message $ErrorMessage -EntryType 'Error' ` + -EventID 1 -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $tenantIdValue + throw $ErrorMessage + } + else + { + Write-Verbose -Message 'Dependencies were all successfully validated.' + $Script:M365DSCDependenciesValidated = $true + } + } + else + { + Write-Verbose -Message 'Dependencies were already successfully validated.' + } +} + +<# +.DESCRIPTION + This function checks if a specific module is loaded and validates its version against the required version specified in the M365DSC dependencies manifest. + +.PARAMETER ModuleName + The name of the module to check and validate. + +.EXAMPLE + PS> Confirm-M365DSCLoadedModule -ModuleName 'Microsoft.Graph.Authentication' + +.FUNCTIONALITY + Internal +#> +function Confirm-M365DSCLoadedModule +{ + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [System.String] + $ModuleName + ) + + if ($Script:M365DSCValidatedDependencies.Contains($ModuleName)) + { + Write-Verbose -Message "Module '$ModuleName' has already been validated." + return + } + + $manifestModule = $Script:M365DSCDependencies[$ModuleName] + + if ($null -ne $manifestModule.DependsOn -and $manifestModule.DependsOn.Count -gt 0) + { + foreach ($dependency in $manifestModule.DependsOn) + { + Write-Verbose -Message "Validating dependency '$dependency' for module '$ModuleName'." + Confirm-M365DSCLoadedModule -ModuleName $dependency + } + } + + $loadedModule = Get-Module -Name $ModuleName + if ($null -eq $loadedModule) + { + Write-Verbose -Message "Module '$ModuleName' is not loaded. Importing it now." + $importModuleSplat = @{ + Name = $ModuleName + RequiredVersion = $manifestModule.RequiredVersion + Global = $true + Alias = @() + Cmdlet = @() + Variable = @() + DisableNameChecking = $true + } + if ($manifestModule.Commands.Count -gt 0) + { + $importModuleSplat.Add('Function', $manifestModule.Commands) + $importModuleSplat.Cmdlet = $manifestModule.Commands + } + Import-Module @importModuleSplat + Write-Verbose -Message "Module '$ModuleName' with version '$($manifestModule.RequiredVersion)' has been imported." + } + elseif ($loadedModule.Version -ne $manifestModule.RequiredVersion) + { + Write-Verbose -Message "Module '$ModuleName' is loaded but the version '$($loadedModule.Version)' does not match the required version '$($manifestModule.RequiredVersion)'." + Remove-Module -Name $ModuleName -Force -ErrorAction SilentlyContinue + Write-Verbose -Message "Unloaded module '$ModuleName' with version '$($loadedModule.Version)'." + Import-Module -Name $ModuleName -RequiredVersion $manifestModule.RequiredVersion -Global -Alias @() -Cmdlet @() -Variable @() -DisableNameChecking + Write-Verbose -Message "Re-imported module '$ModuleName' with version '$($manifestModule.RequiredVersion)'." + } + else + { + Write-Verbose -Message "Module '$ModuleName' is already loaded." + } + + if (-not $Script:M365DSCValidatedDependencies.Contains($ModuleName)) + { + $Script:M365DSCValidatedDependencies.Add($ModuleName) + } +} + +<# +.DESCRIPTION + This function checks the required dependencies for a specific M365DSC module and validates that they are loaded. + +.PARAMETER ModuleName + The name of the DSC resource for which to check dependencies. + +.EXAMPLE + PS> Confirm-M365DSCModuleDependency -ModuleName 'MSFT_AADApplication' + +.FUNCTIONALITY + Public +#> +function Confirm-M365DSCModuleDependency +{ + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [System.String] + $ModuleName + ) + + $Global:MaximumFunctionCount = 32767 + + if ($Global:IsTestEnvironment -or (Get-M365DSCModuleConfiguration).skipModuleDependencyValidation) + { + Write-Verbose -Message "Skipping module dependency validation in test environment for module '$ModuleName'." + return + } + + $modulesToCheck = $Script:M365DSCResourceSettings[$ModuleName.Replace('MSFT_', '')].requiredModules + foreach ($module in $modulesToCheck) + { + Write-Verbose -Message "Validating module dependency: $($module)" + Confirm-M365DSCLoadedModule -ModuleName $module + } + Write-Verbose -Message "All dependencies for module '$ModuleName' have been validated." +} + +<# +.DESCRIPTION + This function checks if new versions are available for the M365DSC dependencies + +.EXAMPLE + PS> Test-M365DSCDependenciesForNewVersions + +.FUNCTIONALITY + Public +#> +function Test-M365DSCDependenciesForNewVersions +{ + [CmdletBinding()] + param () + + $i = 1 + Import-Module PowerShellGet -Force + + foreach ($dependency in $Script:M365DSCDependencies.Values.GetEnumerator()) + { + Write-Progress -Activity 'Scanning Dependencies' -PercentComplete ($i / $Script:M365DSCDependencies.Count * 100) + try + { + $moduleInGallery = Find-Module $dependency.ModuleName + [array]$moduleInstalled = Get-Module $dependency.ModuleName -ListAvailable | Select-Object Version + if ($moduleInstalled) + { + $modules = $moduleInstalled | Sort-Object Version -Descending + } + $moduleInstalled = $modules[0] + if (-not $modules -or [Version]($moduleInGallery.Version) -gt [Version]($moduleInstalled[0].Version)) + { + Write-Host "New version of {$($dependency.ModuleName)} is available {$($moduleInGallery.Version)}" + } + } + catch + { + Write-Host $_ + Write-Host "New version of {$($dependency.ModuleName)} is available" + } + $i++ + } + + # The progress bar seems to hang sometimes. Make sure it is no longer displayed. + Write-Progress -Activity 'Scanning Dependencies' -Completed +} + +<# +.DESCRIPTION + This function validates there are no updates to the module or it's dependencies and no multiple versions are present on the local system. + +.EXAMPLE + Test-M365DSCModuleValidity + +.FUNCTIONALITY + Public +#> +function Test-M365DSCModuleValidity +{ + [CmdletBinding()] + param() + + if ($Script:IsM365DSCModuleValidated) + { + Write-Verbose -Message 'The Microsoft365DSC module has already been validated in this session.' + Write-Verbose -Message 'If you have updated the module, please restart your PowerShell session to re-validate.' + return + } + + if ($env:AZUREPS_HOST_ENVIRONMENT -like 'AzureAutomation*') + { + $message = 'Skipping check for newer version of Microsoft365DSC due to Azure Automation Environment restrictions.' + Write-Verbose -Message $message + return + } + + # Validate if only one installation of the module is present and that it's the latest version available + if ($Script:IsPsResourceGetAvailable) + { + $latestVersion = (Find-PSResource -Name 'Microsoft365DSC' -Repository 'PSGallery').Version | Sort-Object -Descending | Select-Object -First 1 + } + else + { + $latestVersion = (Find-Module -Name 'Microsoft365DSC' -Includes 'DSCResource').Version + } + $localVersion = (Get-Module -Name 'Microsoft365DSC').Version + + if ($latestVersion -gt $localVersion) + { + Write-Host "There is a newer version of the 'Microsoft365DSC' module available on the gallery." + Write-Host "To update the module and it's dependencies, run the following command:" + Write-Host 'Update-M365DSCModule' -ForegroundColor Blue + } + + $Script:IsM365DSCModuleValidated = $true +} + +<# +.DESCRIPTION + This function uninstalls all previous M365DSC dependencies and older versions of the module. + +.EXAMPLE + Uninstall-M365DSCOutdatedDependencies + +.FUNCTIONALITY + Public +#> +function Uninstall-M365DSCOutdatedDependencies +{ + [CmdletBinding()] + param() + + try + { + $InformationPreference = 'Continue' + + [array]$microsoft365DscModules = Get-Module Microsoft365DSC -ListAvailable + $outdatedMicrosoft365DscModules = $microsoft365DscModules | Sort-Object -Property Version | Select-Object -SkipLast 1 + + foreach ($module in $outdatedMicrosoft365DscModules) + { + try + { + Write-Information -MessageData "Uninstalling $($module.Name) Version {$($module.Version)}" + if (Test-Path -Path $($module.Path)) + { + Remove-Item $($module.ModuleBase) -Force -Recurse + } + } + catch + { + New-M365DSCLogEntry -Message "Could not uninstall $($module.Name) Version $($module.Version)" ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) + Write-Error -Message "Could not uninstall $($module.Name) Version $($module.Version)" -ErrorAction Continue + } + } + + $allDependenciesExceptAuth = $Script:M365DSCDependencies.Values.GetEnumerator().Where({ $_.ModuleName -ne 'Microsoft.Graph.Authentication' }) + + $i = 1 + foreach ($dependency in $allDependenciesExceptAuth) + { + Write-Progress -Activity 'Scanning Dependencies' -PercentComplete ($i / $allDependenciesExceptAuth.Count * 100) + try + { + if ($dependency.PowerShellCore -and -not $Script:IsPowerShellCore) + { + Write-Verbose -Message "Skipping module {$($dependency.ModuleName)} as it is managed by PowerShell Core." + continue + } + elseif ($dependency.PowerShellCore -eq $false -and $Script:IsPowerShellCore) + { + Write-Verbose -Message "Skipping module {$($dependency.ModuleName)} as it is managed by Windows PowerShell." + continue + } + $found = Get-Module $dependency.ModuleName -ListAvailable | Where-Object -FilterScript { $_.Version -ne $dependency.RequiredVersion } + foreach ($foundModule in $found) + { + try + { + Write-Information -MessageData "Uninstalling $($foundModule.Name) Version {$($foundModule.Version)}" + if (Test-Path -Path $($foundModule.Path)) + { + Remove-Item $($foundModule.ModuleBase) -Force -Recurse + } + } + catch + { + New-M365DSCLogEntry -Message "Could not uninstall $($foundModule.Name) Version $($foundModule.Version)" ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) + Write-Error -Message "Could not uninstall $($foundModule.Name) Version $($foundModule.Version)" -ErrorAction Continue + } + } + } + catch + { + Write-Error -Message "Could not uninstall {$($dependency.ModuleName)}" -ErrorAction Continue + } + $i++ + } + } + catch + { + New-M365DSCLogEntry -Message 'Error uninstalling outdated dependencies:' ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) + Write-Error $_ + } + + $authModule = $Script:M365DSCDependencies['Microsoft.Graph.Authentication'] + try + { + Write-Information -MessageData 'Checking Microsoft.Graph.Authentication' + $found = Get-Module $authModule.ModuleName -ListAvailable | Where-Object -FilterScript { $_.Version -ne $authModule.RequiredVersion } + foreach ($foundModule in $found) + { + try + { + Write-Information -MessageData "Uninstalling $($foundModule.Name) version {$($foundModule.Version)}" + if (Test-Path -Path $($foundModule.Path)) + { + Remove-Item $($foundModule.ModuleBase) -Force -Recurse + } + } + catch + { + Write-Error -Message "Could not uninstall $($foundModule.Name) Version $($foundModule.Version)" -ErrorAction Continue + } + } + } + catch + { + Write-Error -Message "Could not uninstall {$($dependency.ModuleName)}" -ErrorAction Continue + } +} + +<# +.DESCRIPTION + This function installs all missing M365DSC dependencies + +.PARAMETER Force + Specifies that all dependencies should be forcefully imported again. + +.PARAMETER ValidateOnly + Specifies that the function should only return the dependencies that are not installed. + +.PARAMETER Scope + Specifies the scope of the update of the module. The default value is AllUsers(needs to run as elevated user). + +.PARAMETER Proxy + Specifies the proxy server to use for the module installation. + +.PARAMETER Repository + Specifies the PowerShell repository name to use for the installation of the dependencies. + +.EXAMPLE + PS> Update-M365DSCDependencies + +.EXAMPLE + PS> Update-M365DSCDependencies -Force + +.EXAMPLE + PS> Update-M365DSCDependencies -Scope CurrentUser + +.FUNCTIONALITY + Public +#> +function Update-M365DSCDependencies +{ + [CmdletBinding()] + param + ( + [Parameter()] + [Switch] + $Force, + + [Parameter()] + [Switch] + $ValidateOnly, + + [Parameter()] + [ValidateSet("CurrentUser", "AllUsers")] + $Scope = "AllUsers", + + [Parameter()] + [System.String] + $Proxy, + + [Parameter()] + [System.String] + $Repository = 'PSGallery' + ) + + try + { + $InformationPreference = 'Continue' + $i = 1 + + $returnValue = @() + + $params = @{} + if (-not [System.String]::IsNullOrEmpty($Proxy)) + { + $params.Add('Proxy', $Proxy) + } + + # Check if PSResourceGet is installed or not + if (-not $Script:IsPsResourceGetAvailable) + { + Write-Warning -Message 'Microsoft.PowerShell.PSResourceGet is not installed, installing it now...' + try + { + Install-Module -Name Microsoft.PowerShell.PSResourceGet -Scope $Scope -AllowClobber @params -Force -ErrorAction Stop -Repository PSGallery + $Script:IsPsResourceGetAvailable = $true + } + catch + { + Write-Warning -Message "Failed to install Microsoft.PowerShell.PSResourceGet, continuing without it..." + } + } + + $scopedIsPsResourceGetAvailable = $Script:IsPsResourceGetAvailable + if ($params.ContainsKey('Proxy')) + { + Write-Information -MessageData "Falling back to Install-Module because Install-PSResource does not support a proxy" + $scopedIsPsResourceGetAvailable = $false + } + + foreach ($dependency in $Script:M365DSCDependencies.Values.GetEnumerator()) + { + Write-Progress -Activity 'Scanning dependencies' -PercentComplete ($i / $Script:M365DSCDependencies.Count * 100) + try + { + if (-not $Force) + { + if ($dependency.PowerShellCore -and -not $Script:IsPowerShellCore) + { + Write-Verbose -Message "The dependency {$($dependency.ModuleName)} requires PowerShell Core. Skipping." + continue + } + elseif ($dependency.PowerShellCore -eq $false -and $Script:IsPowerShellCore) + { + Write-Verbose -Message "The dependency {$($dependency.ModuleName)} requires Windows PowerShell. Skipping." + continue + } + $found = Get-Module $dependency.ModuleName -ListAvailable | Where-Object -FilterScript { $_.Version -eq $dependency.RequiredVersion } + } + + if ((-not $found -or $Force) -and -not $ValidateOnly) + { + $errorFound = $false + try + { + if ((-not(([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))) -and ($Scope -eq "AllUsers")) + { + Write-Error 'Cannot update the dependencies for Microsoft365DSC. You need to run this command as a local administrator.' + $errorFound = $true + } + } + catch + { + Write-Verbose -Message "Couldn't retrieve Windows Principal. One possible cause is that the current environment is not a Windows OS." + } + if (-not $errorFound) + { + if (-not $dependency.PowerShellCore -and $Script:IsPowerShellCore) + { + Write-Warning "The dependency {$($dependency.ModuleName)} does not support PowerShell Core. Please run Update-M365DSCDependencies in Windows PowerShell." + continue + } + elseif ($dependency.PowerShellCore -and -not $Script:IsPowerShellCore) + { + Write-Warning "The dependency {$($dependency.ModuleName)} requires PowerShell Core. Please run Update-M365DSCDependencies in PowerShell Core." + continue + } + + Remove-Module $dependency.ModuleName -Force -ErrorAction SilentlyContinue + if ($dependency.ModuleName -like 'Microsoft.Graph*') + { + Remove-Module 'Microsoft.Graph.Authentication' -Force -ErrorAction SilentlyContinue + } + Remove-Module $dependency.ModuleName -Force -ErrorAction SilentlyContinue + + if ($scopedIsPsResourceGetAvailable) + { + Write-Information -MessageData "Using Install-PSResource to install $($dependency.ModuleName) with version {$($dependency.RequiredVersion)}" + Install-PSResource -Name $dependency.ModuleName -Version $dependency.RequiredVersion -Scope $Scope -AcceptLicense -SkipDependencyCheck -TrustRepository -Repository $Repository + } + else + { + Write-Information -MessageData "Using Install-Module to install $($dependency.ModuleName) with version {$($dependency.RequiredVersion)}" + Install-Module $dependency.ModuleName -RequiredVersion $dependency.RequiredVersion -AllowClobber -Force -Scope "$Scope" @Params -Repository $Repository + } + } + } + + if ($dependency.ExplicitLoading) + { + Remove-Module $dependency.ModuleName -Force -ErrorAction SilentlyContinue + if ($dependency.Prefix) + { + Import-Module $dependency.ModuleName -Global -Prefix $dependency.Prefix -Force -DisableNameChecking + } + else + { + Import-Module $dependency.ModuleName -Global -Force -Alias @() -Cmdlet @() -Variable @() -DisableNameChecking + } + } + + if (-not $found -and $validateOnly) + { + $returnValue += $dependency + } + } + catch + { + Write-Error -Message "Could not update or import {$($dependency.ModuleName)}: $($_.Exception.Message)" -ErrorAction Continue + } + + $i++ + } + + # The progress bar seems to hang sometimes. Make sure it is no longer displayed. + Write-Progress -Activity 'Scanning dependencies' -Completed + + if ($ValidateOnly) + { + return $returnValue + } + } + catch + { + New-M365DSCLogEntry -Message 'Error updating dependencies:' ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) + Write-Error $_ -ErrorAction Continue + } +} + +<# +.DESCRIPTION + This function updates the module, dependencies and uninstalls outdated dependencies. + +.PARAMETER Scope + Specifies the scope of the update of the module. The default value is AllUsers(needs to run as elevated user). + +.PARAMETER Proxy + Specifies the proxy server to use for the update. + +.PARAMETER BaseRepository + Specifies the PowerShell Repository name to use for the installation of the Microsoft365DSC module. + +.PARAMETER DependencyRepository + Specifies the PowerShell Repository name to use for the installation of the dependencies of the Microsoft365DSC module. + +.PARAMETER NoUninstall + Indicates if outdated dependencies and modules should be uninstalled. + +.EXAMPLE + PS> Update-M365DSCModule + +.EXAMPLE + PS> Update-M365DSCModule -Scope CurrentUser + +.EXAMPLE + PS> Update-M365DSCModule -Scope AllUsers + +.FUNCTIONALITY + Public +#> +function Update-M365DSCModule +{ + [CmdletBinding()] + param( + [Parameter()] + [ValidateSet("CurrentUser", "AllUsers")] + $Scope = "AllUsers", + + [Parameter()] + [System.String] + $Proxy, + + [Parameter()] + [System.String] + $BaseRepository = 'PSGallery', + + [Parameter()] + [System.String] + $DependencyRepository = 'PSGallery', + + [Parameter()] + [switch] + $NoUninstall + ) + + $params = @{} + + if (-not [System.String]::IsNullOrEmpty($proxy)) + { + $params.Add('Proxy', $Proxy) + } + try + { + Update-Module -Name 'Microsoft365DSC' @Params -ErrorAction Stop + } + catch + { + if ($_.Exception.Message -like "*Module 'Microsoft365DSC' was not installed by using Install-Module*") + { + Write-Verbose -Message "The Microsoft365DSC module might have been installed with Install-PSResource" + if ($Script:IsPsResourceGetAvailable) + { + Write-Verbose -Message "Updating the Microsoft365DSC module using Update-PSResource..." + try + { + Update-PSResource -Name 'Microsoft365DSC' -Scope $Scope ` + -TrustRepository -AcceptLicense -SkipDependencyCheck ` + -Repository $BaseRepository -ErrorAction Stop + } + catch + { + if ($_.Exception.Message -like "*No installed packages*") + { + Write-Verbose -Message "Microsoft365DSC was neither installed using Install-Module nor Install-PSResource. Skipping update check." + } + else + { + New-M365DSCLogEntry -Message 'Error Updating Module:' ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) + throw $_ + } + } + } + } + } + try + { + Write-Verbose -Message "Unloading all instances of the Microsoft365DSC module from the current PowerShell session." + Remove-Module Microsoft365DSC -Force + + Write-Verbose -Message "Retrieving all versions of the Microsoft365DSC installed on the machine." + [Array]$instances = Get-Module Microsoft365DSC -ListAvailable | Sort-Object -Property Version -Descending + if ($instances.Length -gt 0) + { + Write-Verbose -Message "Loading version {$($instances[0].Version.ToString())} of the Microsoft365DSC module from {$($instances[0].ModuleBase)}" + Import-Module Microsoft365DSC -RequiredVersion $instances[0].Version.ToString() -Force + } + } + catch + { + New-M365DSCLogEntry -Message 'Error Updating Module:' ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) + throw $_ + } + + Update-M365DSCDependencies -Scope $Scope -Proxy $Proxy -Repository $DependencyRepository + + if (-not $NoUninstall) + { + Uninstall-M365DSCOutdatedDependencies + } +} + +Export-ModuleMember -Function @( + 'Confirm-M365DSCDependencies', + 'Confirm-M365DSCLoadedModule', + 'Confirm-M365DSCModuleDependency', + 'Get-M365DSCModuleConfiguration', + 'Get-M365DSCRequiredModules', + 'Get-M365DSCResourceSettings', + 'Set-M365DSCModuleConfiguration', + 'Test-M365DSCDependenciesForNewVersions', + 'Test-M365DSCModuleValidity', + 'Uninstall-M365DSCOutdatedDependencies', + 'Update-M365DSCDependencies', + 'Update-M365DSCModule' +) diff --git a/Modules/Microsoft365DSC/Modules/M365DSCReport.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCReport.psm1 index a40ba476f2..695f01ecfd 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCReport.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCReport.psm1 @@ -1,3 +1,6 @@ +# Automatically initialize accelerator on module import +Initialize-M365DSCDllLoader -ErrorAction SilentlyContinue + $Script:ReportCSS = @"