diff --git a/CHANGELOG.md b/CHANGELOG.md index 347bee63d0..547f483cb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * AADAuthenticationRequirement * Changed Export logic to extract instances from all users. +* AADGroupEligibilitySchedule + * New resource for Privileged Identity Management (PIM) for Groups * AADOrganizationCertificateBasedAuthConfiguration * Fixed the primary key of the resource. FIXES [#5523](https://github.com/microsoft/Microsoft365DSC/issues/5523) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroupEligibilitySchedule/MSFT_AADGroupEligibilitySchedule.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroupEligibilitySchedule/MSFT_AADGroupEligibilitySchedule.psm1 new file mode 100644 index 0000000000..b827841667 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroupEligibilitySchedule/MSFT_AADGroupEligibilitySchedule.psm1 @@ -0,0 +1,913 @@ +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + #region resource generator code + [Parameter()] + [ValidateSet('owner','member','unknownFutureValue')] + [System.String] + $AccessId, + + [Parameter()] + [System.String] + $GroupId, + + [Parameter(Mandatory = $true)] + [System.String] + $GroupDisplayName, + + [Parameter()] + [ValidateSet('direct','group','unknownFutureValue')] + [System.String] + $MemberType, + + [Parameter()] + [System.String] + $PrincipalId, + + [Parameter()] + [System.String] + $PrincipalType, + + [Parameter()] + [System.String] + $PrincipalDisplayName, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $ScheduleInfo, + + [Parameter()] + [System.String] + $Id, + + #endregion + + [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, + + [Parameter()] + [System.String[]] + $AccessTokens + ) + + Write-Verbose -Message "Getting configuration of the Azure AD Group {$GroupDisplayName}Eligibility Schedule" + + try + { + $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' + + $getValue = $null + if($GroupId.Length -eq 0){ + $Filter = "DisplayName eq '" + $GroupDisplayName + "'" + $GroupId = (Get-MgGroup -Filter $Filter).Id + } + if ($Id -notmatch '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}_member_[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') { + $getId = Get-MgIdentityGovernancePrivilegedAccessGroupEligibilitySchedule ` + -Filter "Groupid eq '$GroupId'" ` + -ErrorAction SilentlyContinue + $Id = $getId.Id + } + + $uri = 'https://graph.microsoft.com/v1.0/identityGovernance/privilegedAccess/group/eligibilitySchedules/' + $Id + $getvalue = Invoke-GraphRequest -Uri $uri -Method Get -ErrorAction SilentlyContinue + + #endregion + if ($null -eq $getValue) + { + Write-Verbose -Message "Could not find an Azure AD Group Eligibility Schedule with {$GroupDisplayName}." + return $nullResult + } + $Id = $getValue.Id + Write-Verbose -Message "An Azure AD Group Eligibility Schedule with Id {$Id} and DisplayName {$GroupDisplayName} was found" + + #region resource generator code + $complexScheduleInfo = @{} + $complexExpiration = @{} + $complexExpiration.Add('Duration', $getValue.scheduleInfo.expiration.duration) + if ($null -ne $getValue.scheduleInfo.expiration.endDateTime) + { + $complexExpiration.Add('EndDateTime', ([DateTimeOffset]$getValue.scheduleInfo.expiration.endDateTime).ToString('')) + } + if ($null -ne $getValue.scheduleInfo.expiration.type) + { + $complexExpiration.Add('Type', $getValue.scheduleInfo.expiration.type.ToString()) + } + if ($complexExpiration.values.Where({$null -ne $_}).Count -eq 0) + { + $complexExpiration = $null + } + $complexScheduleInfo.Add('Expiration',$complexExpiration) + $complexRecurrence = @{} + $complexPattern = @{} + $complexPattern.Add('DayOfMonth', $getValue.scheduleInfo.recurrence.pattern.dayOfMonth) + if ($null -ne $getValue.scheduleInfo.recurrence.pattern.daysOfWeek) + { + $complexPattern.Add('DaysOfWeek', $getValue.scheduleInfo.recurrence.pattern.daysOfWeek.ToString()) + } + if ($null -ne $getValue.scheduleInfo.recurrence.pattern.firstDayOfWeek) + { + $complexPattern.Add('FirstDayOfWeek', $getValue.scheduleInfo.recurrence.pattern.firstDayOfWeek.ToString()) + } + if ($null -ne $getValue.scheduleInfo.recurrence.pattern.index) + { + $complexPattern.Add('Index', $getValue.scheduleInfo.recurrence.pattern.index.ToString()) + } + $complexPattern.Add('Interval', $getValue.scheduleInfo.recurrence.pattern.interval) + $complexPattern.Add('Month', $getValue.scheduleInfo.recurrence.pattern.month) + if ($null -ne $getValue.scheduleInfo.recurrence.pattern.type) + { + $complexPattern.Add('Type', $getValue.scheduleInfo.recurrence.pattern.type.ToString()) + } + if ($complexPattern.values.Where({$null -ne $_}).Count -eq 0) + { + $complexPattern = $null + } + $complexRecurrence.Add('Pattern',$complexPattern) + $complexRange = @{} + if ($null -ne $getValue.scheduleInfo.recurrence.range.endDate) + { + $complexRange.Add('EndDate', ([DateTime]$getValue.scheduleInfo.recurrence.range.endDate).ToString('')) + } + $complexRange.Add('NumberOfOccurrences', $getValue.scheduleInfo.recurrence.range.numberOfOccurrences) + $complexRange.Add('RecurrenceTimeZone', $getValue.scheduleInfo.recurrence.range.recurrenceTimeZone) + if ($null -ne $getValue.scheduleInfo.recurrence.range.startDate) + { + $complexRange.Add('StartDate', ([DateTime]$getValue.scheduleInfo.recurrence.range.startDate).ToString('')) + } + if ($null -ne $getValue.scheduleInfo.recurrence.range.type) + { + $complexRange.Add('Type', $getValue.scheduleInfo.recurrence.range.type.ToString()) + } + if ($complexRange.values.Where({$null -ne $_}).Count -eq 0) + { + $complexRange = $null + } + $complexRecurrence.Add('Range',$complexRange) + if ($complexRecurrence.values.Where({$null -ne $_}).Count -eq 0) + { + $complexRecurrence = $null + } + $complexScheduleInfo.Add('Recurrence',$complexRecurrence) + if ($null -ne $getValue.ScheduleInfo.startDateTime) + { + $complexScheduleInfo.Add('StartDateTime', ([DateTimeOffset]$getValue.ScheduleInfo.startDateTime).ToString('o')) + } + if ($complexScheduleInfo.values.Where({$null -ne $_}).Count -eq 0) + { + $complexScheduleInfo = $null + } + #endregion + + #region resource generator code + $enumAccessId = $null + if ($null -ne $getValue.accessId) + { + $enumAccessId = $getValue.accessId.ToString() + } + + $enumMemberType = $null + if ($null -ne $getValue.memberType) + { + $enumMemberType = $getValue.memberType.ToString() + } + #endregion + + switch ($getValue.PrincipalType) + { + 'group' { + $PrincipalDisplayName = (Get-MgGroup -GroupId $getvalue.PrincipalId).DisplayName + } + 'user' { + $PrincipalDisplayName = (Get-MgUser -UserId $getvalue.PrincipalId).DisplayName + } + } + + $GroupDisplayName = (Get-MgGroup -GroupId $getvalue.GroupId).DisplayName + + $results = @{ + #region resource generator code + AccessId = $enumAccessId + GroupId = $getValue.groupId + GroupDisplayName = $GroupDisplayName + MemberType = $enumMemberType + PrincipalType = $PrincipalType + PrincipalDisplayname = $PrincipalDisplayName + ScheduleInfo = $complexScheduleInfo + Id = $getValue.Id + Ensure = 'Present' + Credential = $Credential + ApplicationId = $ApplicationId + TenantId = $TenantId + ApplicationSecret = $ApplicationSecret + CertificateThumbprint = $CertificateThumbprint + ManagedIdentity = $ManagedIdentity.IsPresent + #endregion + } + + return [System.Collections.Hashtable] $results + } + catch + { + New-M365DSCLogEntry -Message 'Error retrieving data:' ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantId ` + -Credential $Credential + + return $nullResult + } +} + +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + #region resource generator code + [Parameter()] + [ValidateSet('owner','member','unknownFutureValue')] + [System.String] + $AccessId, + + [Parameter()] + [System.String] + $GroupId, + + [Parameter(Mandatory = $true)] + [System.String] + $GroupDisplayName, + + [Parameter()] + [ValidateSet('direct','group','unknownFutureValue')] + [System.String] + $MemberType, + + [Parameter()] + [System.String] + $PrincipalId, + + [Parameter()] + [System.String] + $PrincipalType, + + [Parameter()] + [System.String] + $PrincipalDisplayName, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $ScheduleInfo, + + [Parameter()] + [System.String] + $Id, + + #endregion + [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, + + [Parameter()] + [System.String[]] + $AccessTokens + ) + + Write-Verbose -Message "Setting configuration of the Azure AD Group Eligibility Schedule for group {$GroupId} and DisplayName {$GroupDisplayName}" + + #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 + + $currentInstance = Get-TargetResource @PSBoundParameters + + $BoundParameters = Remove-M365DSCAuthenticationParameter -BoundParameters $PSBoundParameters + + + if ($Ensure -eq 'Present' -and $currentInstance.Ensure -eq 'Absent') + { + Write-Verbose -Message "Creating an Azure AD Group Eligibility Schedule for Group {$GroupDisplayName}" + + $createParameters = ([Hashtable]$BoundParameters).Clone() + $createParameters = Rename-M365DSCCimInstanceParameter -Properties $createParameters + $createParameters.Remove('Id') | Out-Null + $createParameters.Remove('PrincipalType') | Out-Null + $createParameters.Remove('PrincipalDisplayName') | Out-Null + $createParameters.Remove('GroupDisplayName') | Out-Null + $createParameters.Add('Action', 'adminAssign') + + $GroupFilter = "DisplayName eq '" + $GroupDisplayName + "'" + $GroupId = (Get-MgGroup -Filter $GroupFilter).Id + + if($ScheduleInfo.Expiration.Type -eq 'noExpiration'){ + $p = Get-MgBetaPolicyRoleManagementPolicyAssignment -Filter $("scopeId eq '{0}' and scopeType eq 'Group' and RoleDefinitionId eq 'member'" -f $GroupId) + $unifiedRoleManagementPolicyId = $p.PolicyId + $unifiedRoleManagementPolicyRuleId = "Expiration_Admin_Eligibility" + $isExpirationRequired = (Get-MgBetaPolicyRoleManagementPolicyRule -UnifiedRoleManagementPolicyId $unifiedRoleManagementPolicyId -UnifiedRoleManagementPolicyRuleId $unifiedRoleManagementPolicyRuleId).AdditionalProperties.isExpirationRequired + if($isExpirationRequired){ + $params = @{ + "@odata.type" = "#microsoft.graph.unifiedRoleManagementPolicyExpirationRule" + id = "Expiration_Admin_Eligibility" + isExpirationRequired = $false + target = @{ + caller = "Admin" + operations = @( + "All" + ) + level = "Eligibility" + inheritableSettings = @( + ) + enforcedSettings = @( + ) + } + } + Update-MgBetaPolicyRoleManagementPolicyRule -UnifiedRoleManagementPolicyId $unifiedRoleManagementPolicyId -UnifiedRoleManagementPolicyRuleId $unifiedRoleManagementPolicyRuleId -BodyParameter $params + } + } + elseif($ScheduleInfo.Expiration.Type -match "^after"){ + $p = Get-MgBetaPolicyRoleManagementPolicyAssignment -Filter $("scopeId eq '{0}' and scopeType eq 'Group' and RoleDefinitionId eq 'member'" -f $GroupId) + $unifiedRoleManagementPolicyId = $p.PolicyId + $unifiedRoleManagementPolicyRuleId = "Expiration_Admin_Eligibility" + $isExpirationRequired = (Get-MgBetaPolicyRoleManagementPolicyRule -UnifiedRoleManagementPolicyId $unifiedRoleManagementPolicyId -UnifiedRoleManagementPolicyRuleId $unifiedRoleManagementPolicyRuleId).AdditionalProperties.isExpirationRequired + if(-not $isExpirationRequired){ + $params = @{ + "@odata.type" = "#microsoft.graph.unifiedRoleManagementPolicyExpirationRule" + id = "Expiration_Admin_Eligibility" + isExpirationRequired = $true + maximumDuration = 'P365D' + target = @{ + caller = "Admin" + operations = @( + "All" + ) + level = "Eligibility" + inheritableSettings = @( + ) + enforcedSettings = @( + ) + } + } + Update-MgBetaPolicyRoleManagementPolicyRule -UnifiedRoleManagementPolicyId $unifiedRoleManagementPolicyId -UnifiedRoleManagementPolicyRuleId $unifiedRoleManagementPolicyRuleId -BodyParameter $params + } + } + + $createParameters.Add('GroupId', $GroupId) + $Filter = "DisplayName eq '" + $PrincipalDisplayname + "'" + if($PrincipalType -eq 'group'){ + $PrincipalId = (Get-MgGroup -Filter $Filter).Id + } + else{ + $PrincipalId = (Get-MgUser -Filter $Filter).Id + } + $createParameters.Add('PrincipalId', $PrincipalId) + + $keys = (([Hashtable]$createParameters).Clone()).Keys + foreach ($key in $keys) + { + if ($null -ne $createParameters.$key -and $createParameters.$key.GetType().Name -like '*CimInstance*') + { + $createParameters.$key = Convert-M365DSCDRGComplexTypeToHashtable -ComplexObject $createParameters.$key + } + } + #region resource generator code + New-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest -BodyParameter $createParameters + #endregion + } + elseif ($Ensure -eq 'Present' -and $currentInstance.Ensure -eq 'Present') + { + Write-Verbose -Message "Updating the Azure AD Group Eligibility Schedule with Id {$($currentInstance.Id)}" + + $scheduledStart = $currentInstance.ScheduleInfo.StartDateTime + $scheduledEnd = $currentInstance.ScheduleInfo.Expiration.EndDateTime + if($scheduledStart -ne $ScheduleInfo.StartDateTime -or $scheduledEnd -ne $ScheduleInfo.Expiration.EndDateTime){ + $Action = 'adminExtend' + } + else{ + $Action = 'adminUpdate' + } + $updateParameters = ([Hashtable]$BoundParameters).Clone() + $updateParameters = Rename-M365DSCCimInstanceParameter -Properties $updateParameters + + $updateParameters.Remove('Id') | Out-Null + $updateParameters.Remove('PrincipalType') | Out-Null + $updateParameters.Remove('PrincipalDisplayName') | Out-Null + $updateParameters.Remove('GroupDisplayName') | Out-Null + $updateParameters.Add('Action', $Action) + + $GroupFilter = "DisplayName eq '" + $GroupDisplayName + "'" + $GroupId = (Get-MgGroup -Filter $GroupFilter).Id + if($ScheduleInfo.Expiration.Type -eq 'noExpiration'){ + $p = Get-MgBetaPolicyRoleManagementPolicyAssignment -Filter $("scopeId eq '{0}' and scopeType eq 'Group' and RoleDefinitionId eq 'member'" -f $GroupId) + $unifiedRoleManagementPolicyId = $p.PolicyId + $unifiedRoleManagementPolicyRuleId = "Expiration_Admin_Eligibility" + $isExpirationRequired = (Get-MgBetaPolicyRoleManagementPolicyRule -UnifiedRoleManagementPolicyId $unifiedRoleManagementPolicyId -UnifiedRoleManagementPolicyRuleId $unifiedRoleManagementPolicyRuleId).AdditionalProperties.isExpirationRequired + if($isExpirationRequired){ + $params = @{ + "@odata.type" = "#microsoft.graph.unifiedRoleManagementPolicyExpirationRule" + id = "Expiration_Admin_Eligibility" + isExpirationRequired = $false + target = @{ + caller = "Admin" + operations = @( + "All" + ) + level = "Eligibility" + inheritableSettings = @( + ) + enforcedSettings = @( + ) + } + } + Write-Verbose -Message "Updating the expiration policy for the group {$GroupDisplayName}" + Update-MgBetaPolicyRoleManagementPolicyRule -UnifiedRoleManagementPolicyId $unifiedRoleManagementPolicyId -UnifiedRoleManagementPolicyRuleId $unifiedRoleManagementPolicyRuleId -BodyParameter $params + } + } + elseif($ScheduleInfo.Expiration.Type -match "^after"){ + $p = Get-MgBetaPolicyRoleManagementPolicyAssignment -Filter $("scopeId eq '{0}' and scopeType eq 'Group' and RoleDefinitionId eq 'member'" -f $GroupId) + $unifiedRoleManagementPolicyId = $p.PolicyId + $unifiedRoleManagementPolicyRuleId = "Expiration_Admin_Eligibility" + $isExpirationRequired = (Get-MgBetaPolicyRoleManagementPolicyRule -UnifiedRoleManagementPolicyId $unifiedRoleManagementPolicyId -UnifiedRoleManagementPolicyRuleId $unifiedRoleManagementPolicyRuleId).AdditionalProperties.isExpirationRequired + if(-not $isExpirationRequired){ + $params = @{ + "@odata.type" = "#microsoft.graph.unifiedRoleManagementPolicyExpirationRule" + id = "Expiration_Admin_Eligibility" + isExpirationRequired = $true + maximumDuration = 'P365D' + target = @{ + caller = "Admin" + operations = @( + "All" + ) + level = "Eligibility" + inheritableSettings = @( + ) + enforcedSettings = @( + ) + } + } + Write-Verbose -Message "Updating the expiration policy for the group {$GroupDisplayName}" + Update-MgBetaPolicyRoleManagementPolicyRule -UnifiedRoleManagementPolicyId $unifiedRoleManagementPolicyId -UnifiedRoleManagementPolicyRuleId $unifiedRoleManagementPolicyRuleId -BodyParameter $params + } + } + $updateParameters.Add('GroupId', $GroupId) + $Filter = "DisplayName eq '" + $PrincipalDisplayname + "'" + if($PrincipalType -eq 'group'){ + $PrincipalId = (Get-MgGroup -Filter $Filter).Id + } + else{ + $PrincipalId = (Get-MgUser -Filter $Filter).Id + } + $updateParameters.Add('PrincipalId', $PrincipalId) + + $keys = (([Hashtable]$updateParameters).Clone()).Keys + foreach ($key in $keys) + { + if ($null -ne $pdateParameters.$key -and $updateParameters.$key.GetType().Name -like '*CimInstance*') + { + $updateParameters.$key = Convert-M365DSCDRGComplexTypeToHashtable -ComplexObject $updateParameters.PrivilegedAccessGroupEligibilityScheduleId + } + } + + #region resource generator code + New-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest -BodyParameter $UpdateParameters + #endregion + } + elseif ($Ensure -eq 'Absent' -and $currentInstance.Ensure -eq 'Present') + { + Write-Verbose -Message "Removiong the Azure AD Group Eligibility Schedule with Id {$($currentInstance.Id)}" + + $updateParameters = ([Hashtable]$BoundParameters).Clone() + $updateParameters = Rename-M365DSCCimInstanceParameter -Properties $updateParameters + + $updateParameters.Remove('Id') | Out-Null + $updateParameters.Remove('PrincipalType') | Out-Null + $updateParameters.Remove('PrincipalDisplayName') | Out-Null + $updateParameters.Remove('GroupDisplayName') | Out-Null + $updateParameters.Add('Action', 'adminRemove') + + $GroupFilter = "DisplayName eq '" + $GroupDisplayName + "'" + $GroupId = (Get-MgGroup -Filter $GroupFilter).Id + $updateParameters.Add('GroupId', $GroupId) + $Filter = "DisplayName eq '" + $PrincipalDisplayname + "'" + if($PrincipalType -eq 'group'){ + $PrincipalId = (Get-MgGroup -Filter $Filter).Id + } + else{ + $PrincipalId = (Get-MgUser -Filter $Filter).Id + } + $updateParameters.Add('PrincipalId', $PrincipalId) + + $keys = (([Hashtable]$updateParameters).Clone()).Keys + foreach ($key in $keys) + { + if ($null -ne $pdateParameters.$key -and $updateParameters.$key.GetType().Name -like '*CimInstance*') + { + $updateParameters.$key = Convert-M365DSCDRGComplexTypeToHashtable -ComplexObject $updateParameters.PrivilegedAccessGroupEligibilityScheduleId + } + } + + #region resource generator code + New-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest -BodyParameter $UpdateParameters + #endregion + } +} + +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + #region resource generator code + [Parameter()] + [ValidateSet('owner','member','unknownFutureValue')] + [System.String] + $AccessId, + + [Parameter()] + [System.String] + $GroupId, + + [Parameter(Mandatory = $true)] + [System.String] + $GroupDisplayName, + + [Parameter()] + [ValidateSet('direct','group','unknownFutureValue')] + [System.String] + $MemberType, + + [Parameter()] + [System.String] + $PrincipalId, + + [Parameter()] + [System.String] + $PrincipalType, + + [Parameter()] + [System.String] + $PrincipalDisplayName, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $ScheduleInfo, + + [Parameter()] + [System.String] + $Id, + + #endregion + + [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, + + [Parameter()] + [System.String[]] + $AccessTokens + ) + + #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 the Azure AD Group Eligibility Schedule for Group {$GroupId} and DisplayName {$GroupDisplayName}" + + $CurrentValues = Get-TargetResource @PSBoundParameters + $ValuesToCheck = ([Hashtable]$PSBoundParameters).clone() + + if ($CurrentValues.Ensure -ne $Ensure) + { + Write-Verbose -Message "Test-TargetResource returned $false" + return $false + } + $testResult = $true + + #Compare Cim instances + foreach ($key in $PSBoundParameters.Keys) + { + $source = $PSBoundParameters.$key + $target = $CurrentValues.$key + if ($null -ne $source -and $source.GetType().Name -like '*CimInstance*') + { + $testResult = Compare-M365DSCComplexObject ` + -Source ($source) ` + -Target ($target) + + if (-not $testResult) + { + break + } + + $ValuesToCheck.Remove($key) | Out-Null + } + } + + $ValuesToCheck.Remove('Id') | Out-Null + $ValuesToCheck = Remove-M365DSCAuthenticationParameter -BoundParameters $ValuesToCheck + + Write-Verbose -Message "Current Values: $(Convert-M365DscHashtableToString -Hashtable $CurrentValues)" + Write-Verbose -Message "Target Values: $(Convert-M365DscHashtableToString -Hashtable $ValuesToCheck)" + + if ($testResult) + { + $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.String] + $Filter, + + [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, + + [Parameter()] + [System.String[]] + $AccessTokens + ) + + $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 + { + + $groups = Get-MgGroup -Filter "MailEnabled eq false and NOT(groupTypes/any(x:x eq 'DynamicMembership'))" -Property "displayname,Id" -CountVariable CountVar -ConsistencyLevel eventual -ErrorAction Stop + foreach ($group in $groups) + { + Write-Host "get group $($group.DisplayName)" + #region resource generator code + $getValue = Get-MgIdentityGovernancePrivilegedAccessGroupEligibilitySchedule ` + -Filter "groupId eq '$($group.Id)'" ` + -All ` + -ErrorAction Stop + if($null -eq $getValue) + { + continue + } + + $i = 1 + $dscContent = '' + if ($getValue.Length -eq 0) + { + Write-Host $Global:M365DSCEmojiGreenCheckMark + } + else + { + Write-Host "`r`n" -NoNewline + } + foreach ($config in $getValue) + { + Write-Host " |---[$i/$($getValue.Count)] $($group.DisplayName)" -NoNewline + $params = @{ + Id = $config.Id + GroupDisplayName = $group.DisplayName + Ensure = 'Present' + Credential = $Credential + ApplicationId = $ApplicationId + TenantId = $TenantId + ApplicationSecret = $ApplicationSecret + CertificateThumbprint = $CertificateThumbprint + ManagedIdentity = $ManagedIdentity.IsPresent + AccessTokens = $AccessTokens + } + + $Results = Get-TargetResource @Params + $Results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode ` + -Results $Results + if ($null -ne $Results.ScheduleInfo) + { + $complexMapping = @( + @{ + Name = 'ScheduleInfo' + CimInstanceName = 'MicrosoftGraphRequestSchedule' + IsRequired = $True + } + @{ + Name = 'Expiration' + CimInstanceName = 'MicrosoftGraphExpirationPattern' + IsRequired = $False + } + @{ + Name = 'Recurrence' + CimInstanceName = 'MicrosoftGraphPatternedRecurrence1' + IsRequired = $False + } + @{ + Name = 'Pattern' + CimInstanceName = 'MicrosoftGraphRecurrencePattern1' + IsRequired = $False + } + @{ + Name = 'Range' + CimInstanceName = 'MicrosoftGraphRecurrenceRange1' + IsRequired = $False + } + ) + $complexTypeStringResult = Get-M365DSCDRGComplexTypeToString ` + -ComplexObject $Results.ScheduleInfo ` + -CIMInstanceName 'MicrosoftGraphrequestSchedule' ` + -ComplexTypeMapping $complexMapping + + if (-not [String]::IsNullOrWhiteSpace($complexTypeStringResult)) + { + $Results.ScheduleInfo = $complexTypeStringResult + } + else + { + $Results.Remove('ScheduleInfo') | Out-Null + } + } + + $currentDSCBlock = Get-M365DSCExportContentForResource -ResourceName $ResourceName ` + -ConnectionMode $ConnectionMode ` + -ModulePath $PSScriptRoot ` + -Results $Results ` + -Credential $Credential + if ($Results.ScheduleInfo) + { + $currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock -ParameterName "ScheduleInfo" -IsCIMArray:$False + } + + $dscContent += $currentDSCBlock + Save-M365DSCPartialExport -Content $currentDSCBlock ` + -FileName $Global:PartialExportFileName + $i++ + Write-Host $Global:M365DSCEmojiGreenCheckMark + } + return $dscContent + } + } + catch + { + 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_AADGroupEligibilitySchedule/MSFT_AADGroupEligibilitySchedule.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroupEligibilitySchedule/MSFT_AADGroupEligibilitySchedule.schema.mof new file mode 100644 index 0000000000..f2c14e3fc2 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroupEligibilitySchedule/MSFT_AADGroupEligibilitySchedule.schema.mof @@ -0,0 +1,62 @@ +[ClassVersion("1.0.0")] +class MSFT_MicrosoftGraphRequestSchedule +{ + [Write, Description("When the eligible or active assignment expires."), EmbeddedInstance("MSFT_MicrosoftGraphExpirationPattern")] String Expiration; + [Write, Description("The frequency of the eligible or active assignment. This property is currently unsupported in PIM."), EmbeddedInstance("MSFT_MicrosoftGraphPatternedRecurrence1")] String Recurrence; + [Write, Description("When the eligible or active assignment becomes active.")] String StartDateTime; +}; +[ClassVersion("1.0.0")] +class MSFT_MicrosoftGraphExpirationPattern +{ + [Write, Description("The requestor's desired duration of access represented in ISO 8601 format for durations. For example, PT3H refers to three hours. If specified in a request, endDateTime should not be present and the type property should be set to afterDuration.")] String Duration; + [Write, Description("Timestamp of date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is 2014-01-01T00:00:00Z.")] String EndDateTime; + [Write, Description("The requestor's desired expiration pattern type. The possible values are: notSpecified, noExpiration, afterDateTime, afterDuration."), ValueMap{"notSpecified","noExpiration","afterDateTime","afterDuration"}, Values{"notSpecified","noExpiration","afterDateTime","afterDuration"}] String Type; +}; +[ClassVersion("1.0.0")] +class MSFT_MicrosoftGraphPatternedRecurrence1 +{ + [Write, Description("The frequency of an event. For access reviews: Do not specify this property for a one-time access review. Only interval, dayOfMonth, and type (weekly, absoluteMonthly) properties of recurrencePattern are supported."), EmbeddedInstance("MSFT_MicrosoftGraphRecurrencePattern1")] String Pattern; + [Write, Description("The duration of an event."), EmbeddedInstance("MSFT_MicrosoftGraphRecurrenceRange1")] String Range; +}; +[ClassVersion("1.0.0")] +class MSFT_MicrosoftGraphRecurrencePattern1 +{ + [Write, Description("The day of the month on which the event occurs. Required if type is absoluteMonthly or absoluteYearly.")] UInt32 DayOfMonth; + [Write, Description("A collection of the days of the week on which the event occurs. The possible values are: sunday, monday, tuesday, wednesday, thursday, friday, saturday. If type is relativeMonthly or relativeYearly, and daysOfWeek specifies more than one day, the event falls on the first day that satisfies the pattern. Required if type is weekly, relativeMonthly, or relativeYearly."), ValueMap{"sunday","monday","tuesday","wednesday","thursday","friday","saturday"}, Values{"sunday","monday","tuesday","wednesday","thursday","friday","saturday"}] String DaysOfWeek[]; + [Write, Description("The first day of the week. The possible values are: sunday, monday, tuesday, wednesday, thursday, friday, saturday. Default is sunday. Required if type is weekly."), ValueMap{"sunday","monday","tuesday","wednesday","thursday","friday","saturday"}, Values{"sunday","monday","tuesday","wednesday","thursday","friday","saturday"}] String FirstDayOfWeek; + [Write, Description("Specifies on which instance of the allowed days specified in daysOfWeek the event occurs, counted from the first instance in the month. The possible values are: first, second, third, fourth, last. Default is first. Optional and used if type is relativeMonthly or relativeYearly."), ValueMap{"first","second","third","fourth","last"}, Values{"first","second","third","fourth","last"}] String Index; + [Write, Description("The number of units between occurrences, where units can be in days, weeks, months, or years, depending on the type. Required.")] UInt32 Interval; + [Write, Description("The month in which the event occurs. This is a number from 1 to 12.")] UInt32 Month; + [Write, Description("The recurrence pattern type: daily, weekly, absoluteMonthly, relativeMonthly, absoluteYearly, relativeYearly. Required. For more information, see values of type property."), ValueMap{"daily","weekly","absoluteMonthly","relativeMonthly","absoluteYearly","relativeYearly"}, Values{"daily","weekly","absoluteMonthly","relativeMonthly","absoluteYearly","relativeYearly"}] String Type; +}; +[ClassVersion("1.0.0")] +class MSFT_MicrosoftGraphRecurrenceRange1 +{ + [Write, Description("The date to stop applying the recurrence pattern. Depending on the recurrence pattern of the event, the last occurrence of the meeting may not be this date. Required if type is endDate.")] String EndDate; + [Write, Description("The number of times to repeat the event. Required and must be positive if type is numbered.")] UInt32 NumberOfOccurrences; + [Write, Description("Time zone for the startDate and endDate properties. Optional. If not specified, the time zone of the event is used.")] String RecurrenceTimeZone; + [Write, Description("The date to start applying the recurrence pattern. The first occurrence of the meeting may be this date or later, depending on the recurrence pattern of the event. Must be the same value as the start property of the recurring event. Required.")] String StartDate; + [Write, Description("The recurrence range. The possible values are: endDate, noEnd, numbered. Required."), ValueMap{"endDate","noEnd","numbered"}, Values{"endDate","noEnd","numbered"}] String Type; +}; + +[ClassVersion("1.0.0.0"), FriendlyName("AADGroupEligibilitySchedule")] +class MSFT_AADGroupEligibilitySchedule : OMI_BaseResource +{ + [Write, Description("The identifier of the membership or ownership eligibility to the group that is governed by PIM. Required. The possible values are: owner, member. Supports $filter (eq)."), ValueMap{"owner","member","unknownFutureValue"}, Values{"owner","member","unknownFutureValue"}] String AccessId; + [Write, Description("The identifier of the group representing the scope of the membership or ownership eligibility through PIM for groups. Required. Supports $filter (eq).")] String GroupId; + [Key, Description("Displayname of the group representing the scope of the membership or ownership eligibility through PIM for groups.")] String GroupDisplayName; + [Write, Description("Indicates whether the assignment is derived from a group assignment. It can further imply whether the caller can manage the schedule. Required. The possible values are: direct, group, unknownFutureValue. Supports $filter (eq)."), ValueMap{"direct","group","unknownFutureValue"}, Values{"direct","group","unknownFutureValue"}] String MemberType; + [Write, Description("The identifier of the principal whose membership or ownership eligibility is granted through PIM for groups. Required. Supports $filter (eq).")] String PrincipalId; + [Write, Description("Principal type user or group"), ValueMap{"user","group"}, Values{"user","group"}] String PrincipalType; + [Write, Description("Displayname of the Principal")] String PrincipalDisplayName; + [Write, Description("Represents the period of the access assignment or eligibility. The scheduleInfo can represent a single occurrence or multiple recurring instances. Required."), EmbeddedInstance("MSFT_MicrosoftGraphrequestSchedule")] String ScheduleInfo; + [Write, Description("The unique identifier for an entity. Read-only.")] String Id; + [Write, Description("Present ensures the policy exists, absent ensures it is removed."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] string Ensure; + [Write, Description("Credentials of the 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; + [Write, Description("Access token used for authentication.")] String AccessTokens[]; +}; diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroupEligibilitySchedule/readme.md b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroupEligibilitySchedule/readme.md new file mode 100644 index 0000000000..be5cf0dab6 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroupEligibilitySchedule/readme.md @@ -0,0 +1,6 @@ + +# AADGroupEligibilitySchedule + +## Description + +Azure AD Group Eligibility Schedule diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroupEligibilitySchedule/settings.json b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroupEligibilitySchedule/settings.json new file mode 100644 index 0000000000..0cd7ea504f --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADGroupEligibilitySchedule/settings.json @@ -0,0 +1,29 @@ +{ + "resourceName": "AADGroupEligibilitySchedule", + "description": "This resource configures an Azure AD Group Eligibility Schedule.", + "permissions": { + "graph": { + "delegated": { + "read": [ + { + "name": "PrivilegedEligibilitySchedule.Read.AzureADGroup" + } + ], + "update": [ + + ] + }, + "application": { + "read": [ + { + "name": "PrivilegedEligibilitySchedule.Read.AzureADGroup" + } + ], + "update": [ + + ] + } + } +} + +} diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADGroupEligibilitySchedule/1-Create.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADGroupEligibilitySchedule/1-Create.ps1 new file mode 100644 index 0000000000..885b7dc471 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/AADGroupEligibilitySchedule/1-Create.ps1 @@ -0,0 +1,34 @@ +<# +This example is used to test new resources and showcase the usage of new resources being worked on. +It is not meant to use as a production baseline. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $Credscredential + ) + Import-DscResource -ModuleName Microsoft365DSC + + node localhost + { + AADGroupEligibilitySchedule 'Example' + { + AccessId = "member"; + Ensure = "Present"; + GroupDisplayName = "MyPIMGroup"; + MemberType = "direct"; + PrincipalDisplayname = "MyPrincipalGroup"; + PrincipalType = "group"; + ScheduleInfo = MSFT_MicrosoftGraphrequestSchedule{ + StartDateTime = '2024-12-23T08:59:28.1200000+00:00' + Expiration = MSFT_MicrosoftGraphExpirationPattern{ + EndDateTime = '12/23/2025 8:59:00 AM +00:00' + Type = 'afterDateTime' + } + }; + } + } +} diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADGroupEligibilitySchedule/2-Update.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADGroupEligibilitySchedule/2-Update.ps1 new file mode 100644 index 0000000000..d33e380248 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/AADGroupEligibilitySchedule/2-Update.ps1 @@ -0,0 +1,32 @@ +<# +This example is used to test new resources and showcase the usage of new resources being worked on. +It is not meant to use as a production baseline. +#> + +Configuration Example +{ + param( + [Parameter(Mandatory = $true)] + [PSCredential] + $Credscredential + ) + Import-DscResource -ModuleName Microsoft365DSC + + node localhost + { + AADGroupEligibilitySchedule 'Example' + { + AccessId = "member"; + Ensure = "Present"; + GroupDisplayName = "MyPIMGroup"; + MemberType = "direct"; + PrincipalDisplayname = "MyPrincipalGroup"; + PrincipalType = "group"; + ScheduleInfo = MSFT_MicrosoftGraphrequestSchedule{ + Expiration = MSFT_MicrosoftGraphExpirationPattern{ + Type = 'noExpiration' + } + }; + } + } +} diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADGroupEligibilitySchedule.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADGroupEligibilitySchedule.Tests.ps1 new file mode 100644 index 0000000000..d021389b2d --- /dev/null +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADGroupEligibilitySchedule.Tests.ps1 @@ -0,0 +1,354 @@ +[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 "AADGroupEligibilitySchedule" -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 (New-Guid | Out-String) -AsPlainText -Force + $Credential = New-Object System.Management.Automation.PSCredential ('tenantadmin@mydomain.com', $secpasswd) + + Mock -CommandName Confirm-M365DSCDependencies -MockWith { + } + + Mock -CommandName Get-MSCloudLoginConnectionProfile -MockWith { + } + + Mock -CommandName Reset-MSCloudLoginConnectionProfileContext -MockWith { + } + + Mock -CommandName Get-PSSession -MockWith { + } + + Mock -CommandName Remove-PSSession -MockWith { + } + + Mock -CommandName New-M365DSCConnection -MockWith { + return "Credentials" + } + + Mock -CommandName Get-MgPolicyRoleManagementPolicyAssignment -MockWith { + return @( + @{ + PolicyIdId = 'FakeId' + } + ) + } + + Mock -CommandName Get-MgPolicyRoleManagementPolicyRule -MockWith { + return @() + } + + Mock -CommandName Update-MgPolicyRoleManagementPolicyRule -MockWith { + return @() + } + # Mock Write-Host to hide output during the tests + Mock -CommandName Write-Host -MockWith { + } + $Script:exportedInstances =$null + $Script:ExportMode = $false + } + + # Test contexts + Context -Name "The AADGroupEligibilitySchedule should exist but it DOES NOT" -Fixture { + BeforeAll { + $testParams = @{ + AccessId = "member" + GroupDisplayName = "FakeStringValue" + MemberType = "direct" + PrincipalDisplayName = "FakePrincipal" + ScheduleInfo = (New-CimInstance -ClassName MSFT_MicrosoftGraphRequestSchedule -Property @{ + startDateTime = '2025-01-23T08:59:00.0000000+00:00' + Expiration = (New-CimInstance -ClassName MSFT_MicrosoftGraphExpirationPattern -Property @{ + EndDateTime = '23/12/2025 08:59:00 +00:00' + Type = 'afterDateTime'} -ClientOnly) + } -ClientOnly) + Ensure = "Present" + Credential = $Credential; + } + + Mock -CommandName Get-MgIdentityGovernancePrivilegedAccessGroupEligibilitySchedule -MockWith { + return $null + } + + Mock -CommandName New-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest -MockWith { + return $null + } + } + It 'Should return Values from the Get method' { + (Get-TargetResource @testParams).Ensure | Should -Be 'Absent' + } + It 'Should return false from the Test method' { + Test-TargetResource @testParams | Should -Be $false + } + It 'Should Create the group from the Set method' { + Set-TargetResource @testParams + Should -Invoke -CommandName New-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest -Exactly 1 + } + } + + Context -Name "The AADGroupEligibilitySchedule exists but it SHOULD NOT" -Fixture { + BeforeAll { + $testParams = @{ + AccessId = "member" + GroupDisplayName = "FakeStringValue" + MemberType = "direct" + PrincipalDisplayName = "FakePrincipal" + ScheduleInfo = (New-CimInstance -ClassName MSFT_MicrosoftGraphRequestSchedule -Property @{ + startDateTime = '2025-01-23T08:59:00.0000000+00:00' + Expiration = (New-CimInstance -ClassName MSFT_MicrosoftGraphExpirationPattern -Property @{ + EndDateTime = '23/12/2025 08:59:00 +00:00' + Type = 'afterDateTime'} -ClientOnly) + } -ClientOnly) + Ensure = "Absent" + Credential = $Credential; + } + + Mock -CommandName Get-MgIdentityGovernancePrivilegedAccessGroupEligibilitySchedule -MockWith { + return $null + } + + mock -CommandName Get-MgGroup -MockWith { + return @{ + Id = 'FakeId' + DisplayName = 'FakeStringValue' + } + } + + Mock -CommandName New-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest -MockWith { + return $null + } + + Mock -CommandName Invoke-GraphRequest -MockWith { + return @{ + AccessId = 'member' + GroupDisplayName = 'FakeStringValue' + MemberType = 'direct' + PrincipalDisplayName = 'FakePrincipal' + ScheduleInfo = @{ + StartDateTime = '2025-01-23T08:59:00.000Z' + Expiration = @{ + EndDateTime = '2025-12-23T08:59:00.000Z' + type = 'afterDateTime' + } + } + } + } + + } + + It 'Should return Values from the Get method' { + (Get-TargetResource @testParams).Ensure | Should -Be 'Present' + } + + It 'Should return true from the Test method' { + Test-TargetResource @testParams | Should -Be $false + } + + It 'Should Remove the group from the Set method' { + Set-TargetResource @testParams + Should -Invoke -CommandName New-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest -Exactly 1 + } + } + + Context -Name "The AADGroupEligibilitySchedule Exists and Values are already in the desired state" -Fixture { + BeforeAll { + $testParams = @{ + AccessId = "member" + GroupDisplayName = "FakeStringValue" + MemberType = "direct" + PrincipalDisplayName = "FakePrincipal" + ScheduleInfo = (New-CimInstance -ClassName MSFT_MicrosoftGraphRequestSchedule -Property @{ + Expiration = (New-CimInstance -ClassName MSFT_MicrosoftGraphExpirationPattern -Property @{ + Type = 'noExpiration'} -ClientOnly) + } -ClientOnly) + Ensure = "Present" + Credential = $Credential; + } + + Mock -CommandName Get-MgIdentityGovernancePrivilegedAccessGroupEligibilitySchedule -MockWith { + return $null + } + + mock -CommandName Get-MgGroup -MockWith { + return @{ + Id = 'FakeId' + DisplayName = 'FakeStringValue' + } + } + + Mock -CommandName Invoke-GraphRequest -MockWith { + return @{ + AccessId = 'member' + GroupDisplayName = 'FakeStringValue' + MemberType = 'direct' + PrincipalDisplayName = 'FakePrincipal' + ScheduleInfo = @{ + Expiration = @{ + type = 'noExpiration' + } + } + } + } + + Mock -CommandName New-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest -MockWith { + return $null + } + + Mock -CommandName Get-MgPolicyRoleManagementPolicyAssignment -MockWith { + return @( + @{ + PolicyId = 'FakeId' + } + ) + } + + Mock -CommandName Get-MgPolicyRoleManagementPolicyRule -MockWith { + return @{ + AdditionalProperties = @{ + isExpirationRequired = $true + } + } + } + + Mock -CommandName Update-MgPolicyRoleManagementPolicyRule -MockWith { + return @() + } + + } + + It 'Should return true from the Test method' { + Test-TargetResource @testParams | Should -Be $true + } + } + + Context -Name "The AADGroupEligibilitySchedule exists and values are NOT in the desired state" -Fixture { + BeforeAll { + $testParams = @{ + AccessId = "member" + GroupDisplayName = "FakeStringValue" + MemberType = "direct" + PrincipalDisplayName = "FakePrincipal" + ScheduleInfo = (New-CimInstance -ClassName MSFT_MicrosoftGraphRequestSchedule -Property @{ + startDateTime = '2025-01-23T08:59:00.0000000+00:00' + Expiration = (New-CimInstance -ClassName MSFT_MicrosoftGraphExpirationPattern -Property @{ + EndDateTime = '23/12/2025 08:59:00 +00:00' + Type = 'afterDateTime'} -ClientOnly) + } -ClientOnly) + Ensure = "Present" + Credential = $Credential; + } + + Mock -CommandName Get-MgIdentityGovernancePrivilegedAccessGroupEligibilitySchedule -MockWith { + return $null + } + + mock -CommandName Get-MgGroup -MockWith { + return @{ + Id = 'FakeId' + DisplayName = 'FakeStringValue' + } + } + + Mock -CommandName Invoke-GraphRequest -MockWith { + return @{ + AccessId = 'member' + GroupDisplayName = 'FakeStringValue' + MemberType = 'direct' + PrincipalDisplayName = 'FakePrincipal' + ScheduleInfo = @{ + StartDateTime = '2025-01-23T08:59:00.000Z' + Expiration = @{ + EndDateTime = '2025-12-22T08:59:00.000Z' + type = 'afterDateTime' + } + } + } + } + + Mock -CommandName New-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest -MockWith { + return $null + } + + Mock -CommandName Get-MgPolicyRoleManagementPolicyAssignment -MockWith { + return @( + @{ + PolicyId = 'FakeId' + } + ) + } + + Mock -CommandName Get-MgPolicyRoleManagementPolicyRule -MockWith { + return @{ + AdditionalProperties = @{ + isExpirationRequired = $true + } + } + } + + Mock -CommandName Update-MgPolicyRoleManagementPolicyRule -MockWith { + return @() + } + } + + It 'Should return Values 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 call the Set method' { + Set-TargetResource @testParams + Should -Invoke -CommandName New-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest -Exactly 1 + } + } + + Context -Name 'ReverseDSC Tests' -Fixture { + BeforeAll { + $Global:CurrentModeIsExport = $true + $Global:PartialExportFileName = "$(New-Guid).partial.ps1" + $testParams = @{ + Credential = $Credential + } + + mock -CommandName Get-MgGroup -MockWith { + return @{ + Id = 'FakeId' + DisplayName = 'FakeStringValue' + } + } + + Mock -CommandName Get-MgIdentityGovernancePrivilegedAccessGroupEligibilitySchedule -MockWith { + return @{ + Id = 'FakeStringValue' + } + } + } + + 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/Stubs/Microsoft365.psm1 b/Tests/Unit/Stubs/Microsoft365.psm1 index 4f8feade39..fae2f3e556 100644 --- a/Tests/Unit/Stubs/Microsoft365.psm1 +++ b/Tests/Unit/Stubs/Microsoft365.psm1 @@ -105439,3 +105439,409 @@ function Update-MgDeviceManagementDeviceConfigurationAssignment #endregion +#region MgIdentityGovernancePrivilegedAccessGroupEligibilitySchedule +function Get-MgIdentityGovernancePrivilegedAccessGroupEligibilitySchedule +{ + [CmdletBinding()] + param + ( + [Parameter()] + [System.String] + $PrivilegedAccessGroupEligibilityScheduleId, + + [Parameter()] + [PSObject] + $InputObject, + + [Parameter()] + [System.String[]] + $ExpandProperty, + + [Parameter()] + [System.String[]] + $Property, + + [Parameter()] + [System.String] + $Filter, + + [Parameter()] + [System.String] + $Search, + + [Parameter()] + [System.Int32] + $Skip, + + [Parameter()] + [System.String[]] + $Sort, + + [Parameter()] + [System.Int32] + $Top, + + [Parameter()] + [System.String] + $ResponseHeadersVariable, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Break, + + [Parameter()] + [System.Collections.IDictionary] + $Headers, + + [Parameter()] + [PSObject[]] + $HttpPipelineAppend, + + [Parameter()] + [PSObject[]] + $HttpPipelinePrepend, + + [Parameter()] + [System.Uri] + $Proxy, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ProxyCredential, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ProxyUseDefaultCredentials, + + [Parameter()] + [System.Int32] + $PageSize, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $All, + + [Parameter()] + [System.String] + $CountVariable + ) +} + +function New-MgBetaIdentityGovernancePrivilegedAccessGroupEligibilityScheduleRequest +{ + [CmdletBinding()] + param + ( + [Parameter()] + [PSObject] + $BodyParameter, + + [Parameter()] + [System.String] + $ResponseHeadersVariable, + + [Parameter()] + [System.String] + $AccessId, + + [Parameter()] + [System.String] + $Action, + + [Parameter()] + [System.Collections.Hashtable] + $AdditionalProperties, + + [Parameter()] + [System.String] + $ApprovalId, + + [Parameter()] + [System.DateTime] + $CompletedDateTime, + + [Parameter()] + [PSObject] + $CreatedBy, + + [Parameter()] + [System.DateTime] + $CreatedDateTime, + + [Parameter()] + [System.String] + $CustomData, + + [Parameter()] + [PSObject] + $Group, + + [Parameter()] + [System.String] + $GroupId, + + [Parameter()] + [System.String] + $Id, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $IsValidationOnly, + + [Parameter()] + [System.String] + $Justification, + + [Parameter()] + [PSObject] + $Principal, + + [Parameter()] + [System.String] + $PrincipalId, + + [Parameter()] + [PSObject] + $ScheduleInfo, + + [Parameter()] + [System.String] + $Status, + + [Parameter()] + [PSObject] + $TargetSchedule, + + [Parameter()] + [System.String] + $TargetScheduleId, + + [Parameter()] + [PSObject] + $TicketInfo, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Break, + + [Parameter()] + [System.Collections.IDictionary] + $Headers, + + [Parameter()] + [PSObject[]] + $HttpPipelineAppend, + + [Parameter()] + [PSObject[]] + $HttpPipelinePrepend, + + [Parameter()] + [System.Uri] + $Proxy, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ProxyCredential, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ProxyUseDefaultCredentials, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Confirm + ) +} + +function Get-MgPolicyRoleManagementPolicyAssignment +{ + [CmdletBinding()] + param + ( + [Parameter()] + [System.String] + $PolicyAssignmentId, + + [Parameter()] + [System.String] + $PolicyId, + + [Parameter()] + [PSObject] + $InputObject, + + [Parameter()] + [System.String[]] + $ExpandProperty, + + [Parameter()] + [System.String[]] + $Property, + + [Parameter()] + [System.String] + $Filter, + + [Parameter()] + [System.String] + $Search, + + [Parameter()] + [System.Int32] + $Skip, + + [Parameter()] + [System.String[]] + $Sort, + + [Parameter()] + [System.Int32] + $Top, + + [Parameter()] + [System.String] + $ResponseHeadersVariable, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Break, + + [Parameter()] + [System.Collections.IDictionary] + $Headers, + + [Parameter()] + [PSObject[]] + $HttpPipelineAppend, + + [Parameter()] + [PSObject[]] + $HttpPipelinePrepend, + + [Parameter()] + [System.Uri] + $Proxy, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ProxyCredential, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ProxyUseDefaultCredentials, + + [Parameter()] + [System.Int32] + $PageSize, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $All, + + [Parameter()] + [System.String] + $CountVariable + ) +} + +function Update-MgPolicyRoleManagementPolicyRule +{ + [CmdletBinding()] + param + ( + [Parameter()] + [System.String] + $PolicyRuleId, + + [Parameter()] + [PSObject] + $InputObject, + + [Parameter()] + [PSObject] + $BodyParameter, + + [Parameter()] + [System.String] + $ResponseHeadersVariable, + + [Parameter()] + [System.Collections.Hashtable] + $AdditionalProperties, + + [Parameter()] + [System.String] + $Id, + + [Parameter()] + [System.String] + $Name, + + [Parameter()] + [System.String] + $PolicyId, + + [Parameter()] + [System.String] + $PolicyRuleType, + + [Parameter()] + [System.String] + $PolicyType, + + [Parameter()] + [System.String] + $Priority, + + [Parameter()] + [System.String] + $RuleType, + + [Parameter()] + [System.String] + $Status, + + [Parameter()] + [System.String] + $TargetType, + + [Parameter()] + [System.String] + $TargetValue, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Break, + + [Parameter()] + [System.Collections.IDictionary] + $Headers, + + [Parameter()] + [PSObject[]] + $HttpPipelineAppend, + + [Parameter()] + [PSObject[]] + $HttpPipelinePrepend, + + [Parameter()] + [System.Uri] + $Proxy, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ProxyCredential, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ProxyUseDefaultCredentials, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Confirm + ) +} +#endregion +