diff --git a/CHANGELOG.md b/CHANGELOG.md index 02ce28caaf..3697a33a6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,27 @@ # Change log for Microsoft365DSC +# 1.23.1101.1 + +* AADRoleEligibilityScheduleRequest + * Fixes how the Get method retrieves existing instances for Groups. + FIXES [#3787](https://github.com/microsoft/Microsoft365DSC/issues/3787) +* SCSecurityFilter + * Fixes an issue because Region could be empty + FIXES: [#3854](https://github.com/microsoft/Microsoft365DSC/issues/3854) +* SPOSharingSettings + * Fixes parameter validation of ExternalUserExpireInDays and ExternalUserExpirationRequired. + FIXES [#3856](https://github.com/microsoft/Microsoft365DSC/issues/3856) +* TeamsComplianceRecordingPolicy + * Fix an issue where the Compliance Application ID wasn't properly retrieved. + FIXES [#3848](https://github.com/microsoft/Microsoft365DSC/issues/3848) + # 1.23.1025.1 +* AADEntitlementManagementAccessPackageAssignmentPolicy + * Fixes an issue where reviewers were not properly exported +* M365DSCDRGUTIL + * Fixes an issue with Get-M365DSCDRGComplexTypeToHashtable where Beta cmdlet were not recognized for recursive calls + FIXES [#3448](https://github.com/microsoft/Microsoft365DSC/issues/3448) * AADApplication * Changes to how permissions drifts are logged. FIXES [#3830](https://github.com/microsoft/Microsoft365DSC/issues/3830) @@ -33,6 +53,9 @@ * IntuneAntivirusPolicyWindows10SettingCatalog * Added "-All" parameter to retrieve all settings from a template. FIXES [#3722](https://github.com/microsoft/Microsoft365DSC/issues/3722) +* IntuneDeviceCleanupRule + * Initial release. + FIXES [#3599](https://github.com/microsoft/Microsoft365DSC/issues/3599) * TeamsGroupPolicyAssignment * Fixes the export of CsGroup, when the display name of a group is included in another display name. diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADEntitlementManagementAccessPackageAssignmentPolicy/MSFT_AADEntitlementManagementAccessPackageAssignmentPolicy.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADEntitlementManagementAccessPackageAssignmentPolicy/MSFT_AADEntitlementManagementAccessPackageAssignmentPolicy.psm1 index 099402bc79..030d514a65 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADEntitlementManagementAccessPackageAssignmentPolicy/MSFT_AADEntitlementManagementAccessPackageAssignmentPolicy.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADEntitlementManagementAccessPackageAssignmentPolicy/MSFT_AADEntitlementManagementAccessPackageAssignmentPolicy.psm1 @@ -126,7 +126,7 @@ function Get-TargetResource Write-Verbose -Message "Found access package assignment policy with id {$($getValue.Id)} and DisplayName {$DisplayName}" #region Format AccessReviewSettings - $formattedAccessReviewSettings = Get-M365DSCDRGComplexTypeToHashtable -ComplexObject $getValue.AccessReviewSettings + $formattedAccessReviewSettings = Get-M365DSCDRGComplexTypeToHashtable -ComplexObject $getValue.AccessReviewSettings -Verbose if($null -ne $formattedAccessReviewSettings) { $formattedAccessReviewSettings.remove('additionalProperties') | Out-Null @@ -139,6 +139,7 @@ function Get-TargetResource if (-not [String]::isNullOrEmpty($setting.AdditionalProperties.id)) { $user = Get-MgUser -UserId $setting.AdditionalProperties.id -ErrorAction SilentlyContinue + if ($null -ne $user) { $setting.add('Id', $user.UserPrincipalName) @@ -148,7 +149,7 @@ function Get-TargetResource { $setting.add('ManagerLevel', $setting.AdditionalProperties.managerLevel) } - $setting.remove('additionalProperties') | Out-Null + $setting.remove('AdditionalProperties') | Out-Null } } #endregion @@ -170,7 +171,11 @@ function Get-TargetResource $setting.add('odataType', $setting.AdditionalProperties.'@odata.type') if (-not [String]::isNullOrEmpty($setting.AdditionalProperties.id)) { - $setting.add('Id', $setting.AdditionalProperties.id) + $user = Get-MgUser -UserId $setting.AdditionalProperties.id -ErrorAction SilentlyContinue + if ($null -ne $user) + { + $setting.add('Id', $user.UserPrincipalName) + } } if (-not [String]::isNullOrEmpty($setting.AdditionalProperties.managerLevel)) { @@ -187,7 +192,11 @@ function Get-TargetResource $setting.add('odataType', $setting.AdditionalProperties.'@odata.type') if (-not [String]::isNullOrEmpty($setting.AdditionalProperties.id)) { - $setting.add('Id', $setting.AdditionalProperties.id) + $user = Get-MgUser -UserId $setting.AdditionalProperties.id -ErrorAction SilentlyContinue + if ($null -ne $user) + { + $setting.add('Id', $user.UserPrincipalName) + } } if (-not [String]::isNullOrEmpty($setting.AdditionalProperties.managerLevel)) { @@ -462,6 +471,36 @@ function Set-TargetResource } } } + if ( $null -ne $CreateParameters.RequestApprovalSettings.ApprovalStages.PrimaryApprovers) + { + for ($i = 0; $i -lt $CreateParameters.RequestApprovalSettings.ApprovalStages.PrimaryApprovers.Length; $i++) + { + $primaryApprover = $CreateParameters.RequestApprovalSettings.ApprovalStages.PrimaryApprovers[$i] + if ($null -ne $primaryApprover.id) + { + $user = Get-MgUser -Filter "startswith(UserPrincipalName, '$($primaryApprover.Id.Split('@')[0])')" -ErrorAction SilentlyContinue + if ($null -ne $user) + { + $CreateParameters.RequestApprovalSettings.ApprovalStages.PrimaryApprovers[$i].Id = $user.Id + } + } + } + } + if ( $null -ne $CreateParameters.RequestApprovalSettings.ApprovalStages.EscalationApprovers) + { + for ($i = 0; $i -lt $CreateParameters.RequestApprovalSettings.ApprovalStages.EscalationApprovers.Length; $i++) + { + $escalationApprover = $CreateParameters.RequestApprovalSettings.ApprovalStages.EscalationApprovers[$i] + if ($null -ne $escalationApprover.id) + { + $user = Get-MgUser -Filter "startswith(UserPrincipalName, '$($escalationApprover.Id.Split('@')[0])')" -ErrorAction SilentlyContinue + if ($null -ne $user) + { + $CreateParameters.RequestApprovalSettings.ApprovalStages.EscalationApprovers[$i].Id = $user.Id + } + } + } + } if ($null -ne $CreateParameters.RequestorSettings -and $null -ne $CreateParameters.RequestorSettings.AllowedRequestors) { for ($i = 0; $i -lt $CreateParameters.RequestorSettings.AllowedRequestors.Length; $i++) @@ -527,6 +566,36 @@ function Set-TargetResource } } } + if ($null -ne $UpdateParameters.RequestApprovalSettings.ApprovalStages.PrimaryApprovers) + { + for ($i = 0; $i -lt $UpdateParameters.RequestApprovalSettings.ApprovalStages.PrimaryApprovers.Length; $i++) + { + $primaryApprover = $UpdateParameters.RequestApprovalSettings.ApprovalStages.PrimaryApprovers[$i] + if ($null -ne $primaryApprover.id) + { + $user = Get-MgUser -Filter "startswith(UserPrincipalName, '$($primaryApprover.Id.Split('@')[0])')" -ErrorAction SilentlyContinue + if ($null -ne $user) + { + $UpdateParameters.RequestApprovalSettings.ApprovalStages.PrimaryApprovers[$i].Id = $user.Id + } + } + } + } + if ($null -ne $UpdateParameters.RequestApprovalSettings.ApprovalStages.EscalationApprovers) + { + for ($i = 0; $i -lt $UpdateParameters.RequestApprovalSettings.ApprovalStages.EscalationApprovers.Length; $i++) + { + $escalationApprover = $UpdateParameters.RequestApprovalSettings.ApprovalStages.EscalationApprovers[$i] + if ($null -ne $escalationApprover.id) + { + $user = Get-MgUser -Filter "startswith(UserPrincipalName, '$($escalationApprover.Id.Split('@')[0])')" -ErrorAction SilentlyContinue + if ($null -ne $user) + { + $UpdateParameters.RequestApprovalSettings.ApprovalStages.EscalationApprovers[$i].Id = $user.Id + } + } + } + } if ($null -ne $UpdateParameters.RequestorSettings -and $null -ne $UpdateParameters.RequestorSettings.AllowedRequestors) { #Write-Verbose -Message "Updating Requestors' Id" @@ -810,7 +879,17 @@ function Export-TargetResource if ($null -ne $Results.AccessReviewSettings) { - $complexTypeStringResult = Get-M365DSCDRGComplexTypeToString -ComplexObject $Results.AccessReviewSettings -CIMInstanceName MicrosoftGraphassignmentreviewsettings + $complexMapping = @( + @{ + Name = 'Reviewers' + CimInstanceName = 'MicrosoftGraphuserset' + IsRequired = $false + } + ) + $complexTypeStringResult = Get-M365DSCDRGComplexTypeToString ` + -ComplexObject $Results.AccessReviewSettings ` + -CIMInstanceName MicrosoftGraphassignmentreviewsettings ` + -ComplexTypeMapping $complexMapping if ($complexTypeStringResult) { $Results.AccessReviewSettings = $complexTypeStringResult @@ -939,7 +1018,7 @@ function Export-TargetResource if ($null -ne $Results.AccessReviewSettings) { $currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock -ParameterName 'AccessReviewSettings' - $currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock -ParameterName 'Reviewers' + #$currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock -ParameterName 'Reviewers' } if ($null -ne $Results.Questions ) { diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/MSFT_AADRoleEligibilityScheduleRequest.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/MSFT_AADRoleEligibilityScheduleRequest.psm1 index 873eddbbce..2e4b3c31ac 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/MSFT_AADRoleEligibilityScheduleRequest.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/MSFT_AADRoleEligibilityScheduleRequest.psm1 @@ -177,10 +177,16 @@ $RoleDefinitionId = (Get-MgBetaRoleManagementDirectoryRoleDefinition -Filter "DisplayName eq '$RoleDefinition'").Id Write-Verbose -Message "Found Role {$RoleDefinitionId}" + $schedule = Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule -Filter "PrincipalId eq '$PrincipalId' and RoleDefinitionId eq '$RoleDefinitionId'" $request = Get-MgBetaRoleManagementDirectoryRoleEligibilityScheduleRequest -Filter "PrincipalId eq '$PrincipalId' and RoleDefinitionId eq '$RoleDefinitionId'" } } - if ($null -eq $request) + else + { + $RoleDefinitionId = (Get-MgBetaRoleManagementDirectoryRoleDefinition -Filter "DisplayName eq '$RoleDefinition'").Id + $schedule = Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule -Filter "PrincipalId eq '$($request.PrincipalId)' and RoleDefinitionId eq '$RoleDefinitionId'" + } + if ($null -eq $schedule -or $null -eq $request) { return $nullResult } @@ -201,47 +207,46 @@ { return $nullResult } - $RoleDefinitionValue = Get-MgBetaRoleManagementDirectoryRoleDefinition -UnifiedRoleDefinitionId $request.RoleDefinitionId $ScheduleInfoValue = @{} - if ($null -ne $request.ScheduleInfo.Expiration) + if ($null -ne $schedule.ScheduleInfo.Expiration) { $expirationValue = @{ - duration = $request.ScheduleInfo.Expiration.Duration - type = $request.ScheduleInfo.Expiration.Type + duration = $schedule.ScheduleInfo.Expiration.Duration + type = $schedule.ScheduleInfo.Expiration.Type } - if ($null -ne $request.ScheduleInfo.Expiration.EndDateTime) + if ($null -ne $schedule.ScheduleInfo.Expiration.EndDateTime) { - $expirationValue.Add('endDateTime', $request.ScheduleInfo.Expiration.EndDateTime.ToString("yyyy-MM-ddThh:mm:ssZ")) + $expirationValue.Add('endDateTime', $schedule.ScheduleInfo.Expiration.EndDateTime.ToString("yyyy-MM-ddThh:mm:ssZ")) } $ScheduleInfoValue.Add('expiration', $expirationValue) } - if ($null -ne $request.ScheduleInfo.Recurrence) + if ($null -ne $schedule.ScheduleInfo.Recurrence) { $recurrenceValue = @{ pattern = @{ - dayOfMonth = $request.ScheduleInfo.Recurrence.Pattern.dayOfMonth - daysOfWeek = $request.ScheduleInfo.Recurrence.Pattern.daysOfWeek - firstDayOfWeek = $request.ScheduleInfo.Recurrence.Pattern.firstDayOfWeek - index = $request.ScheduleInfo.Recurrence.Pattern.index - interval = $request.ScheduleInfo.Recurrence.Pattern.interval - month = $request.ScheduleInfo.Recurrence.Pattern.month - type = $request.ScheduleInfo.Recurrence.Pattern.type + dayOfMonth = $schedule.ScheduleInfo.Recurrence.Pattern.dayOfMonth + daysOfWeek = $schedule.ScheduleInfo.Recurrence.Pattern.daysOfWeek + firstDayOfWeek = $schedule.ScheduleInfo.Recurrence.Pattern.firstDayOfWeek + index = $schedule.ScheduleInfo.Recurrence.Pattern.index + interval = $schedule.ScheduleInfo.Recurrence.Pattern.interval + month = $schedule.ScheduleInfo.Recurrence.Pattern.month + type = $schedule.ScheduleInfo.Recurrence.Pattern.type } range = @{ - endDate = $request.ScheduleInfo.Recurrence.Range.endDate - numberOfOccurrences = $request.ScheduleInfo.Recurrence.Range.numberOfOccurrences - recurrenceTimeZone = $request.ScheduleInfo.Recurrence.Range.recurrenceTimeZone - startDate = $request.ScheduleInfo.Recurrence.Range.startDate - type = $request.ScheduleInfo.Recurrence.Range.type + endDate = $schedule.ScheduleInfo.Recurrence.Range.endDate + numberOfOccurrences = $schedule.ScheduleInfo.Recurrence.Range.numberOfOccurrences + recurrenceTimeZone = $schedule.ScheduleInfo.Recurrence.Range.recurrenceTimeZone + startDate = $schedule.ScheduleInfo.Recurrence.Range.startDate + type = $schedule.ScheduleInfo.Recurrence.Range.type } } $ScheduleInfoValue.Add('Recurrence', $recurrenceValue) } - if ($null -ne $request.ScheduleInfo.StartDateTime) + if ($null -ne $schedule.ScheduleInfo.StartDateTime) { - $ScheduleInfoValue.Add('StartDateTime', $request.ScheduleInfo.StartDateTime.ToString("yyyy-MM-ddThh:mm:ssZ")) + $ScheduleInfoValue.Add('StartDateTime', $schedule.ScheduleInfo.StartDateTime.ToString("yyyy-MM-ddThh:mm:ssZ")) } $ticketInfoValue = $null @@ -254,11 +259,11 @@ } $PrincipalValue = $null - if ($PrincipalTypeValue -eq 'User') + if ($PrincipalType -eq 'User') { $PrincipalValue = $PrincipalInstance.UserPrincipalName } - elseif ($PrincipalTypeValue -eq 'Group') + if ($null -eq $PrincipalValue -or $PrincipalTypeValue -eq 'Group') { $PrincipalValue = $PrincipalInstance.DisplayName } @@ -266,7 +271,7 @@ $results = @{ Principal = $PrincipalValue PrincipalType = $PrincipalTypeValue - RoleDefinition = $RoleDefinitionValue.DisplayName + RoleDefinition = $RoleDefinition DirectoryScopeId = $request.DirectoryScopeId AppScopeId = $request.AppScopeId Action = $request.Action @@ -729,10 +734,10 @@ function Export-TargetResource #region resource generator code $schedules = Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule -All -ErrorAction Stop [array] $Script:exportedInstances = @() - foreach ($schedule in $schedules) - { - [array] $allRequests = Get-MgBetaRoleManagementDirectoryRoleEligibilityScheduleRequest -All ` + [array] $allRequests = Get-MgBetaRoleManagementDirectoryRoleEligibilityScheduleRequest -All ` -Filter "Status ne 'Revoked'" -ErrorAction Stop + foreach ($schedule in $schedules) + { [array] $Script:exportedInstances += $allRequests | Where-Object -FilterScript {$_.TargetScheduleId -eq $schedule.Id} } #endregion @@ -751,10 +756,12 @@ function Export-TargetResource { $displayedKey = $request.Id Write-Host " |---[$i/$($Script:exportedInstances.Count)] $displayedKey" -NoNewline + + $RoleDefinitionId = Get-MgBetaRoleManagementDirectoryRoleDefinition -UnifiedRoleDefinitionId $request.RoleDefinitionId $params = @{ Id = $request.Id Principal = $request.PrincipalId - RoleDefinition = 'TempDefinition' + RoleDefinition = $RoleDefinitionId.DisplayName ScheduleInfo = 'TempSchedule' Ensure = 'Present' Credential = $Credential diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCleanupRule/MSFT_IntuneDeviceCleanupRule.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCleanupRule/MSFT_IntuneDeviceCleanupRule.psm1 new file mode 100644 index 0000000000..f60d059e3f --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCleanupRule/MSFT_IntuneDeviceCleanupRule.psm1 @@ -0,0 +1,398 @@ +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + [ValidateSet('Yes')] + $IsSingleInstance, + + [Parameter(Mandatory = $true)] + [System.Boolean] + $Enabled, + + [Parameter()] + [System.Int32] + [ValidateRange(30, 270)] + $DeviceInactivityBeforeRetirementInDays, + + [Parameter()] + [System.String] + [ValidateSet('Absent', 'Present')] + $Ensure = 'Present', + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity + ) + + if ($Enabled -and $DeviceInactivityBeforeRetirementInDays -eq 0) + { + throw [System.ArgumentException]::new('DeviceInactivityBeforeRetirementInDays must be greater than 30 and less than 270 when Enabled is set to true.') + } + + Write-Verbose -Message "Checking for the Intune Device Cleanup Rule" + $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters + + #Ensure the proper dependencies are installed in the current environment. + Confirm-M365DSCDependencies + + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName -replace 'MSFT_', '' + $CommandName = $MyInvocation.MyCommand + $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` + -CommandName $CommandName ` + -Parameters $PSBoundParameters + Add-M365DSCTelemetryEvent -Data $data + #endregion + + $nullResult = $PSBoundParameters + $nullResult.Ensure = 'Absent' + + try + { + $url = $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.ResourceUrl + "beta/deviceManagement/managedDeviceCleanupSettings" + $cleanupRule = Invoke-MgGraphRequest -Method GET -Uri $url -ErrorAction Stop + + $return = @{ + Enabled = $cleanupRule.deviceInactivityBeforeRetirementInDays -gt 0 + IsSingleInstance = 'Yes' + Ensure = 'Present' + Credential = $Credential + ApplicationId = $ApplicationId + TenantId = $TenantId + ApplicationSecret = $ApplicationSecret + CertificateThumbprint = $CertificateThumbprint + Managedidentity = $ManagedIdentity.IsPresent + } + + if ($return.Enabled) + { + $return.Add('DeviceInactivityBeforeRetirementInDays', $cleanupRule.deviceInactivityBeforeRetirementInDays) + } + + return $return + } + catch + { + New-M365DSCLogEntry -Message 'Error retrieving data:' ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantId ` + -Credential $Credential + + return $nullResult + } +} + +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + [ValidateSet('Yes')] + $IsSingleInstance, + + [Parameter(Mandatory = $true)] + [System.Boolean] + $Enabled, + + [Parameter()] + [System.Int32] + [ValidateRange(30, 270)] + $DeviceInactivityBeforeRetirementInDays, + + [Parameter()] + [System.String] + [ValidateSet('Absent', 'Present')] + $Ensure = 'Present', + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity + ) + + if ($Enabled -and $DeviceInactivityBeforeRetirementInDays -eq 0) + { + throw [System.ArgumentException]::new('DeviceInactivityBeforeRetirementInDays must be greater than 30 and less than 270 when Enabled is set to true.') + } + + Write-Verbose -Message "Updating Device Cleanup Rule" + + $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters + + #Ensure the proper dependencies are installed in the current environment. + Confirm-M365DSCDependencies + + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName -replace 'MSFT_', '' + $CommandName = $MyInvocation.MyCommand + $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` + -CommandName $CommandName ` + -Parameters $PSBoundParameters + Add-M365DSCTelemetryEvent -Data $data + #endregion + + $url = $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.ResourceUrl + "beta/deviceManagement/managedDeviceCleanupSettings" + $body = @{ + DeviceInactivityBeforeRetirementInDays = "$(if ($Enabled) { $DeviceInactivityBeforeRetirementInDays } else { 0 })" + } + Invoke-MgGraphRequest -Method PATCH -Uri $url -Body ($body | ConvertTo-Json) +} + +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + [ValidateSet('Yes')] + $IsSingleInstance, + + [Parameter(Mandatory = $true)] + [System.Boolean] + $Enabled, + + [Parameter()] + [System.Int32] + [ValidateRange(30, 270)] + $DeviceInactivityBeforeRetirementInDays, + + [Parameter()] + [System.String] + [ValidateSet('Absent', 'Present')] + $Ensure = 'Present', + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity + ) + + if ($Enabled -and $DeviceInactivityBeforeRetirementInDays -eq 0) + { + throw [System.ArgumentException]::new('DeviceInactivityBeforeRetirementInDays must be greater than 30 and less than 270 when Enabled is set to true.') + } + + #Ensure the proper dependencies are installed in the current environment. + Confirm-M365DSCDependencies + + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName -replace 'MSFT_', '' + $CommandName = $MyInvocation.MyCommand + $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` + -CommandName $CommandName ` + -Parameters $PSBoundParameters + Add-M365DSCTelemetryEvent -Data $data + #endregion + Write-Verbose -Message "Testing configuration of Device Cleanup Rule" + + $CurrentValues = Get-TargetResource @PSBoundParameters + + Write-Verbose -Message "Current Values: $(Convert-M365DscHashtableToString -Hashtable $CurrentValues)" + Write-Verbose -Message "Target Values: $(Convert-M365DscHashtableToString -Hashtable $PSBoundParameters)" + + $ValuesToCheck = $PSBoundParameters + $ValuesToCheck.Remove('Credential') | Out-Null + $ValuesToCheck.Remove('ApplicationId') | Out-Null + $ValuesToCheck.Remove('ApplicationSecret') | Out-Null + $ValuesToCheck.Remove('TenantId') | Out-Null + + if ($CurrentValues.Enabled -eq $false) { + $ValuesToCheck.Remove('DeviceInactivityBeforeRetirementInDays') | Out-Null + } + + $TestResult = Test-M365DSCParameterState -CurrentValues $CurrentValues ` + -Source $($MyInvocation.MyCommand.Source) ` + -DesiredValues $PSBoundParameters ` + -ValuesToCheck $ValuesToCheck.Keys + + Write-Verbose -Message "Test-TargetResource returned $TestResult" + + return $TestResult +} + +function Export-TargetResource +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity + ) + $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters + + #Ensure the proper dependencies are installed in the current environment. + Confirm-M365DSCDependencies + + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName -replace 'MSFT_', '' + $CommandName = $MyInvocation.MyCommand + $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` + -CommandName $CommandName ` + -Parameters $PSBoundParameters + Add-M365DSCTelemetryEvent -Data $data + #endregion + + try + { + $url = $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.ResourceUrl + "beta/deviceManagement/managedDeviceCleanupSettings" + [array]$cleanupRules = Invoke-MgGraphRequest -Method GET -Uri $url -ErrorAction Stop + $i = 1 + $dscContent = '' + if ($categories.Length -eq 0) + { + Write-Host $Global:M365DSCEmojiGreenCheckMark + } + else + { + Write-Host "`r`n" -NoNewline + } + + foreach ($cleanupRule in $cleanupRules) + { + Write-Host " |---[$i/$($cleanupRules.Count)] Cleanup Rule" -NoNewline + $params = @{ + Enabled = $cleanupRule.deviceInactivityBeforeRetirementInDays -gt 0 + Ensure = 'Present' + IsSingleInstance = 'Yes' + Credential = $Credential + ApplicationId = $ApplicationId + ApplicationSecret = $ApplicationSecret + TenantId = $TenantId + CertificateThumbprint = $CertificateThumbprint + Managedidentity = $ManagedIdentity.IsPresent + } + + if ($params.Enabled) { + $params.Add('DeviceInactivityBeforeRetirementInDays', $cleanupRule.deviceInactivityBeforeRetirementInDays) + } + + $Results = Get-TargetResource @Params + $Results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode ` + -Results $Results + $currentDSCBlock = Get-M365DSCExportContentForResource -ResourceName $ResourceName ` + -ConnectionMode $ConnectionMode ` + -ModulePath $PSScriptRoot ` + -Results $Results ` + -Credential $Credential + $dscContent += $currentDSCBlock + Save-M365DSCPartialExport -Content $currentDSCBlock ` + -FileName $Global:PartialExportFileName + $i++ + Write-Host $Global:M365DSCEmojiGreenCheckMark + } + return $dscContent + } + catch + { + if ($_.Exception -like '*401*' -or $_.ErrorDetails.Message -like "*`"ErrorCode`":`"Forbidden`"*") + { + Write-Host "`r`n $($Global:M365DSCEmojiYellowCircle) The current tenant is not registered for Intune." + } + else + { + Write-Host $Global:M365DSCEmojiRedX + + New-M365DSCLogEntry -Message 'Error during Export:' ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantId ` + -Credential $Credential + } + + return '' + } +} + +Export-ModuleMember -Function *-TargetResource diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCleanupRule/MSFT_IntuneDeviceCleanupRule.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCleanupRule/MSFT_IntuneDeviceCleanupRule.schema.mof new file mode 100644 index 0000000000..8e83fcef35 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCleanupRule/MSFT_IntuneDeviceCleanupRule.schema.mof @@ -0,0 +1,14 @@ +[ClassVersion("1.0.0.0"), FriendlyName("IntuneDeviceCleanupRule")] +class MSFT_IntuneDeviceCleanupRule : OMI_BaseResource +{ + [Key, Description("Only valid value is 'Yes'."), ValueMap{"Yes"}, Values{"Yes"}] String IsSingleInstance; + [Key, Description("Indicates whether the cleanup rule is enabled.")] Boolean Enabled; + [Write, Description("Number of days until Intune devices are deleted. Minimum: 30, Maximum: 270.")] UInt32 DeviceInactivityBeforeRetirementInDays; + [Write, Description("Present ensures the category exists, absent ensures it is removed."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] string Ensure; + [Write, Description("Credentials of the Intune Admin"), EmbeddedInstance("MSFT_Credential")] string Credential; + [Write, Description("Id of the Azure Active Directory application to authenticate with.")] String ApplicationId; + [Write, Description("Id of the Azure Active Directory tenant used for authentication.")] String TenantId; + [Write, Description("Secret of the Azure Active Directory tenant used for authentication."), EmbeddedInstance("MSFT_Credential")] String ApplicationSecret; + [Write, Description("Thumbprint of the Azure Active Directory application's authentication certificate to use for authentication.")] String CertificateThumbprint; + [Write, Description("Managed ID being used for authentication.")] Boolean ManagedIdentity; +}; diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCleanupRule/readme.md b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCleanupRule/readme.md new file mode 100644 index 0000000000..b5aa2bc25a --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCleanupRule/readme.md @@ -0,0 +1,6 @@ + +# IntuneDeviceCleanupRule + +## Description + +This resource configures the Intune device cleanup rule. diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCleanupRule/settings.json b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCleanupRule/settings.json new file mode 100644 index 0000000000..0ca5cbbdbb --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_IntuneDeviceCleanupRule/settings.json @@ -0,0 +1,32 @@ +{ + "resourceName": "IntuneDeviceCleanupRule", + "description": "This resource configures the Intune device cleanup rule.", + "permissions": { + "graph": { + "delegated": { + "read": [ + { + "name": "DeviceManagementManagedDevices.Read.All" + } + ], + "update": [ + { + "name": "DeviceManagementManagedDevices.ReadWrite.All" + } + ] + }, + "application": { + "read": [ + { + "name": "DeviceManagementManagedDevices.Read.All" + } + ], + "update": [ + { + "name": "DeviceManagementManagedDevices.ReadWrite.All" + } + ] + } + } + } +} diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_SCSecurityFilter/MSFT_SCSecurityFilter.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_SCSecurityFilter/MSFT_SCSecurityFilter.psm1 index 595704ada4..1e87cf5340 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_SCSecurityFilter/MSFT_SCSecurityFilter.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_SCSecurityFilter/MSFT_SCSecurityFilter.psm1 @@ -37,13 +37,13 @@ function Get-TargetResource 'IND', # India 'JPN', # Japan 'LAM', # Latin America - 'NAM' # North America + 'NAM', # North America + '' # NOT MANDATORY )] [System.String] $Region, # And the DSC ones - [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] @@ -227,7 +227,8 @@ function Set-TargetResource 'IND', # India 'JPN', # Japan 'LAM', # Latin America - 'NAM' # North America + 'NAM', # North America + '' # NOT MANDATORY )] [System.String] $Region, @@ -393,7 +394,8 @@ function Test-TargetResource 'IND', # India 'JPN', # Japan 'LAM', # Latin America - 'NAM' # North America + 'NAM', # North America + '' # NOT MANDATORY )] [System.String] $Region, diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_SPOSharingSettings/MSFT_SPOSharingSettings.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_SPOSharingSettings/MSFT_SPOSharingSettings.psm1 index 3511c3159f..f56913aaa1 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_SPOSharingSettings/MSFT_SPOSharingSettings.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_SPOSharingSettings/MSFT_SPOSharingSettings.psm1 @@ -447,9 +447,9 @@ function Set-TargetResource Write-Warning -Message 'The sharing capabilities for the tenant are not configured to be ExternalUserAndGuestSharing for that the RequireAnonymousLinksExpireInDays property cannot be configured' $CurrentParameters.Remove('RequireAnonymousLinksExpireInDays') | Out-Null } - if ($SharingCapability -ne 'ExternalUserExpirationRequired') + if ($ExternalUserExpireInDays -and $ExternalUserExpirationRequired -eq $false) { - Write-Warning -Message 'The sharing capabilities for the tenant are not configured to be ExternalUserExpirationRequired for that the ExternalUserExpireInDays property cannot be configured' + Write-Warning -Message 'ExternalUserExpirationRequired is set to be false. For that the ExternalUserExpireInDays property cannot be configured' $CurrentParameters.Remove('ExternalUserExpireInDays') | Out-Null } if ($RequireAcceptingAccountMatchInvitedAccount -eq $false) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsComplianceRecordingPolicy/MSFT_TeamsComplianceRecordingPolicy.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsComplianceRecordingPolicy/MSFT_TeamsComplianceRecordingPolicy.psm1 index feffced474..14e0c8c493 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsComplianceRecordingPolicy/MSFT_TeamsComplianceRecordingPolicy.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_TeamsComplianceRecordingPolicy/MSFT_TeamsComplianceRecordingPolicy.psm1 @@ -74,11 +74,20 @@ function Get-TargetResource { return $nullResult } + $recordingApplications = [Array](Get-CsTeamsComplianceRecordingApplication -Filter "$($instance.Identity)/*") + if ($null -eq $recordingApplications) + { + $recordingApplications = @() + } + $recordApplicationIds = @() + foreach ($app in $recordingApplications) { + $recordApplicationIds += @{Id=$app.Id} + } Write-Verbose -Message "Found an instance with Identity {$Identity}" $results = @{ Identity = $instance.Identity - ComplianceRecordingApplications = [Array]$instance.ComplianceRecordingApplications.Id + ComplianceRecordingApplications = $recordApplicationIds Description = $instance.Description DisableComplianceRecordingAudioNotificationForCalls = $instance.DisableComplianceRecordingAudioNotificationForCalls Enabled = $instance.Enabled diff --git a/Modules/Microsoft365DSC/Examples/Resources/IntuneDeviceCleanupRule/1-SetDeviceCleanupRule.ps1 b/Modules/Microsoft365DSC/Examples/Resources/IntuneDeviceCleanupRule/1-SetDeviceCleanupRule.ps1 new file mode 100644 index 0000000000..5299c98ef4 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/IntuneDeviceCleanupRule/1-SetDeviceCleanupRule.ps1 @@ -0,0 +1,25 @@ +<# +This example sets the device cleanup rule. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $credsGlobalAdmin + ) + Import-DscResource -ModuleName Microsoft365DSC + + node localhost + { + IntuneDeviceCleanupRule 'Example' + { + Enabled = $true + IsSingleInstance = 'Yes' + DeviceInactivityBeforeRetirementInDays = 30 + Ensure = 'Present' + Credential = $credsGlobalAdmin + } + } +} diff --git a/Modules/Microsoft365DSC/Microsoft365DSC.psd1 b/Modules/Microsoft365DSC/Microsoft365DSC.psd1 index e9001ef1e7..83383dfbf0 100644 --- a/Modules/Microsoft365DSC/Microsoft365DSC.psd1 +++ b/Modules/Microsoft365DSC/Microsoft365DSC.psd1 @@ -3,7 +3,7 @@ # # Generated by: Microsoft Corporation # -# Generated on: 2023-10-25 +# Generated on: 2023-11-01 @{ @@ -11,7 +11,7 @@ # RootModule = '' # Version number of this module. - ModuleVersion = '1.23.1025.1' + ModuleVersion = '1.23.1101.1' # Supported PSEditions # CompatiblePSEditions = @() @@ -140,24 +140,18 @@ IconUri = 'https://github.com/microsoft/Microsoft365DSC/blob/Dev/Modules/Microsoft365DSC/Dependencies/Images/Logo.png?raw=true' # ReleaseNotes of this module - ReleaseNotes = '* AADApplication - * Changes to how permissions drifts are logged. - FIXES [#3830](https://github.com/microsoft/Microsoft365DSC/issues/3830) - * AADAttributeSet - * Initial Release. - * AADAuthenticationContext - * Initial Release. - * AADConditionalAccessPolicy - * Adds support for Authentication Context. - FIXES [#3813](https://github.com/microsoft/Microsoft365DSC/issues/3813) - * AADSocialIdentityProvider - * Initial release. + ReleaseNotes = '* AADRoleEligibilityScheduleRequest + * Fixes how the Get method retrieves existing instances for Groups. + FIXES [#3787](https://github.com/microsoft/Microsoft365DSC/issues/3787) + * SCSecurityFilter + * Fixes an issue because Region could be empty + FIXES: [#3854](https://github.com/microsoft/Microsoft365DSC/issues/3854) + * SPOSharingSettings + * Fixes parameter validation of ExternalUserExpireInDays and ExternalUserExpirationRequired. + FIXES [#3856](https://github.com/microsoft/Microsoft365DSC/issues/3856) * TeamsComplianceRecordingPolicy - * Fixes an issue where the Compliance Application ID was not properly retrieved. - FIXES [#3712](https://github.com/microsoft/Microsoft365DSC/issues/3712) - * DEPENDENCIES - * Updated Microsoft.Graph dependencies to version 2.8.0. - * Updated MicrosoftTeams dependency to version 5.7.1.' + * Fix an issue where the Compliance Application ID was not properly retrieved. + FIXES [#3848](https://github.com/microsoft/Microsoft365DSC/issues/3848)' # Flag to indicate whether the module requires explicit user acceptance for install/update # RequireLicenseAcceptance = $false diff --git a/Modules/Microsoft365DSC/Modules/M365DSCDRGUtil.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCDRGUtil.psm1 index 53c6531a34..318f1becae 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCDRGUtil.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCDRGUtil.psm1 @@ -134,21 +134,21 @@ function Get-M365DSCDRGComplexTypeToHashtable return , [hashtable[]]$results } + if ($ComplexObject.getType().fullname -like '*Dictionary*') { $results = @{} $ComplexObject = [hashtable]::new($ComplexObject) $keys = $ComplexObject.Keys + foreach ($key in $keys) { if ($null -ne $ComplexObject.$key) { $keyName = $key - $keyType = $ComplexObject.$key.gettype().fullname - - if ($keyType -like '*CimInstance*' -or $keyType -like '*Dictionary*' -or $keyType -like 'Microsoft.Graph.PowerShell.Models.*' -or $keyType -like '*[[\]]') + 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 @@ -185,7 +185,7 @@ function Get-M365DSCDRGComplexTypeToHashtable 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.*') + if ($keyType -like '*CimInstance*' -or $keyType -like '*Dictionary*' -or $keyType -like 'Microsoft.Graph.*PowerShell.Models.*') { $hash = Get-M365DSCDRGComplexTypeToHashtable -ComplexObject $ComplexObject.$keyName diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleEligibilityScheduleRequest.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleEligibilityScheduleRequest.Tests.ps1 index a3330e6392..6f7082cd94 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleEligibilityScheduleRequest.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleEligibilityScheduleRequest.Tests.ps1 @@ -25,6 +25,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { $secpasswd = ConvertTo-SecureString 'test@password1' -AsPlainText -Force $Credential = New-Object System.Management.Automation.PSCredential ('tenantadmin@mydomain.com', $secpasswd) $Script:exportedInstances = $null + $Script:ExportMode = $null Mock -CommandName Add-M365DSCTelemetryEvent -MockWith { } @@ -159,8 +160,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { RoleDefinition = "Teams Communications Administrator"; ScheduleInfo = New-CimInstance -ClassName MSFT_AADRoleEligibilityScheduleRequestSchedule -Property @{ - expiration = New-CimInstance -ClassName MSFT_AADRoleEligibilityScheduleRequestScheduleExpiration -Property @{ - + expiration = New-CimInstance -ClassName MSFT_AADRoleEligibilityScheduleRequestScheduleExpiration -Property @{ type = 'afterDateTime' } -ClientOnly } -ClientOnly @@ -182,6 +182,21 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { }; } } + Mock -CommandName Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule -MockWith { + return @{ + Action = "AdminAssign"; + Id = '12345-12345-12345-12345-12345' + DirectoryScopeId = "/"; + IsValidationOnly = $False; + PrincipalId = "123456"; + RoleDefinitionId = "12345"; + ScheduleInfo = @{ + expiration = @{ + type = 'afterDateTime' + } + }; + } + } } It 'Should return Values from the Get method' { diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.IntuneDeviceCleanupRule.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.IntuneDeviceCleanupRule.Tests.ps1 new file mode 100644 index 0000000000..35a043cb2a --- /dev/null +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.IntuneDeviceCleanupRule.Tests.ps1 @@ -0,0 +1,120 @@ +[CmdletBinding()] +param( +) +$M365DSCTestFolder = Join-Path -Path $PSScriptRoot ` + -ChildPath '..\..\Unit' ` + -Resolve +$CmdletModule = (Join-Path -Path $M365DSCTestFolder ` + -ChildPath '\Stubs\Microsoft365.psm1' ` + -Resolve) +$GenericStubPath = (Join-Path -Path $M365DSCTestFolder ` + -ChildPath '\Stubs\Generic.psm1' ` + -Resolve) +Import-Module -Name (Join-Path -Path $M365DSCTestFolder ` + -ChildPath '\UnitTestHelper.psm1' ` + -Resolve) + +$Global:DscHelper = New-M365DscUnitTestHelper -StubModule $CmdletModule ` + -DscResource 'IntuneDeviceCleanupRule' -GenericStubModule $GenericStubPath + +Describe -Name $Global:DscHelper.DescribeHeader -Fixture { + InModuleScope -ModuleName $Global:DscHelper.ModuleName -ScriptBlock { + Invoke-Command -ScriptBlock $Global:DscHelper.InitializeScript -NoNewScope + + BeforeAll { + $secpasswd = ConvertTo-SecureString 'Pass@word1' -AsPlainText -Force + $Credential = New-Object System.Management.Automation.PSCredential ('tenantadmin@mydomain.com', $secpasswd) + + Mock -CommandName Confirm-M365DSCDependencies -MockWith { + } + + Mock -CommandName New-M365DSCConnection -MockWith { + return 'Credentials' + } + + Mock -CommandName Invoke-MgGraphRequest -MockWith { + } + + # Mock Write-Host to hide output during the tests + Mock -CommandName Write-Host -MockWith { + } + } + + # Test contexts + Context -Name 'When the policy is NOT in the Desired State' -Fixture { + BeforeAll { + $testParams = @{ + Enabled = $true + DeviceInactivityBeforeRetirementInDays = 30 + Ensure = 'Present' + IsSingleInstance = 'Yes' + Credential = $Credential + } + + Mock -CommandName Invoke-MgGraphRequest -MockWith { + return @{ + DeviceInactivityBeforeRetirementInDays = 0 + } + } + } + + It 'Should return Present from the Get method' { + (Get-TargetResource @testParams).Ensure | Should -Be 'Present' + } + + It 'Should return false from the Test method' { + Test-TargetResource @testParams | Should -Be $false + } + + It 'Should update the device cleanup rule from the Set method' { + Set-TargetResource @testParams + Should -Invoke -CommandName Invoke-MgGraphRequest -Exactly 1 + } + } + + Context -Name 'When the policy IS in the Desired State' -Fixture { + BeforeAll { + $testParams = @{ + Enabled = $true + DeviceInactivityBeforeRetirementInDays = 30 + Ensure = 'Present' + IsSingleInstance = 'Yes' + Credential = $Credential + } + + Mock -CommandName Invoke-MgGraphRequest -MockWith { + return @{ + DeviceInactivityBeforeRetirementInDays = 30 + } + } + } + + It 'Should return true from the Test method' { + Test-TargetResource @testParams | Should -Be $true + } + } + + Context -Name 'ReverseDSC Tests' -Fixture { + BeforeAll { + $Global:CurrentModeIsExport = $true + $Global:PartialExportFileName = "$(New-Guid).partial.ps1" + $testParams = @{ + Credential = $Credential + } + + Mock -CommandName Invoke-MgGraphRequest -MockWith { + return @{ + DeviceInactivityBeforeRetirementInDays = 30 + } + } + } + + It 'Should Reverse Engineer resource from the Export method' { + $result = Export-TargetResource @testParams + $result | Should -Not -BeNullOrEmpty + } + } + } +} + +Invoke-Command -ScriptBlock $Global:DscHelper.CleanupScript -NoNewScope diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.TeamsComplianceRecordingPolicy.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.TeamsComplianceRecordingPolicy.Tests.ps1 index a0d97fe0c9..6ea4b41bd0 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.TeamsComplianceRecordingPolicy.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.TeamsComplianceRecordingPolicy.Tests.ps1 @@ -63,7 +63,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Description = 'FakeStringValue' Enabled = $True DisableComplianceRecordingAudioNotificationForCalls = $True - ComplianceRecordingApplications = 'FakeStringValue' + ComplianceRecordingApplications = @(@{Id="123456"}) Identity = 'FakeStringValue' Ensure = 'Present' Credential = $Credential @@ -95,7 +95,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Description = 'FakeStringValue' Enabled = $True DisableComplianceRecordingAudioNotificationForCalls = $True - ComplianceRecordingApplications = 'FakeStringValue' + ComplianceRecordingApplications = @(@{Id='123456'}) Identity = 'FakeStringValue' Ensure = 'Absent' Credential = $Credential @@ -107,15 +107,27 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Description = 'FakeStringValue' Enabled = $True DisableComplianceRecordingAudioNotificationForCalls = $True - ComplianceRecordingApplications = @{Id='FakeStringValue'} + ComplianceRecordingApplications = "Microsoft.Teams.Policy.Aministration.Cmdlets.Core.CompianceRecordingApplication" Identity = 'FakeStringValue' - } } + Mock -CommandName Get-CsTeamsComplianceRecordingApplication -MockWith { + return @{ + Identity = 'FakeStringValue/123456' + Id = '123456' + } + } + } It 'Should return Values from the Get method' { - (Get-TargetResource @testParams).Ensure | Should -Be 'Present' + $Result = (Get-TargetResource @testParams) + $Result.Ensure | Should -Be 'Present' + $Result.ComplianceRecordingApplications.Length | Should -Be 1 + $Result.ComplianceRecordingApplications[0].Id | Should -Be '123456' + Should -Invoke -CommandName Get-CsTeamsComplianceRecordingPolicy -Exactly 1 + Should -Invoke -CommandName Get-CsTeamsComplianceRecordingApplication -ParameterFilter {$Filter -eq 'FakeStringValue/*'} -Exactly 1 + } It 'Should return true from the Test method' { @@ -135,7 +147,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Description = 'FakeStringValue' Enabled = $True DisableComplianceRecordingAudioNotificationForCalls = $True - ComplianceRecordingApplications = 'FakeStringValue' + ComplianceRecordingApplications = @(@{Id='123456'}) Identity = 'FakeStringValue' Ensure = 'Present' Credential = $Credential @@ -147,11 +159,17 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Description = 'FakeStringValue' Enabled = $True DisableComplianceRecordingAudioNotificationForCalls = $True - ComplianceRecordingApplications = @{Id='FakeStringValue'} + ComplianceRecordingApplications = "Microsoft.Teams.Policy.Aministration.Cmdlets.Core.CompianceRecordingApplication" Identity = 'FakeStringValue' } } + Mock -CommandName Get-CsTeamsComplianceRecordingApplication -MockWith { + return @{ + Identity = 'FakeStringValue/123456' + Id = '123456' + } + } } It 'Should return true from the Test method' { @@ -166,7 +184,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Description = 'FakeStringValue' Enabled = $True DisableComplianceRecordingAudioNotificationForCalls = $True - ComplianceRecordingApplications = 'FakeStringValue' + ComplianceRecordingApplications = @{Id='123456'} Identity = 'FakeStringValue' Ensure = 'Present' Credential = $Credential @@ -178,10 +196,17 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Description = 'FakeStringValueDrift' #Drift Enabled = $False DisableComplianceRecordingAudioNotificationForCalls = $False - ComplianceRecordingApplications = @{Id='FakeStringValueDrift'} #Drift + ComplianceRecordingApplications = "Microsoft.Teams.Policy.Aministration.Cmdlets.Core.CompianceRecordingApplication" Identity = 'FakeStringValue' } } + + Mock -CommandName Get-CsTeamsComplianceRecordingApplication -MockWith { + return @{ + Identity = 'FakeStringValue/123456Drift' + Id = '123456Drift' #Drift + } + } } It 'Should return Values from the Get method' { @@ -212,11 +237,18 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Description = 'FakeStringValue' Enabled = $True DisableComplianceRecordingAudioNotificationForCalls = $True - ComplianceRecordingApplications = @{Id='FakeStringValue'} + ComplianceRecordingApplications = "Microsoft.Teams.Policy.Aministration.Cmdlets.Core.CompianceRecordingApplication" Identity = 'FakeStringValue' } } + Mock -CommandName Get-CsTeamsComplianceRecordingApplication -MockWith { + return @{ + Identity = 'FakeStringValue/123456' + Id = '123456' + } + } + } It 'Should Reverse Engineer resource from the Export method' { diff --git a/Tests/Unit/Stubs/Microsoft365.psm1 b/Tests/Unit/Stubs/Microsoft365.psm1 index 6c9faaecea..db2d079c8b 100644 --- a/Tests/Unit/Stubs/Microsoft365.psm1 +++ b/Tests/Unit/Stubs/Microsoft365.psm1 @@ -70486,6 +70486,19 @@ function Get-CsTeamsComplianceRecordingPolicy $Identity ) } +function Get-CsTeamsComplianceRecordingApplication +{ + [CmdletBinding()] + param( + [Parameter()] + [System.String] + $Filter, + + [Parameter()] + [System.String] + $Identity + ) +} function Get-CsTeamsEmergencyCallingPolicy { [CmdletBinding()] diff --git a/docs/docs/concepts/personas.md b/docs/docs/concepts/personas.md index f5da2b61f4..7efaac432e 100644 --- a/docs/docs/concepts/personas.md +++ b/docs/docs/concepts/personas.md @@ -69,7 +69,7 @@ This article describes the personas we've identified for Microsoft365DSC and pro Description: -

The Security Administrators are responsible for defining new Entra Identity policies, make updates to exsting ones and monitor them for configuration drifts at scale and across one or multiple tenants. Their goal is to ensure the overal security of the tenant by ensuring only authorized users can perform certain tasks. They are dealing with components such as:

+

The Security Administrators are responsible for defining new Entra Identity policies, make updates to existing ones and monitor them for configuration drifts at scale and across one or multiple tenants. Their goal is to ensure the overal security of the tenant by ensuring only authorized users can perform certain tasks. They are dealing with components such as: