From 2e6d7d5cc8fb1f7e95376b3278df444f9fe6b140 Mon Sep 17 00:00:00 2001 From: Nik Charlebois Date: Mon, 18 Nov 2024 15:55:33 -0500 Subject: [PATCH 1/5] AADRoleAssignmentScheduleRequest - Initial Request --- .../MSFT_AADApplication.psm1 | 8 +- ...MSFT_AADRoleAssignmentScheduleRequest.psm1 | 1078 +++++++++++++++++ ...ADRoleAssignmentScheduleRequest.schema.mof | 75 ++ .../readme.md | 6 + .../settings.json | 28 + .../settings.json | 2 +- .../1-Create.ps1 | 26 + .../2-Update.ps1 | 26 + .../3-Remove.ps1 | 26 + ...AADRoleAssignmentScheduleRequest.Tests.ps1 | 178 +++ 10 files changed, 1451 insertions(+), 2 deletions(-) create mode 100644 Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.psm1 create mode 100644 Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.schema.mof create mode 100644 Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/readme.md create mode 100644 Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/settings.json create mode 100644 Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/1-Create.ps1 create mode 100644 Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/2-Update.ps1 create mode 100644 Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/3-Remove.ps1 create mode 100644 Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleAssignmentScheduleRequest.Tests.ps1 diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 index 75b7e9a62a..69b7f58fa4 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 @@ -457,13 +457,19 @@ function Get-TargetResource } #endregion + $IdentifierUrisValue = @() + if ($null -ne $AADApp.IdentifierUris) + { + $IdentifierUrisValue = $AADApp.IdentifierUris + } + $result = @{ DisplayName = $AADApp.DisplayName AvailableToOtherTenants = $AvailableToOtherTenantsValue Description = $AADApp.Description GroupMembershipClaims = $AADApp.GroupMembershipClaims Homepage = $AADApp.web.HomepageUrl - IdentifierUris = $AADApp.IdentifierUris + IdentifierUris = $IdentifierUrisValue IsFallbackPublicClient = $IsFallbackPublicClientValue KnownClientApplications = $AADApp.Api.KnownClientApplications LogoutURL = $AADApp.web.LogoutURL diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.psm1 new file mode 100644 index 0000000000..bea1cf6526 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.psm1 @@ -0,0 +1,1078 @@ +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Principal, + + [Parameter(Mandatory = $true)] + [System.String] + $RoleDefinition, + + [Parameter()] + [ValidateSet('User', 'Group')] + [System.String] + $PrincipalType = 'User', + + [Parameter()] + [System.String] + $Id, + + [Parameter()] + [System.String] + $DirectoryScopeId, + + [Parameter()] + [System.String] + $AppScopeId, + + [Parameter()] + [ValidateSet("adminAssign", "adminUpdate", "adminRemove", "selfActivate", "selfDeactivate", "adminExtend", "adminRenew", "selfExtend", "selfRenew", "unknownFutureValue")] + [System.String] + $Action, + + [Parameter()] + [System.String] + $Justification, + + [Parameter()] + [System.Boolean] + $IsValidationOnly, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $ScheduleInfo, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $TicketInfo, + + [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 + ) + try + { + $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters + } + catch + { + Write-Verbose -Message ($_) + } + + #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 + { + $request = $null + if (-not [System.String]::IsNullOrEmpty($Id)) + { + if ($null -ne $Script:exportedInstances -and $Script:ExportMode) + { + $request = $Script:exportedInstances | Where-Object -FilterScript {$_.Id -eq $Id} + } + else + { + Write-Verbose -Message "Getting Role Eligibility by Id {$Id}" + $request = Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest -UnifiedRoleAssignmentScheduleRequestId $Id ` + -ErrorAction SilentlyContinue + } + } + + if ($null -eq $request) + { + if ($null -ne $Script:exportedInstances -and $Script:ExportMode) + { + Write-Verbose -Message "Getting Role Eligibility by PrincipalId and RoleDefinitionId" + $PrincipalTypeValue = $null + if ($PrincipalType -eq 'User') + { + Write-Verbose -Message "Retrieving Principal by UserPrincipalName {$Principal}" + $PrincipalIdValue = Get-MgUser -Filter "UserPrincipalName eq '$Principal'" -ErrorAction SilentlyContinue + $PrincipalTypeValue = 'User' + } + if ($null -eq $PrincipalIdValue -or $PrincipalType -eq 'Group') + { + Write-Verbose -Message "Retrieving Principal by DisplayName {$Principal}" + $PrincipalIdValue = Get-MgGroup -Filter "DisplayName eq '$Principal'" -ErrorAction SilentlyContinue + $PrincipalTypeValue = 'Group' + } + + if ($null -ne $PrincipalIdValue) + { + $PrincipalId = $PrincipalIdValue.Id + } + else + { + return $nullResult + } + Write-Verbose -Message "Found Principal {$PrincipalId}" + $RoleDefinitionId = (Get-MgBetaRoleManagementDirectoryRoleDefinition -Filter "DisplayName eq '$RoleDefinition'").Id + $request = $Script:exportedInstances | Where-Object -FilterScript {$_.PrincipalId -eq $PrincipalId -and $_.RoleDefinitionId -eq $RoleDefinition} | Sort-Object -Property CompletedDateTime -Descending + } + else + { + Write-Verbose -Message "Getting Role Eligibility by PrincipalId and RoleDefinitionId" + if ($PrincipalType -eq 'User') + { + Write-Verbose -Message "Retrieving principal {$Principal} of type {$PrincipalType}" + $PrincipalIdValue = Get-MgUser -Filter "UserPrincipalName eq '$Principal'" -ErrorAction SilentlyContinue + $PrincipalTypeValue = 'User' + } + + if ($null -eq $PrincipalIdValue -or $PrincipalType -eq 'Group') + { + Write-Verbose -Message "Retrieving principal {$Principal} of type {$PrincipalType}" + $PrincipalIdValue = Get-MgGroup -Filter "DisplayName eq '$Principal'" -ErrorAction SilentlyContinue + $PrincipalTypeValue = 'Group' + } + + if ($null -ne $PrincipalIdValue) + { + $PrincipalId = $PrincipalIdValue.Id + } + else + { + return $nullResult + } + Write-Verbose -Message "Found Principal {$PrincipalId}" + $schedulesForPrincipal = Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -Filter "PrincipalId eq '$PrincipalId'" + foreach ($instance in $schedulesForPrincipal) + { + $roleInfo = Get-MgBetaRoleManagementDirectoryRoleDefinition -UnifiedRoleDefinitionId $instance.RoleDefinitionId + if ($roleInfo.DisplayName -eq $RoleDefinition) + { + $schedule = $instance + } + } + [Array]$request = Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest -Filter "PrincipalId eq '$PrincipalId'" | Where-Object -FilterScript {$_.RoleDefinitionId -eq $schedule.RoleDefinitionId} | Sort-Object -Property CompletedDateTime -Descending +` + if ($request.Length -gt 1) + { + $request = $request[0] + } + } + } + else + { + Write-Verbose -Message "Request is not null: $request" + $ObjectGuid = [System.Guid]::empty + if ($PrincipalType -eq 'User') + { + Write-Verbose -Message "Retrieving principal {$Principal} of type {$PrincipalType}" + + if ([System.Guid]::TryParse($Principal,[System.Management.Automation.PSReference]$ObjectGuid)) + { + $PrincipalIdValue = Get-MgUser -UserId $Principal -ErrorAction SilentlyContinue + } + else + { + $PrincipalIdValue = Get-MgUser -Filter "UserPrincipalName eq '$Principal'" -ErrorAction SilentlyContinue + } + $PrincipalTypeValue = 'User' + } + + if ($null -eq $PrincipalIdValue -or $PrincipalType -eq 'Group') + { + Write-Verbose -Message "Retrieving principal {$Principal} of type {$PrincipalType}" + if ([System.Guid]::TryParse($Principal,[System.Management.Automation.PSReference]$ObjectGuid)) + { + $PrincipalIdValue = Get-MgGroup -GroupId $Principal -ErrorAction SilentlyContinue + } + else + { + $PrincipalIdValue = Get-MgGroup -Filter "DisplayName eq '$Principal'" -ErrorAction SilentlyContinue + } + $PrincipalTypeValue = 'Group' + } + + if ($null -ne $PrincipalIdValue) + { + $PrincipalId = $PrincipalIdValue.Id + } + else + { + return $nullResult + } + $RoleDefinitionId = (Get-MgBetaRoleManagementDirectoryRoleDefinition -Filter "DisplayName eq '$RoleDefinition'").Id + $schedules = Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -Filter "PrincipalId eq '$($request.PrincipalId)'" + $schedule = $schedules | Where-Object -FilterScript {$_.RoleDefinitionId -eq $RoleDefinitionId} + if ($null -eq $schedule) + { + foreach ($instance in $schedules) + { + $roleDefinitionInfo = Get-MgBetaRoleManagementDirectoryRoleDefinition -UnifiedRoleDefinitionId $instance.RoleDefinitionId + if ($null -ne $roleDefinitionInfo -and $RoleDefinitionInfo.DisplayName -eq $RoleDefinition) + { + $schedule = $instance + break + } + } + } + } + if ($null -eq $schedule -or $null -eq $request) + { + if ($null -eq $schedule) + { + Write-Verbose -Message "Could not retrieve the schedule for {$($request.PrincipalId)} & RoleDefinitionId {$RoleDefinitionId}" + } + if ($null -eq $request) + { + Write-Verbose -Message "Could not request the schedule for {$RoleDefinition}" + } + return $nullResult + } + + Write-Verbose -Message "Found existing AADRolelLigibilityScheduleRequest" + if ($PrincipalType -eq 'User') + { + Write-Verbose -Message "Retrieving Principal by UserId {$($request.PrincipalId)}" + $PrincipalInstance = Get-MgUser -UserId $request.PrincipalId -ErrorAction SilentlyContinue + $PrincipalTypeValue = 'User' + } + if ($null -eq $PrincipalInstance -or $PrincipalType -eq 'Group') + { + Write-Verbose -Message "Retrieving Principal by GroupId {$($request.PrincipalId)}" + $requestArray = [Array]$request + if ($requestArray.Count -gt 1) + { + $requestArray = $requestArray | Sort-Object -Property CreatedDateTime -Descending + $request = $requestArray[0] + } + $PrincipalInstance = Get-MGGroup -GroupId $request.PrincipalId -ErrorAction SilentlyContinue + $PrincipalTypeValue = 'Group' + } + + if ($null -eq $PrincipalInstance) + { + Write-Verbose -Message "Couldn't retrieve Principal {$($request.PrincipalId)}" + return $nullResult + } + + $ScheduleInfoValue = @{} + + if ($null -ne $schedule.ScheduleInfo.Expiration) + { + $expirationValue = @{ + duration = $schedule.ScheduleInfo.Expiration.Duration + type = $schedule.ScheduleInfo.Expiration.Type + } + if ($null -ne $schedule.ScheduleInfo.Expiration.EndDateTime) + { + $expirationValue.Add('endDateTime', $schedule.ScheduleInfo.Expiration.EndDateTime.ToString("yyyy-MM-ddThh:mm:ssZ")) + } + $ScheduleInfoValue.Add('expiration', $expirationValue) + } + if ($null -ne $schedule.ScheduleInfo.Recurrence) + { + $recurrenceValue = @{ + pattern = @{ + 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 = $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 $schedule.ScheduleInfo.StartDateTime) + { + $ScheduleInfoValue.Add('StartDateTime', $schedule.ScheduleInfo.StartDateTime.ToString("yyyy-MM-ddThh:mm:ssZ")) + } + + $ticketInfoValue = $null + if ($null -ne $request.TicketInfo) + { + $ticketInfoValue = @{ + ticketNumber = $request.TicketInfo.TicketNumber + ticketSystem = $request.TicketInfo.TicketSystem + } + } + + $PrincipalValue = $null + if ($PrincipalType -eq 'User') + { + $PrincipalValue = $PrincipalInstance.UserPrincipalName + } + if ($null -eq $PrincipalValue -or $PrincipalTypeValue -eq 'Group') + { + $PrincipalValue = $PrincipalInstance.DisplayName + } + + $results = @{ + Principal = $PrincipalValue + PrincipalType = $PrincipalTypeValue + RoleDefinition = $RoleDefinition + DirectoryScopeId = $request.DirectoryScopeId + AppScopeId = $request.AppScopeId + Action = $request.Action + Id = $request.Id + Justification = $request.Justification + IsValidationOnly = $request.IsValidationOnly + ScheduleInfo = $ScheduleInfoValue + TicketInfo = $ticketInfoValue + Ensure = 'Present' + Credential = $Credential + ApplicationId = $ApplicationId + TenantId = $TenantId + ApplicationSecret = $ApplicationSecret + CertificateThumbprint = $CertificateThumbprint + Managedidentity = $ManagedIdentity.IsPresent + AccessTokens = $AccessTokens + } + return $results + } + catch + { + Write-Verbose "Error: $_" + 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] + $Principal, + + [Parameter(Mandatory = $true)] + [System.String] + $RoleDefinition, + + [Parameter()] + [ValidateSet('User', 'Group')] + [System.String] + $PrincipalType = 'User', + + [Parameter()] + [System.String] + $Id, + + [Parameter()] + [System.String] + $DirectoryScopeId, + + [Parameter()] + [System.String] + $AppScopeId, + + [Parameter()] + [ValidateSet("adminAssign", "adminUpdate", "adminRemove", "selfActivate", "selfDeactivate", "adminExtend", "adminRenew", "selfExtend", "selfRenew", "unknownFutureValue")] + [System.String] + $Action, + + [Parameter()] + [System.String] + $Justification, + + [Parameter()] + [System.Boolean] + $IsValidationOnly, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $ScheduleInfo, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $TicketInfo, + + [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 + ) + try + { + $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters ` + } + catch + { + Write-Verbose -Message $_ + } + + #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 + + $PSBoundParameters.Remove('Ensure') | Out-Null + $PSBoundParameters.Remove('Credential') | Out-Null + $PSBoundParameters.Remove('ApplicationId') | Out-Null + $PSBoundParameters.Remove('ApplicationSecret') | Out-Null + $PSBoundParameters.Remove('TenantId') | Out-Null + $PSBoundParameters.Remove('CertificateThumbprint') | Out-Null + $PSBoundParameters.Remove('ManagedIdentity') | Out-Null + $PSBoundParameters.Remove('Verbose') | Out-Null + $PSBoundParameters.Remove('AccessTokens') | Out-Null + + $ParametersOps = ([Hashtable]$PSBoundParameters).clone() + + if ($PrincipalType -eq 'User') + { + [Array]$PrincipalIdValue = (Get-MgUser -Filter "UserPrincipalName eq '$Principal'").Id + } + elseif ($PrincipalType -eq 'Group') + { + [Array]$PrincipalIdValue = (Get-MgGroup -Filter "DisplayName eq '$Principal'").Id + } + + if ($null -eq $PrincipalIdValue) + { + throw "Couldn't find Principal {$PrincipalId} of type {$PrincipalType}" + } + elseif ($PrincipalIdValue.Length -gt 1) + { + throw "Multiple Principal with ID {$PrincipalId} of type {$PrincipalType} were found. Cannot create schedule." + } + $ParametersOps.Add("PrincipalId", $PrincipalIdValue[0]) + $ParametersOps.Remove("Principal") | Out-Null + + $RoleDefinitionIdValue = (Get-MgBetaRoleManagementDirectoryRoleDefinition -Filter "DisplayName eq '$RoleDefinition'").Id + $ParametersOps.Add("RoleDefinitionId", $RoleDefinitionIdValue) + $ParametersOps.Remove("RoleDefinition") | Out-Null + + if ($null -ne $ScheduleInfo) + { + $ScheduleInfoValue = @{} + + if ($ScheduleInfo.StartDateTime) + { + $ScheduleInfoValue.Add("startDateTime", $ScheduleInfo.StartDateTime) + } + + if ($ScheduleInfo.Expiration) + { + $expirationValue = @{ + endDateTime = $ScheduleInfo.Expiration.endDateTime + type = $ScheduleInfo.Expiration.type + } + if ($ScheduleInfo.Expiration.duration) + { + $expirationValue.Add('duration', $ScheduleInfo.Expiration.duration) + } + $ScheduleInfoValue.Add("Expiration", $expirationValue) + } + + if ($ScheduleInfo.Recurrence) + { + $Found = $false + $recurrenceValue = @{} + + if ($ScheduleInfo.Recurrence.Pattern) + { + $Found = $true + $patternValue = @{ + dayOfMonth = $ScheduleInfo.Recurrence.Pattern.dayOfMonth + daysOfWeek = $ScheduleInfo.Recurrence.Pattern.daysOfWeek + firstDayOfWeek = $ScheduleInfo.Recurrence.Pattern.firstDayOfWeek + index = $ScheduleInfo.Recurrence.Pattern.index + interval = $ScheduleInfo.Recurrence.Pattern.interval + month = $ScheduleInfo.Recurrence.Pattern.month + type = $ScheduleInfo.Recurrence.Pattern.type + } + $recurrenceValue.Add("Pattern", $patternValue) + } + if ($ScheduleInfo.Recurrence.Range) + { + $Found = $true + $rangeValue = @{ + endDate = $ScheduleInfo.Recurrence.Range.endDate + numberOfOccurrences = $ScheduleInfo.Recurrence.Range.numberOfOccurrences + recurrenceTimeZone = $ScheduleInfo.Recurrence.Range.recurrenceTimeZone + startDate = $ScheduleInfo.Recurrence.Range.startDate + type = $ScheduleInfo.Recurrence.Range.type + } + $recurrenceValue.Add("Range", $rangeValue) + } + if ($Found) + { + $ScheduleInfoValue.Add("Recurrence", $recurrenceValue) + } + } + Write-Verbose -Message "ScheduleInfo: $(Convert-M365DscHashtableToString -Hashtable $ScheduleInfoValue)" + $ParametersOps.ScheduleInfo = $ScheduleInfoValue + } + $ParametersOps.Remove("PrincipalType") | Out-Null + if ($Ensure -eq 'Present' -and $currentInstance.Ensure -eq 'Absent') + { + Write-Verbose -Message "Creating a Role Eligibility Schedule Request for user {$Principal} and role {$RoleDefinition}" + $ParametersOps.Remove("Id") | Out-Null + Write-Verbose -Message "Values: $(Convert-M365DscHashtableToString -Hashtable $ParametersOps)" + New-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest @ParametersOps + } + elseif ($Ensure -eq 'Present' -and $currentInstance.Ensure -eq 'Present') + { + Write-Verbose -Message "Updating the Role Eligibility Schedule Request for user {$Principal} and role {$RoleDefinition}" + $ParametersOps.Remove("Id") | Out-Null + $ParametersOps.Action = 'AdminUpdate' + New-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest @ParametersOps + } + elseif ($Ensure -eq 'Absent' -and $currentInstance.Ensure -eq 'Present') + { + Write-Verbose -Message "Removing the Role Eligibility Schedule Request for user {$Principal} and role {$RoleDefinition}" + $ParametersOps.Remove("Id") | Out-Null + $ParametersOps.Action = 'AdminRemove' + New-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest @ParametersOps + } +} + +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Principal, + + [Parameter(Mandatory = $true)] + [System.String] + $RoleDefinition, + + [Parameter()] + [ValidateSet('User', 'Group')] + [System.String] + $PrincipalType = 'User', + + [Parameter()] + [System.String] + $Id, + + [Parameter()] + [System.String] + $DirectoryScopeId, + + [Parameter()] + [System.String] + $AppScopeId, + + [Parameter()] + [ValidateSet("adminAssign", "adminUpdate", "adminRemove", "selfActivate", "selfDeactivate", "adminExtend", "adminRenew", "selfExtend", "selfRenew", "unknownFutureValue")] + [System.String] + $Action, + + [Parameter()] + [System.String] + $Justification, + + [Parameter()] + [System.Boolean] + $IsValidationOnly, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $ScheduleInfo, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $TicketInfo, + + [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 Role Eligibility Schedule Request for user {$Principal} and role {$RoleDefinition}" + + $CurrentValues = Get-TargetResource @PSBoundParameters + $ValuesToCheck = ([Hashtable]$PSBoundParameters).clone() + $ValuesToCheck.Remove("Action") | Out-Null + if($null -ne $CurrentValues.ScheduleInfo -and $null -ne $ValuesToCheck.ScheduleInfo) + { + # Compare ScheduleInfo.Expiration + if ($CurrentValues.ScheduleInfo.Expiration.duration -ne $ValuesToCheck.ScheduleInfo.Expiration.duration -or ` + $CurrentValues.ScheduleInfo.Expiration.endDateTime -ne $ValuesToCheck.ScheduleInfo.Expiration.endDateTime -or ` + $CurrentValues.ScheduleInfo.Expiration.type -ne $ValuesToCheck.ScheduleInfo.Expiration.type) + { + Write-Verbose -Message "Discrepancy found in ScheduleInfo.Expiration" + Write-Verbose -Message "Current: $($CurrentValues.ScheduleInfo.Expiration | Out-String)" + Write-Verbose -Message "Desired: $($ValuesToCheck.ScheduleInfo.Expiration | Out-String)" + return $false + } + + # Compare ScheduleInfo.Recurrence.Pattern + if ($CurrentValues.ScheduleInfo.Recurrence.Pattern.dayOfMonth -ne $ValuesToCheck.ScheduleInfo.Recurrence.Pattern.dayOfMonth -or ` + $CurrentValues.ScheduleInfo.Recurrence.Pattern.daysOfWeek -ne $ValuesToCheck.ScheduleInfo.Recurrence.Pattern.daysOfWeek -or ` + $CurrentValues.ScheduleInfo.Recurrence.Pattern.firstDayOfWeek -ne $ValuesToCheck.ScheduleInfo.Recurrence.Pattern.firstDayOfWeek -or ` + $CurrentValues.ScheduleInfo.Recurrence.Pattern.index -ne $ValuesToCheck.ScheduleInfo.Recurrence.Pattern.index -or ` + $CurrentValues.ScheduleInfo.Recurrence.Pattern.interval -ne $ValuesToCheck.ScheduleInfo.Recurrence.Pattern.interval -or ` + $CurrentValues.ScheduleInfo.Recurrence.Pattern.month -ne $ValuesToCheck.ScheduleInfo.Recurrence.Pattern.month -or ` + $CurrentValues.ScheduleInfo.Recurrence.Pattern.type -ne $ValuesToCheck.ScheduleInfo.Recurrence.Pattern.type) + { + Write-Verbose -Message "Discrepancy found in ScheduleInfo.Recurrence.Pattern" + Write-Verbose -Message "Current: $($CurrentValues.ScheduleInfo.Recurrence.Pattern | Out-String)" + Write-Verbose -Message "Desired: $($ValuesToCheck.ScheduleInfo.Recurrence.Pattern | Out-String)" + return $false + } + + # Compare ScheduleInfo.Recurrence.Range + if ($CurrentValues.ScheduleInfo.Recurrence.Range.endDate -ne $ValuesToCheck.ScheduleInfo.Recurrence.Range.endDate -or ` + $CurrentValues.ScheduleInfo.Recurrence.Range.numberOfOccurrences -ne $ValuesToCheck.ScheduleInfo.Recurrence.Range.numberOfOccurrences -or ` + $CurrentValues.ScheduleInfo.Recurrence.Range.recurrenceTimeZone -ne $ValuesToCheck.ScheduleInfo.Recurrence.Range.recurrenceTimeZone -or ` + $CurrentValues.ScheduleInfo.Recurrence.Range.startDate -ne $ValuesToCheck.ScheduleInfo.Recurrence.Range.startDate -or ` + $CurrentValues.ScheduleInfo.Recurrence.Range.type -ne $ValuesToCheck.ScheduleInfo.Recurrence.Range.type) + { + Write-Verbose -Message "Discrepancy found in ScheduleInfo.Recurrence.Range" + Write-Verbose -Message "Current: $($CurrentValues.ScheduleInfo.Recurrence.Range | Out-String)" + Write-Verbose -Message "Desired: $($ValuesToCheck.ScheduleInfo.Recurrence.Range | Out-String)" + return $false + } + } + + Write-Verbose -Message "Current Values: $(Convert-M365DscHashtableToString -Hashtable $CurrentValues)" + Write-Verbose -Message "Target Values: $(Convert-M365DscHashtableToString -Hashtable $ValuesToCheck)" + + $ValuesToCheck.Remove("ScheduleInfo") | 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, + + [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 + { + $Script:ExportMode = $true + #region resource generator code + $schedules = Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -All -ErrorAction Stop + [array] $Script:exportedInstances = @() + [array] $allRequests = Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest -All ` + -Filter "Status ne 'Revoked'" -ErrorAction Stop + foreach ($schedule in $schedules) + { + [array] $Script:exportedInstances += $allRequests | Where-Object -FilterScript {$_.TargetScheduleId -eq $schedule.Id} + } + #endregion + + $i = 1 + $dscContent = '' + if ($Script:exportedInstances.Length -eq 0) + { + Write-Host $Global:M365DSCEmojiGreenCheckMark + } + else + { + Write-Host "`r`n" -NoNewline + } + foreach ($request in $Script:exportedInstances) + { + if ($null -ne $Global:M365DSCExportResourceInstancesCount) + { + $Global:M365DSCExportResourceInstancesCount++ + } + + $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 = $RoleDefinitionId.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 + try + { + if ($null -ne $results.ScheduleInfo) + { + $Results.ScheduleInfo = Get-M365DSCAzureADEligibilityRequestScheduleInfoAsString -ScheduleInfo $Results.ScheduleInfo + } + } + catch + { + Write-Verbose -Message "Error converting Schedule: $_" + } + if ($Results.TicketInfo) + { + $Results.TicketInfo = Get-M365DSCAzureADEligibilityRequestTicketInfoAsString -TicketInfo $Results.TicketInfo + } + $currentDSCBlock = Get-M365DSCExportContentForResource -ResourceName $ResourceName ` + -ConnectionMode $ConnectionMode ` + -ModulePath $PSScriptRoot ` + -Results $Results ` + -Credential $Credential + if ($null -ne $Results.ScheduleInfo) + { + $currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock ` + -ParameterName 'ScheduleInfo' + } + if ($null -ne $Results.TicketInfo) + { + $currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock ` + -ParameterName 'TicketInfo' + } + + $dscContent += $currentDSCBlock + Save-M365DSCPartialExport -Content $currentDSCBlock ` + -FileName $Global:PartialExportFileName + $i++ + Write-Host $Global:M365DSCEmojiGreenCheckMark + } + return $dscContent + } + catch + { + if ($_.ErrorDetails.Message -like "*The tenant needs an AAD Premium*" -or ` + $_.ErrorDetails.MEssage -like "*[AadPremiumLicenseRequired]*") + { + Write-Host "`r`n $($Global:M365DSCEmojiYellowCircle) Tenant does not meet license requirement to extract this component." + } + else + { + Write-Verbose -Message "Exception: $($_.Exception.Message)" + Write-Host $Global:M365DSCEmojiRedX + New-M365DSCLogEntry -Message 'Error during Export:' ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantId ` + -Credential $Credential + } + + return '' + } +} + +function Get-M365DSCAzureADEligibilityRequestTicketInfoAsString +{ + [CmdletBinding()] + [OutputType([System.String])] + param( + [Parameter(Mandatory = $true)] + [System.Collections.Hashtable] + $TicketInfo + ) + + if ($TicketInfo.TicketNumber -or $TicketInfo.TicketSystem) + { + $StringContent = "MSFT_AADRoleAssignmentScheduleRequestTicketInfo {`r`n" + $StringContent += " ticketNumber = '$($TicketInfo.TicketNumber)'`r`n" + $StringContent += " ticketSystem = '$($TicketInfo.TicketSystem)'`r`n" + $StringContent += " }`r`n" + return $StringContent + } + else + { + return $null + } +} + +function Get-M365DSCAzureADEligibilityRequestScheduleInfoAsString +{ + [CmdletBinding()] + [OutputType([System.String])] + param( + [Parameter(Mandatory = $true)] + [System.Collections.Hashtable] + $ScheduleInfo + ) + + $Found = $false + $StringContent = "MSFT_AADRoleAssignmentScheduleRequestSchedule {`r`n" + if ($ScheduleInfo.StartDateTime) + { + $StringContent += " startDateTime = '$($ScheduleInfo.StartDateTime)'`r`n" + } + if ($ScheduleInfo.Expiration.Duration -or $ScheduleInfo.Expiration.EndDateTime -or $ScheduleInfo.Expiration.Type) + { + $Found = $true + $StringContent += " expiration = MSFT_AADRoleAssignmentScheduleRequestScheduleExpiration`r`n" + $StringContent += " {`r`n" + if ($ScheduleInfo.Expiration.Duration) + { + $StringContent += " duration = '$($ScheduleInfo.Expiration.Duration)'`r`n" + } + if ($ScheduleInfo.Expiration.EndDateTime) + { + $StringContent += " endDateTime = '$($ScheduleInfo.Expiration.EndDateTime.ToString())'`r`n" + } + if($ScheduleInfo.Expiration.Type) + { + $StringContent += " type = '$($ScheduleInfo.Expiration.Type)'`r`n" + } + $StringContent += " }`r`n" + } + if($ScheduleInfo.Recurrence.Pattern.DayOfMonth -or $ScheduleInfo.Recurrence.Pattern.DaysOfWeek -or ` + $ScheduleInfo.Recurrence.Pattern.firstDayOfWeek -or $ScheduleInfo.Recurrence.Pattern.Index -or ` + $ScheduleInfo.Recurrence.Pattern.Interval -or $ScheduleInfo.Recurrence.Pattern.Month -or ` + $ScheduleInfo.Recurrence.Pattern.Type -or $ScheduleInfo.Recurrence.Range.EndDate -or $ScheduleInfo.Recurrence.Range.numberOfOccurrences -or ` + $ScheduleInfo.Recurrence.Range.recurrenceTimeZone -or $ScheduleInfo.Recurrence.Range.startDate -or ` + $ScheduleInfo.Recurrence.Range.type) + { + $StringContent += " recurrence = MSFT_AADRoleAssignmentScheduleRequestScheduleRecurrence`r`n" + $StringContent += " {`r`n" + + if ($ScheduleInfo.Recurrence.Pattern.DayOfMonth -or $ScheduleInfo.Recurrence.Pattern.DaysOfWeek -or ` + $ScheduleInfo.Recurrence.Pattern.firstDayOfWeek -or $ScheduleInfo.Recurrence.Pattern.Index -or ` + $ScheduleInfo.Recurrence.Pattern.Interval -or $ScheduleInfo.Recurrence.Pattern.Month -or ` + $ScheduleInfo.Recurrence.Pattern.Type) + { + $Found = $true + $StringContent += " pattern = MSFT_AADRoleAssignmentScheduleRequestScheduleRecurrencePattern`r`n" + $StringContent += " {`r`n" + if ($ScheduleInfo.Recurrence.Pattern.DayOfMonth) + { + $StringContent += " dayOfMonth = $($ScheduleInfo.Recurrence.Pattern.DayOfMonth)`r`n" + } + if ($ScheduleInfo.Recurrence.Pattern.DaysOfWeek) + { + $StringContent += " daysOfWeek = @($($ScheduleInfo.Recurrence.Pattern.DaysOfWeek -join ','))`r`n" + } + if ($ScheduleInfo.Recurrence.Pattern.firstDayOfWeek) + { + $StringContent += " firstDayOfWeek = '$($ScheduleInfo.Recurrence.Pattern.firstDayOfWeek)'`r`n" + } + if ($ScheduleInfo.Recurrence.Pattern.Index) + { + $StringContent += " index = '$($ScheduleInfo.Recurrence.Pattern.Index)'`r`n" + } + if ($ScheduleInfo.Recurrence.Pattern.Interval) + { + $StringContent += " interval = $($ScheduleInfo.Recurrence.Pattern.Interval.ToString())`r`n" + } + if ($ScheduleInfo.Recurrence.Pattern.Month) + { + $StringContent += " month = $($ScheduleInfo.Recurrence.Pattern.Month.ToString())`r`n" + } + if ($ScheduleInfo.Recurrence.Pattern.Type) + { + $StringContent += " type = '$($ScheduleInfo.Recurrence.Pattern.Type)'`r`n" + } + $StringContent += " }`r`n" + } + if ($ScheduleInfo.Recurrence.Range.EndDate -or $ScheduleInfo.Recurrence.Range.numberOfOccurrences -or ` + $ScheduleInfo.Recurrence.Range.recurrenceTimeZone -or $ScheduleInfo.Recurrence.Range.startDate -or ` + $ScheduleInfo.Recurrence.Range.type) + { + $Found = $true + $StringContent += " range = MSFT_AADRoleAssignmentScheduleRequestScheduleRange`r`n" + $StringContent += " {`r`n" + $StringContent += " endDate = '$($ScheduleInfo.Recurrence.Range.EndDate)'`r`n" + $StringContent += " numberOfOccurrences = $($ScheduleInfo.Recurrence.Range.numberOfOccurrences)`r`n" + $StringContent += " recurrenceTimeZone = '$($ScheduleInfo.Recurrence.Range.recurrenceTimeZone)'`r`n" + $StringContent += " startDate = '$($ScheduleInfo.Recurrence.Range.startDate)'`r`n" + $StringContent += " type = '$($ScheduleInfo.Recurrence.Range.type)'`r`n" + $StringContent += " }`r`n" + } + + $StringContent += " }`r`n" + } + $StringContent += " }`r`n" + + if ($Found) + { + return $StringContent + } + return $null +} + +Export-ModuleMember -Function *-TargetResource diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.schema.mof new file mode 100644 index 0000000000..c940cf80b3 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.schema.mof @@ -0,0 +1,75 @@ +[ClassVersion("1.0.0")] +class MSFT_AADRoleAssignmentScheduleRequestScheduleRecurrenceRange +{ + [Required, 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.")] 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.")] String recurrenceTimeZone; + [Required, 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.")] String startDate; + [Required, Description("The recurrence range. The possible values are: endDate, noEnd, numbered."),ValueMap{"endDate","noEnd","numbered"}, Values{"endDate","noEnd","numbered"}] String type; +}; + +[ClassVersion("1.0.0")] +class MSFT_AADRoleAssignmentScheduleRequestScheduleRecurrencePattern +{ + [Write, Description("The day of the month on which the event occurs.")] 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"), 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."), 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."), 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.")] 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."), ValueMap{"daily","weekly","absoluteMonthly","relativeMonthly","absoluteYearly","relativeYearly"}, Values{"daily","weekly","absoluteMonthly","relativeMonthly","absoluteYearly","relativeYearly"}] String type; +}; + +[ClassVersion("1.0.0")] +class MSFT_AADRoleAssignmentScheduleRequestScheduleRecurrence +{ + [Write, Description("The frequency of an event."), EmbeddedInstance("MSFT_AADRoleAssignmentScheduleRequestScheduleRecurrencePattern")] String pattern; + [Write, Description("The duration of an event."), EmbeddedInstance("MSFT_AADRoleAssignmentScheduleRequestScheduleRecurrenceRange")] String range; +}; + +[ClassVersion("1.0.0")] +class MSFT_AADRoleAssignmentScheduleRequestScheduleExpiration +{ + [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_AADRoleAssignmentScheduleRequestSchedule +{ + [Write, Description("When the eligible or active assignment expires."), EmbeddedInstance("MSFT_AADRoleAssignmentScheduleRequestScheduleExpiration")] String expiration; + [Write, Description("The frequency of the eligible or active assignment. This property is currently unsupported in PIM."), EmbeddedInstance("MSFT_AADRoleAssignmentScheduleRequestScheduleRecurrence")] String recurrence; + [Write, Description("When the eligible or active assignment becomes active.")] String startDateTime; +}; + +[ClassVersion("1.0.0")] +class MSFT_AADRoleAssignmentScheduleRequestTicketInfo +{ + [Write, Description("The ticket number.")] String ticketNumber; + [Write, Description("The description of the ticket system.")] String ticketSystem; +}; + +[ClassVersion("1.0.0.0"), FriendlyName("AADRoleAssignmentScheduleRequest")] +class MSFT_AADRoleAssignmentScheduleRequest : OMI_BaseResource +{ + [Key, Description("User Principal Name of the eligibility request.")] String Principal; + [Key, Description("Role associated with the eligibility request.")] String RoleDefinition; + [Write, Description("Represented the type of principal to assign the request to. Accepted values are: Group and User."), ValueMap{"Group","User"}, Values{"Group","User"}] String PrincipalType; + [Write, Description("Identifier of the directory object representing the scope of the role eligibility. The scope of an role eligibility determines the set of resources for which the principal has been granted access. Directory scopes are shared scopes stored in the directory that are understood by multiple applications. Use / for tenant-wide scope. Use appScopeId to limit the scope to an application only. Either directoryScopeId or appScopeId is required.")] String DirectoryScopeId; + [Write, Description("Identifier for the Role Eligibility Schedule Request.")] String Id; + [Write, Description("Identifier of the app-specific scope when the role eligibility is scoped to an app. The scope of a role eligibility determines the set of resources for which the principal is eligible to access. App scopes are scopes that are defined and understood by this application only. Use / for tenant-wide app scopes. Use directoryScopeId to limit the scope to particular directory objects, for example, administrative units. Either directoryScopeId or appScopeId is required.")] String AppScopeId; + [Write, Description("Represents the type of operation on the role eligibility request.The possible values are: adminAssign, adminUpdate, adminRemove, selfActivate, selfDeactivate, adminExtend, adminRenew, selfExtend, selfRenew, unknownFutureValue."), ValueMap{"adminAssign","adminUpdate","adminRemove","selfActivate","selfDeactivate","adminExtend","adminRenew","selfExtend","selfRenew","unknownFutureValue"}, Values{"adminAssign","adminUpdate","adminRemove","selfActivate","selfDeactivate","adminExtend","adminRenew","selfExtend","selfRenew","unknownFutureValue"}] String Action; + [Write, Description("Determines whether the call is a validation or an actual call. Only set this property if you want to check whether an activation is subject to additional rules like MFA before actually submitting the request.")] Boolean IsValidationOnly; + [Write, Description("A message provided by users and administrators when create they create the unifiedRoileAssignmentScheduleRequest object. Optional when action is adminRemove. Whether this property is required or optional is also dependent on the settings for the Azure AD role.")] String Justification; + [Write, Description("The period of the role eligibility. Optional when action is adminRemove. The period of eligibility is dependent on the settings of the Azure AD role."), EmbeddedInstance("MSFT_AADRoleAssignmentScheduleRequestSchedule")] String ScheduleInfo; + [Write, Description("Ticket details linked to the role eligibility request including details of the ticket number and ticket system."), EmbeddedInstance("MSFT_AADRoleAssignmentScheduleRequestTicketInfo")] String TicketInfo; + [Write, Description("Present ensures the instance 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 application to authenticate with."), 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_AADRoleAssignmentScheduleRequest/readme.md b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/readme.md new file mode 100644 index 0000000000..bcec0502ad --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/readme.md @@ -0,0 +1,6 @@ + +# AADRoleAssignmentScheduleRequest + +## Description + +This resource configures an Azure Active Directory Privilege Identity Management assignment. diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/settings.json b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/settings.json new file mode 100644 index 0000000000..6bcd855bee --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/settings.json @@ -0,0 +1,28 @@ +{ + "resourceName": "AADRoleAssignmentScheduleRequest", + "description": "This resource configures an Azure Active Directory Privilege Identity Management assignment.", + "roles": { + "read": [ + "Security Reader" + ], + "update": [ + "Privileged Role Administrator" + ] + }, + "permissions": { + "graph": { + "application": { + "read": [ + { + "name": "RoleAssignmentSchedule.Read.Directory" + } + ], + "update": [ + { + "name": "RoleAssignmentSchedule.ReadWrite.Directory" + } + ] + } + } + } +} diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/settings.json b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/settings.json index 920f41deef..357f40c509 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/settings.json +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleEligibilityScheduleRequest/settings.json @@ -1,6 +1,6 @@ { "resourceName": "AADRoleEligibilityScheduleRequest", - "description": "This resource configures an Azure Active Directory administrative unit.", + "description": "This resource configures an Azure Active Directory Privilege Identity Management eligibility request.", "roles": { "read": [ "Security Reader" diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/1-Create.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/1-Create.ps1 new file mode 100644 index 0000000000..b516274848 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/1-Create.ps1 @@ -0,0 +1,26 @@ +<# +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()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint + ) + Import-DscResource -ModuleName Microsoft365DSC + node localhost + { + + } +} diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/2-Update.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/2-Update.ps1 new file mode 100644 index 0000000000..b516274848 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/2-Update.ps1 @@ -0,0 +1,26 @@ +<# +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()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint + ) + Import-DscResource -ModuleName Microsoft365DSC + node localhost + { + + } +} diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/3-Remove.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/3-Remove.ps1 new file mode 100644 index 0000000000..b516274848 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/3-Remove.ps1 @@ -0,0 +1,26 @@ +<# +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()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint + ) + Import-DscResource -ModuleName Microsoft365DSC + node localhost + { + + } +} diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleAssignmentScheduleRequest.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleAssignmentScheduleRequest.Tests.ps1 new file mode 100644 index 0000000000..780e0f343d --- /dev/null +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleAssignmentScheduleRequest.Tests.ps1 @@ -0,0 +1,178 @@ +[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) + +$CurrentScriptPath = $PSCommandPath.Split('\') +$CurrentScriptName = $CurrentScriptPath[$CurrentScriptPath.Length -1] +$ResourceName = $CurrentScriptName.Split('.')[1] +$Global:DscHelper = New-M365DscUnitTestHelper -StubModule $CmdletModule ` + -DscResource $ResourceName -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 New-M365DSCConnection -MockWith { + return "Credentials" + } + + ##TODO - Mock any Remove/Set/New cmdlets + + # 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 instance should exist but it DOES NOT" -Fixture { + BeforeAll { + $testParams = @{ + ##TODO - Add Parameters + Ensure = 'Present' + Credential = $Credential; + } + + ##TODO - Mock the Get-Cmdlet to return $null + Mock -CommandName Get-Cmdlet -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 a new instance from the Set method' { + ##TODO - Replace the New-Cmdlet by the appropriate one + Set-TargetResource @testParams + Should -Invoke -CommandName New-Cmdlet -Exactly 1 + } + } + + Context -Name "The instance exists but it SHOULD NOT" -Fixture { + BeforeAll { + $testParams = @{ + ##TODO - Add Parameters + Ensure = 'Absent' + Credential = $Credential; + } + + ##TODO - Mock the Get-Cmdlet to return an instance + Mock -CommandName Get-Cmdlet -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 remove the instance from the Set method' { + Set-TargetResource @testParams + ##TODO - Replace the Remove-Cmdlet by the appropriate one + Should -Invoke -CommandName Remove-Cmdlet -Exactly 1 + } + } + + Context -Name "The instance exists and values are already in the desired state" -Fixture { + BeforeAll { + $testParams = @{ + ##TODO - Add Parameters + Ensure = 'Present' + Credential = $Credential; + } + + ##TODO - Mock the Get-Cmdlet to return the desired values + Mock -CommandName Get-Cmdlet -MockWith { + return @{ + + } + } + } + + It 'Should return true from the Test method' { + Test-TargetResource @testParams | Should -Be $true + } + } + + Context -Name "The instance exists and values are NOT in the desired state" -Fixture { + BeforeAll { + $testParams = @{ + ##TODO - Add Parameters + Ensure = 'Present' + Credential = $Credential; + } + + ##TODO - Mock the Get-Cmdlet to return a drift + Mock -CommandName Get-Cmdlet -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 + ##TODO - Replace the Update-Cmdlet by the appropriate one + Should -Invoke -CommandName Update-Cmdlet -Exactly 1 + } + } + + Context -Name 'ReverseDSC Tests' -Fixture { + BeforeAll { + $Global:CurrentModeIsExport = $true + $Global:PartialExportFileName = "$(New-Guid).partial.ps1" + $testParams = @{ + Credential = $Credential; + } + + ##TODO - Mock the Get-Cmdlet to return an instance + Mock -CommandName Get-Cmdlet -MockWith { + return @{ + + } + } + } + It 'Should Reverse Engineer resource from the Export method' { + $result = Export-TargetResource @testParams + $result | Should -Not -BeNullOrEmpty + } + } + } +} + +Invoke-Command -ScriptBlock $Global:DscHelper.CleanupScript -NoNewScope From 450baaef3e0183302c3e7a9bcfe2ea64804895e3 Mon Sep 17 00:00:00 2001 From: Nik Charlebois Date: Tue, 19 Nov 2024 08:25:24 -0500 Subject: [PATCH 2/5] Fixes --- ...MSFT_AADRoleAssignmentScheduleRequest.psm1 | 263 ++++++------------ ...ADRoleAssignmentScheduleRequest.schema.mof | 2 +- 2 files changed, 86 insertions(+), 179 deletions(-) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.psm1 index bea1cf6526..53dcf44137 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.psm1 @@ -12,10 +12,10 @@ function Get-TargetResource [System.String] $RoleDefinition, - [Parameter()] - [ValidateSet('User', 'Group')] + [Parameter(Mandatory = $true)] + [ValidateSet('User', 'Group', 'ServicePrincipal')] [System.String] - $PrincipalType = 'User', + $PrincipalType, [Parameter()] [System.String] @@ -83,18 +83,11 @@ function Get-TargetResource [System.String[]] $AccessTokens ) - try - { - $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` - -InboundParameters $PSBoundParameters - } - catch - { - Write-Verbose -Message ($_) - } + $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters - #Ensure the proper dependencies are installed in the current environment. - Confirm-M365DSCDependencies + #Ensure the proper dependencies are installed in the current environment. + Confirm-M365DSCDependencies #region Telemetry $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace('MSFT_', '') @@ -124,137 +117,58 @@ function Get-TargetResource } } - if ($null -eq $request) + Write-Verbose -Message "Getting Role Eligibility by PrincipalId and RoleDefinitionId" + $PrincipalValue = $null + if ($PrincipalType -eq 'User') { - if ($null -ne $Script:exportedInstances -and $Script:ExportMode) - { - Write-Verbose -Message "Getting Role Eligibility by PrincipalId and RoleDefinitionId" - $PrincipalTypeValue = $null - if ($PrincipalType -eq 'User') - { - Write-Verbose -Message "Retrieving Principal by UserPrincipalName {$Principal}" - $PrincipalIdValue = Get-MgUser -Filter "UserPrincipalName eq '$Principal'" -ErrorAction SilentlyContinue - $PrincipalTypeValue = 'User' - } - if ($null -eq $PrincipalIdValue -or $PrincipalType -eq 'Group') - { - Write-Verbose -Message "Retrieving Principal by DisplayName {$Principal}" - $PrincipalIdValue = Get-MgGroup -Filter "DisplayName eq '$Principal'" -ErrorAction SilentlyContinue - $PrincipalTypeValue = 'Group' - } - - if ($null -ne $PrincipalIdValue) - { - $PrincipalId = $PrincipalIdValue.Id - } - else - { - return $nullResult - } - Write-Verbose -Message "Found Principal {$PrincipalId}" - $RoleDefinitionId = (Get-MgBetaRoleManagementDirectoryRoleDefinition -Filter "DisplayName eq '$RoleDefinition'").Id - $request = $Script:exportedInstances | Where-Object -FilterScript {$_.PrincipalId -eq $PrincipalId -and $_.RoleDefinitionId -eq $RoleDefinition} | Sort-Object -Property CompletedDateTime -Descending - } - else - { - Write-Verbose -Message "Getting Role Eligibility by PrincipalId and RoleDefinitionId" - if ($PrincipalType -eq 'User') - { - Write-Verbose -Message "Retrieving principal {$Principal} of type {$PrincipalType}" - $PrincipalIdValue = Get-MgUser -Filter "UserPrincipalName eq '$Principal'" -ErrorAction SilentlyContinue - $PrincipalTypeValue = 'User' - } - - if ($null -eq $PrincipalIdValue -or $PrincipalType -eq 'Group') - { - Write-Verbose -Message "Retrieving principal {$Principal} of type {$PrincipalType}" - $PrincipalIdValue = Get-MgGroup -Filter "DisplayName eq '$Principal'" -ErrorAction SilentlyContinue - $PrincipalTypeValue = 'Group' - } - - if ($null -ne $PrincipalIdValue) - { - $PrincipalId = $PrincipalIdValue.Id - } - else - { - return $nullResult - } - Write-Verbose -Message "Found Principal {$PrincipalId}" - $schedulesForPrincipal = Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -Filter "PrincipalId eq '$PrincipalId'" - foreach ($instance in $schedulesForPrincipal) - { - $roleInfo = Get-MgBetaRoleManagementDirectoryRoleDefinition -UnifiedRoleDefinitionId $instance.RoleDefinitionId - if ($roleInfo.DisplayName -eq $RoleDefinition) - { - $schedule = $instance - } - } - [Array]$request = Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest -Filter "PrincipalId eq '$PrincipalId'" | Where-Object -FilterScript {$_.RoleDefinitionId -eq $schedule.RoleDefinitionId} | Sort-Object -Property CompletedDateTime -Descending -` - if ($request.Length -gt 1) - { - $request = $request[0] - } - } + Write-Verbose -Message "Retrieving Principal by UserPrincipalName {$Principal}" + $PrincipalInstance = Get-MgUser -Filter "UserPrincipalName eq '$Principal'" -ErrorAction SilentlyContinue + $PrincipalValue = $PrincipalInstance.UserPrincipalName + } + elseif ($null -eq $PrincipalIdValue -and $PrincipalType -eq 'Group') + { + Write-Verbose -Message "Retrieving Principal by DisplayName {$Principal}" + $PrincipalInstance = Get-MgGroup -Filter "DisplayName eq '$Principal'" -ErrorAction SilentlyContinue + $PrincipalValue = $PrincipalInstance.DisplayName } else { - Write-Verbose -Message "Request is not null: $request" - $ObjectGuid = [System.Guid]::empty - if ($PrincipalType -eq 'User') - { - Write-Verbose -Message "Retrieving principal {$Principal} of type {$PrincipalType}" - - if ([System.Guid]::TryParse($Principal,[System.Management.Automation.PSReference]$ObjectGuid)) - { - $PrincipalIdValue = Get-MgUser -UserId $Principal -ErrorAction SilentlyContinue - } - else - { - $PrincipalIdValue = Get-MgUser -Filter "UserPrincipalName eq '$Principal'" -ErrorAction SilentlyContinue - } - $PrincipalTypeValue = 'User' - } + Write-Verbose -Message "Retrieving Principal by DisplayName {$Principal}" + $PrincipalInstance = Get-MgServicePrincipal -Filter "DisplayName eq '$Principal'" -ErrorAction SilentlyContinue + $PrincipalValue = $PrincipalInstance.DisplayName + } - if ($null -eq $PrincipalIdValue -or $PrincipalType -eq 'Group') - { - Write-Verbose -Message "Retrieving principal {$Principal} of type {$PrincipalType}" - if ([System.Guid]::TryParse($Principal,[System.Management.Automation.PSReference]$ObjectGuid)) - { - $PrincipalIdValue = Get-MgGroup -GroupId $Principal -ErrorAction SilentlyContinue - } - else - { - $PrincipalIdValue = Get-MgGroup -Filter "DisplayName eq '$Principal'" -ErrorAction SilentlyContinue - } - $PrincipalTypeValue = 'Group' - } + Write-Verbose -Message "Found Principal" + $RoleDefinitionId = (Get-MgBetaRoleManagementDirectoryRoleDefinition -Filter "DisplayName eq '$RoleDefinition'").Id + Write-Verbose -Message "Retrieved role definition {$RoleDefinition} with ID {$RoleDefinitionId}" - if ($null -ne $PrincipalIdValue) - { - $PrincipalId = $PrincipalIdValue.Id - } - else + if ($null -eq $request) + { + Write-Verbose -Message "Retrieving the request by PrincipalId {$($PrincipalInstance.Id)}, RoleDefinitionId {$($RoleDefinitionId)} and DirectoryScopeId {$($DirectoryScopeId)}" + [Array] $requests = Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest -Filter "PrincipalId eq '$($PrincipalInstance.Id)' and RoleDefinitionId eq '$($RoleDefinitionId)' and DirectoryScopeId eq '$($DirectoryScopeId)'" + if ($requests.Length -eq 0) { return $nullResult } - $RoleDefinitionId = (Get-MgBetaRoleManagementDirectoryRoleDefinition -Filter "DisplayName eq '$RoleDefinition'").Id - $schedules = Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -Filter "PrincipalId eq '$($request.PrincipalId)'" - $schedule = $schedules | Where-Object -FilterScript {$_.RoleDefinitionId -eq $RoleDefinitionId} - if ($null -eq $schedule) + + $request = $requests[0] + } + + $schedules = Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -Filter "PrincipalId eq '$($request.PrincipalId)'" + $schedule = $schedules | Where-Object -FilterScript {$_.RoleDefinitionId -eq $RoleDefinitionId} + if ($null -eq $schedule) + { + foreach ($instance in $schedules) { - foreach ($instance in $schedules) + $roleDefinitionInfo = Get-MgBetaRoleManagementDirectoryRoleDefinition -UnifiedRoleDefinitionId $instance.RoleDefinitionId + if ($null -ne $roleDefinitionInfo -and $RoleDefinitionInfo.DisplayName -eq $RoleDefinition) { - $roleDefinitionInfo = Get-MgBetaRoleManagementDirectoryRoleDefinition -UnifiedRoleDefinitionId $instance.RoleDefinitionId - if ($null -ne $roleDefinitionInfo -and $RoleDefinitionInfo.DisplayName -eq $RoleDefinition) - { - $schedule = $instance - break - } + $schedule = $instance + break } } } + if ($null -eq $schedule -or $null -eq $request) { if ($null -eq $schedule) @@ -268,32 +182,6 @@ function Get-TargetResource return $nullResult } - Write-Verbose -Message "Found existing AADRolelLigibilityScheduleRequest" - if ($PrincipalType -eq 'User') - { - Write-Verbose -Message "Retrieving Principal by UserId {$($request.PrincipalId)}" - $PrincipalInstance = Get-MgUser -UserId $request.PrincipalId -ErrorAction SilentlyContinue - $PrincipalTypeValue = 'User' - } - if ($null -eq $PrincipalInstance -or $PrincipalType -eq 'Group') - { - Write-Verbose -Message "Retrieving Principal by GroupId {$($request.PrincipalId)}" - $requestArray = [Array]$request - if ($requestArray.Count -gt 1) - { - $requestArray = $requestArray | Sort-Object -Property CreatedDateTime -Descending - $request = $requestArray[0] - } - $PrincipalInstance = Get-MGGroup -GroupId $request.PrincipalId -ErrorAction SilentlyContinue - $PrincipalTypeValue = 'Group' - } - - if ($null -eq $PrincipalInstance) - { - Write-Verbose -Message "Couldn't retrieve Principal {$($request.PrincipalId)}" - return $nullResult - } - $ScheduleInfoValue = @{} if ($null -ne $schedule.ScheduleInfo.Expiration) @@ -344,19 +232,9 @@ function Get-TargetResource } } - $PrincipalValue = $null - if ($PrincipalType -eq 'User') - { - $PrincipalValue = $PrincipalInstance.UserPrincipalName - } - if ($null -eq $PrincipalValue -or $PrincipalTypeValue -eq 'Group') - { - $PrincipalValue = $PrincipalInstance.DisplayName - } - $results = @{ Principal = $PrincipalValue - PrincipalType = $PrincipalTypeValue + PrincipalType = $PrincipalType RoleDefinition = $RoleDefinition DirectoryScopeId = $request.DirectoryScopeId AppScopeId = $request.AppScopeId @@ -403,10 +281,10 @@ function Set-TargetResource [System.String] $RoleDefinition, - [Parameter()] - [ValidateSet('User', 'Group')] + [Parameter(Mandatory = $true)] + [ValidateSet('User', 'Group', 'ServicePrincipal')] [System.String] - $PrincipalType = 'User', + $PrincipalType, [Parameter()] [System.String] @@ -518,6 +396,10 @@ function Set-TargetResource { [Array]$PrincipalIdValue = (Get-MgGroup -Filter "DisplayName eq '$Principal'").Id } + elseif ($PrincipalType -eq 'ServicePrincipal') + { + [Array]$PrincipalIdValue = (Get-MgServicePrincipal -Filter "DisplayName eq '$Principal'").Id + } if ($null -eq $PrincipalIdValue) { @@ -598,21 +480,21 @@ function Set-TargetResource $ParametersOps.Remove("PrincipalType") | Out-Null if ($Ensure -eq 'Present' -and $currentInstance.Ensure -eq 'Absent') { - Write-Verbose -Message "Creating a Role Eligibility Schedule Request for user {$Principal} and role {$RoleDefinition}" + Write-Verbose -Message "Creating a Role Assignment Schedule Request for principal {$Principal} and role {$RoleDefinition}" $ParametersOps.Remove("Id") | Out-Null Write-Verbose -Message "Values: $(Convert-M365DscHashtableToString -Hashtable $ParametersOps)" New-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest @ParametersOps } elseif ($Ensure -eq 'Present' -and $currentInstance.Ensure -eq 'Present') { - Write-Verbose -Message "Updating the Role Eligibility Schedule Request for user {$Principal} and role {$RoleDefinition}" + Write-Verbose -Message "Updating the Role Assignment Schedule Request for principal {$Principal} and role {$RoleDefinition}" $ParametersOps.Remove("Id") | Out-Null $ParametersOps.Action = 'AdminUpdate' New-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest @ParametersOps } elseif ($Ensure -eq 'Absent' -and $currentInstance.Ensure -eq 'Present') { - Write-Verbose -Message "Removing the Role Eligibility Schedule Request for user {$Principal} and role {$RoleDefinition}" + Write-Verbose -Message "Removing the Role Assignment Schedule Request for principal {$Principal} and role {$RoleDefinition}" $ParametersOps.Remove("Id") | Out-Null $ParametersOps.Action = 'AdminRemove' New-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest @ParametersOps @@ -633,10 +515,10 @@ function Test-TargetResource [System.String] $RoleDefinition, - [Parameter()] - [ValidateSet('User', 'Group')] + [Parameter(Mandatory = $true)] + [ValidateSet('User', 'Group', 'ServicePrincipal')] [System.String] - $PrincipalType = 'User', + $PrincipalType, [Parameter()] [System.String] @@ -862,10 +744,35 @@ function Export-TargetResource $displayedKey = $request.Id Write-Host " |---[$i/$($Script:exportedInstances.Count)] $displayedKey" -NoNewline + # Find the Principal Type + $principalType = 'User' + $userInfo = Get-MgUser -UserId $request.PrincipalId -ErrorAction SilentlyContinue + + if ($null -eq $userInfo) + { + $principalType = 'Group' + $groupInfo = Get-MgGroup -GroupId $request.PrincipalId -ErrorAction SilentlyContinue + if ($null -eq $groupInfo) + { + $principalType = 'ServicePrincipal' + $spnInfo = Get-MgServicePrincipal -ServicePrincipalId $request.PrincipalId + $PrincipalValue = $spnInfo.DisplayName + } + else + { + $PrincipalValue = $groupInfo.DisplayName + } + } + else + { + $PrincipalValue = $userInfo.UserPrincipalName + } + $RoleDefinitionId = Get-MgBetaRoleManagementDirectoryRoleDefinition -UnifiedRoleDefinitionId $request.RoleDefinitionId $params = @{ Id = $request.Id - Principal = $request.PrincipalId + Principal = $PrincipalValue + PrincipalType = $principalType RoleDefinition = $RoleDefinitionId.DisplayName Ensure = 'Present' Credential = $Credential diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.schema.mof index c940cf80b3..06953d873c 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.schema.mof +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.schema.mof @@ -55,7 +55,7 @@ class MSFT_AADRoleAssignmentScheduleRequest : OMI_BaseResource { [Key, Description("User Principal Name of the eligibility request.")] String Principal; [Key, Description("Role associated with the eligibility request.")] String RoleDefinition; - [Write, Description("Represented the type of principal to assign the request to. Accepted values are: Group and User."), ValueMap{"Group","User"}, Values{"Group","User"}] String PrincipalType; + [Write, Description("Represented the type of principal to assign the request to. Accepted values are: Group and User."), ValueMap{"Group","User","ServicePrincipal"}, Values{"Group","User","ServicePrincipal"}] String PrincipalType; [Write, Description("Identifier of the directory object representing the scope of the role eligibility. The scope of an role eligibility determines the set of resources for which the principal has been granted access. Directory scopes are shared scopes stored in the directory that are understood by multiple applications. Use / for tenant-wide scope. Use appScopeId to limit the scope to an application only. Either directoryScopeId or appScopeId is required.")] String DirectoryScopeId; [Write, Description("Identifier for the Role Eligibility Schedule Request.")] String Id; [Write, Description("Identifier of the app-specific scope when the role eligibility is scoped to an app. The scope of a role eligibility determines the set of resources for which the principal is eligible to access. App scopes are scopes that are defined and understood by this application only. Use / for tenant-wide app scopes. Use directoryScopeId to limit the scope to particular directory objects, for example, administrative units. Either directoryScopeId or appScopeId is required.")] String AppScopeId; From cbe4930877e61382d18ea8f14522ea93819da024 Mon Sep 17 00:00:00 2001 From: Nik Charlebois Date: Tue, 19 Nov 2024 09:06:12 -0500 Subject: [PATCH 3/5] AADRoleAssignmentScheduleRequest - Initial Release --- CHANGELOG.md | 2 + ...MSFT_AADRoleAssignmentScheduleRequest.psm1 | 7 +- ...ADRoleAssignmentScheduleRequest.schema.mof | 2 +- .../1-Create.ps1 | 21 +- .../2-Update.ps1 | 21 +- .../3-Remove.ps1 | 21 +- ...AADRoleAssignmentScheduleRequest.Tests.ps1 | 242 +++++++--- Tests/Unit/Stubs/Microsoft365.psm1 | 429 +++++++++--------- 8 files changed, 458 insertions(+), 287 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c983de7fe7..63006d0b92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ sessionControl parameter when there are no session controls, which throws an error. * Fixed bug where a null value was passed in the request for the applicationEnforcedRestrictions parameter when value was set to false, which throws an error. +* AADRoleAssignmentScheduleRequest + * Initial release. * AADRoleEligibilityScheduleRequest * Adds support for custom role assignments at app scope. * IntuneFirewallRulesHyperVPolicyWindows10 diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.psm1 index 53dcf44137..609c174b67 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.psm1 @@ -21,7 +21,7 @@ function Get-TargetResource [System.String] $Id, - [Parameter()] + [Parameter(Mandatory = $true)] [System.String] $DirectoryScopeId, @@ -290,7 +290,7 @@ function Set-TargetResource [System.String] $Id, - [Parameter()] + [Parameter(Mandatory = $true)] [System.String] $DirectoryScopeId, @@ -524,7 +524,7 @@ function Test-TargetResource [System.String] $Id, - [Parameter()] + [Parameter(Mandatory = $true)] [System.String] $DirectoryScopeId, @@ -773,6 +773,7 @@ function Export-TargetResource Id = $request.Id Principal = $PrincipalValue PrincipalType = $principalType + DirectoryScopeId = $request.DirectoryScopeId RoleDefinition = $RoleDefinitionId.DisplayName Ensure = 'Present' Credential = $Credential diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.schema.mof index 06953d873c..276dfb57ef 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.schema.mof +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADRoleAssignmentScheduleRequest/MSFT_AADRoleAssignmentScheduleRequest.schema.mof @@ -56,7 +56,7 @@ class MSFT_AADRoleAssignmentScheduleRequest : OMI_BaseResource [Key, Description("User Principal Name of the eligibility request.")] String Principal; [Key, Description("Role associated with the eligibility request.")] String RoleDefinition; [Write, Description("Represented the type of principal to assign the request to. Accepted values are: Group and User."), ValueMap{"Group","User","ServicePrincipal"}, Values{"Group","User","ServicePrincipal"}] String PrincipalType; - [Write, Description("Identifier of the directory object representing the scope of the role eligibility. The scope of an role eligibility determines the set of resources for which the principal has been granted access. Directory scopes are shared scopes stored in the directory that are understood by multiple applications. Use / for tenant-wide scope. Use appScopeId to limit the scope to an application only. Either directoryScopeId or appScopeId is required.")] String DirectoryScopeId; + [Key, Description("Identifier of the directory object representing the scope of the role eligibility. The scope of an role eligibility determines the set of resources for which the principal has been granted access. Directory scopes are shared scopes stored in the directory that are understood by multiple applications. Use / for tenant-wide scope. Use appScopeId to limit the scope to an application only. Either directoryScopeId or appScopeId is required.")] String DirectoryScopeId; [Write, Description("Identifier for the Role Eligibility Schedule Request.")] String Id; [Write, Description("Identifier of the app-specific scope when the role eligibility is scoped to an app. The scope of a role eligibility determines the set of resources for which the principal is eligible to access. App scopes are scopes that are defined and understood by this application only. Use / for tenant-wide app scopes. Use directoryScopeId to limit the scope to particular directory objects, for example, administrative units. Either directoryScopeId or appScopeId is required.")] String AppScopeId; [Write, Description("Represents the type of operation on the role eligibility request.The possible values are: adminAssign, adminUpdate, adminRemove, selfActivate, selfDeactivate, adminExtend, adminRenew, selfExtend, selfRenew, unknownFutureValue."), ValueMap{"adminAssign","adminUpdate","adminRemove","selfActivate","selfDeactivate","adminExtend","adminRenew","selfExtend","selfRenew","unknownFutureValue"}, Values{"adminAssign","adminUpdate","adminRemove","selfActivate","selfDeactivate","adminExtend","adminRenew","selfExtend","selfRenew","unknownFutureValue"}] String Action; diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/1-Create.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/1-Create.ps1 index b516274848..28973b551b 100644 --- a/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/1-Create.ps1 +++ b/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/1-Create.ps1 @@ -21,6 +21,25 @@ Configuration Example Import-DscResource -ModuleName Microsoft365DSC node localhost { - + AADRoleAssignmentScheduleRequest "MyRequest" + { + Action = "AdminAssign"; + ApplicationId = $ApplicationId + TenantId = $TenantId + CertificateThumbprint = $CertificateThumbprint + DirectoryScopeId = "/"; + Ensure = "Present"; + IsValidationOnly = $False; + Principal = "AdeleV@$TenantId"; + RoleDefinition = "Teams Communications Administrator"; + ScheduleInfo = MSFT_AADRoleAssignmentScheduleRequestSchedule { + startDateTime = '2023-09-01T02:40:44Z' + expiration = MSFT_AADRoleAssignmentScheduleRequestScheduleExpiration + { + endDateTime = '2025-10-31T02:40:09Z' + type = 'afterDateTime' + } + }; + } } } diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/2-Update.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/2-Update.ps1 index b516274848..201957e4dc 100644 --- a/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/2-Update.ps1 +++ b/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/2-Update.ps1 @@ -21,6 +21,25 @@ Configuration Example Import-DscResource -ModuleName Microsoft365DSC node localhost { - + AADRoleAssignmentScheduleRequest "MyRequest" + { + Action = "AdminUpdate"; + ApplicationId = $ApplicationId + TenantId = $TenantId + CertificateThumbprint = $CertificateThumbprint + DirectoryScopeId = "/"; + Ensure = "Present"; + IsValidationOnly = $False; + Principal = "AdeleV@$TenantId"; + RoleDefinition = "Teams Communications Administrator"; + ScheduleInfo = MSFT_AADRoleAssignmentScheduleRequestSchedule { + startDateTime = '2023-09-01T02:45:44Z' # Updated Property + expiration = MSFT_AADRoleAssignmentScheduleRequestScheduleExpiration + { + endDateTime = '2025-10-31T02:40:09Z' + type = 'afterDateTime' + } + }; + } } } diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/3-Remove.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/3-Remove.ps1 index b516274848..3b5f3d400b 100644 --- a/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/3-Remove.ps1 +++ b/Modules/Microsoft365DSC/Examples/Resources/AADRoleAssignmentScheduleRequest/3-Remove.ps1 @@ -21,6 +21,25 @@ Configuration Example Import-DscResource -ModuleName Microsoft365DSC node localhost { - + AADRoleAssignmentScheduleRequest "MyRequest" + { + Action = "AdminAssign"; + ApplicationId = $ApplicationId + TenantId = $TenantId + CertificateThumbprint = $CertificateThumbprint + DirectoryScopeId = "/"; + Ensure = "Absent"; + IsValidationOnly = $True; # Updated Property + Principal = "AdeleV@$TenantId"; + RoleDefinition = "Teams Communications Administrator"; + ScheduleInfo = MSFT_AADRoleAssignmentScheduleRequestSchedule { + startDateTime = '2023-09-01T02:40:44Z' + expiration = MSFT_AADRoleAssignmentScheduleRequestScheduleExpiration + { + endDateTime = '2025-10-31T02:40:09Z' + type = 'afterDateTime' + } + }; + } } } diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleAssignmentScheduleRequest.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleAssignmentScheduleRequest.Tests.ps1 index 780e0f343d..af3ab222ef 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleAssignmentScheduleRequest.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADRoleAssignmentScheduleRequest.Tests.ps1 @@ -1,41 +1,63 @@ [CmdletBinding()] param( ) + $M365DSCTestFolder = Join-Path -Path $PSScriptRoot ` - -ChildPath '..\..\Unit' ` - -Resolve + -ChildPath '..\..\Unit' ` + -Resolve $CmdletModule = (Join-Path -Path $M365DSCTestFolder ` - -ChildPath '\Stubs\Microsoft365.psm1' ` - -Resolve) + -ChildPath '\Stubs\Microsoft365.psm1' ` + -Resolve) $GenericStubPath = (Join-Path -Path $M365DSCTestFolder ` - -ChildPath '\Stubs\Generic.psm1' ` - -Resolve) + -ChildPath '\Stubs\Generic.psm1' ` + -Resolve) Import-Module -Name (Join-Path -Path $M365DSCTestFolder ` -ChildPath '\UnitTestHelper.psm1' ` -Resolve) - -$CurrentScriptPath = $PSCommandPath.Split('\') -$CurrentScriptName = $CurrentScriptPath[$CurrentScriptPath.Length -1] -$ResourceName = $CurrentScriptName.Split('.')[1] $Global:DscHelper = New-M365DscUnitTestHelper -StubModule $CmdletModule ` - -DscResource $ResourceName -GenericStubModule $GenericStubPath + -DscResource 'AADRoleAssignmentScheduleRequest' -GenericStubModule $GenericStubPath Describe -Name $Global:DscHelper.DescribeHeader -Fixture { InModuleScope -ModuleName $Global:DscHelper.ModuleName -ScriptBlock { Invoke-Command -ScriptBlock $Global:DscHelper.InitializeScript -NoNewScope BeforeAll { - + $Global:CurrentModeIsExport = $false $secpasswd = ConvertTo-SecureString (New-Guid | Out-String) -AsPlainText -Force $Credential = New-Object System.Management.Automation.PSCredential ('tenantadmin@mydomain.com', $secpasswd) + $Script:exportedInstances = $null + $Script:ExportMode = $null + Mock -CommandName Add-M365DSCTelemetryEvent -MockWith { + } Mock -CommandName Confirm-M365DSCDependencies -MockWith { } Mock -CommandName New-M365DSCConnection -MockWith { - return "Credentials" + return 'Credentials' + } + + Mock -CommandName New-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest -MockWith { } - ##TODO - Mock any Remove/Set/New cmdlets + Mock -CommandName Get-MgUser -MockWith { + return @{ + Id = '123456' + UserPrincipalName = 'John.Smith@contoso.com' + } + } + + Mock -CommandName Get-MgBetaRoleManagementDirectoryRoleDefinition -MockWith { + return @{ + DisplayName = 'Teams Communications Administrator' + Id = '12345' + } + } + Mock -CommandName Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -MockWith { + return @{ + Id = '12345-12345-12345-12345-12345' + RoleDefinitionId = "12345" + } + } # Mock Write-Host to hide output during the tests Mock -CommandName Write-Host -MockWith { @@ -44,16 +66,27 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { $Script:ExportMode = $false } # Test contexts - Context -Name "The instance should exist but it DOES NOT" -Fixture { + Context -Name 'The instance should exist but it DOES NOT' -Fixture { BeforeAll { $testParams = @{ - ##TODO - Add Parameters - Ensure = 'Present' - Credential = $Credential; + Action = "AdminAssign"; + DirectoryScopeId = "/"; + Ensure = "Present"; + IsValidationOnly = $False; + Principal = "John.Smith@contoso.com"; + PrincipalType = "User" + RoleDefinition = "Teams Communications Administrator"; + ScheduleInfo = New-CimInstance -ClassName MSFT_AADRoleAssignmentScheduleRequestSchedule -Property @{ + startDateTime = '2023-09-01T02:40:44Z' + expiration = New-CimInstance -ClassName MSFT_AADRoleAssignmentScheduleRequestScheduleExpiration -Property @{ + endDateTime = '2025-10-31T02:40:09Z' + type = 'afterDateTime' + } -ClientOnly + } -ClientOnly + Credential = $Credential } - ##TODO - Mock the Get-Cmdlet to return $null - Mock -CommandName Get-Cmdlet -MockWith { + Mock -CommandName Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest -MockWith { return $null } } @@ -63,78 +96,161 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { It 'Should return false from the Test method' { Test-TargetResource @testParams | Should -Be $false } - - It 'Should create a new instance from the Set method' { - ##TODO - Replace the New-Cmdlet by the appropriate one + It 'Should Create the instance from the Set method' { Set-TargetResource @testParams - Should -Invoke -CommandName New-Cmdlet -Exactly 1 + Should -Invoke -CommandName New-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest -Exactly 1 } } - Context -Name "The instance exists but it SHOULD NOT" -Fixture { + Context -Name 'The instance exists but it SHOULD NOT' -Fixture { BeforeAll { $testParams = @{ - ##TODO - Add Parameters - Ensure = 'Absent' - Credential = $Credential; + Action = "AdminAssign"; + DirectoryScopeId = "/"; + Ensure = "Absent"; + IsValidationOnly = $False; + PrincipalType = "User" + Principal = "John.Smith@contoso.com"; + RoleDefinition = "Teams Communications Administrator"; + ScheduleInfo = New-CimInstance -ClassName MSFT_AADRoleAssignmentScheduleRequestSchedule -Property @{ + + expiration = New-CimInstance -ClassName MSFT_AADRoleAssignmentScheduleRequestScheduleExpiration -Property @{ + + type = 'afterDateTime' + } -ClientOnly + } -ClientOnly + Credential = $Credential } - ##TODO - Mock the Get-Cmdlet to return an instance - Mock -CommandName Get-Cmdlet -MockWith { + Mock -CommandName Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest -MockWith { return @{ - + Action = "AdminAssign"; + Id = '12345-12345-12345-12345-12345' + DirectoryScopeId = "/"; + IsValidationOnly = $False; + PrincipalId = "123456"; + RoleDefinitionId = "12345"; + ScheduleInfo = @{ + startDateTime = [System.DateTime]::Parse('2023-09-01T02:40:44Z') + expiration = @{ + endDateTime = [System.DateTime]::Parse('2025-10-31T02:40:09Z') + type = 'afterDateTime' + } + }; } } } + 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 remove the instance from the Set method' { + It 'Should Remove the instance from the Set method' { Set-TargetResource @testParams - ##TODO - Replace the Remove-Cmdlet by the appropriate one - Should -Invoke -CommandName Remove-Cmdlet -Exactly 1 + Should -Invoke -CommandName New-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest -Exactly 1 } } - - Context -Name "The instance exists and values are already in the desired state" -Fixture { + Context -Name 'The instance Exists and Values are already in the desired state' -Fixture { BeforeAll { $testParams = @{ - ##TODO - Add Parameters - Ensure = 'Present' - Credential = $Credential; + Action = "AdminAssign"; + DirectoryScopeId = "/"; + Ensure = "Present"; + IsValidationOnly = $False; + PrincipalType = "User" + Principal = "John.Smith@contoso.com"; + RoleDefinition = "Teams Communications Administrator"; + ScheduleInfo = New-CimInstance -ClassName MSFT_AADRoleAssignmentScheduleRequestSchedule -Property @{ + + expiration = New-CimInstance -ClassName MSFT_AADRoleAssignmentScheduleRequestScheduleExpiration -Property @{ + type = 'afterDateTime' + } -ClientOnly + } -ClientOnly + Credential = $Credential } - ##TODO - Mock the Get-Cmdlet to return the desired values - Mock -CommandName Get-Cmdlet -MockWith { + Mock -CommandName Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest -MockWith { return @{ - + Action = "AdminAssign"; + Id = '12345-12345-12345-12345-12345' + DirectoryScopeId = "/"; + IsValidationOnly = $False; + PrincipalId = "123456"; + RoleDefinitionId = "12345"; + ScheduleInfo = @{ + expiration = @{ + type = 'afterDateTime' + } + }; + } + } + Mock -CommandName Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule -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' { + (Get-TargetResource @testParams).Ensure | Should -Be 'Present' + } + It 'Should return true from the Test method' { Test-TargetResource @testParams | Should -Be $true } } - - Context -Name "The instance exists and values are NOT in the desired state" -Fixture { + Context -Name 'The instance Exists and specified Values are NOT in the desired state' -Fixture { BeforeAll { $testParams = @{ - ##TODO - Add Parameters - Ensure = 'Present' - Credential = $Credential; + Action = "AdminAssign"; + DirectoryScopeId = "/"; + Ensure = "Present"; + IsValidationOnly = $False; + PrincipalType = "User" + Principal = "John.Smith@contoso.com"; + RoleDefinition = "Teams Communications Administrator"; + ScheduleInfo = New-CimInstance -ClassName MSFT_AADRoleAssignmentScheduleRequestSchedule -Property @{ + + expiration = New-CimInstance -ClassName MSFT_AADRoleAssignmentScheduleRequestScheduleExpiration -Property @{ + + type = 'afterDateTime' + } -ClientOnly + } -ClientOnly + Credential = $Credential } - ##TODO - Mock the Get-Cmdlet to return a drift - Mock -CommandName Get-Cmdlet -MockWith { + Mock -CommandName Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest -MockWith { return @{ - + Action = "AdminAssign"; + Id = '12345-12345-12345-12345-12345' + DirectoryScopeId = "/"; + IsValidationOnly = $False; + PrincipalId = "123456"; + RoleDefinitionId = "12345"; + ScheduleInfo = @{ + startDateTime = [System.DateTime]::Parse('2023-09-01T02:40:44Z') + expiration = @{ + endDateTime = [System.DateTime]::Parse('2025-10-31T02:40:09Z') + type = 'afterDateTime' + } + }; } } + } It 'Should return Values from the Get method' { @@ -145,25 +261,35 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Test-TargetResource @testParams | Should -Be $false } - It 'Should call the Set method' { + It 'Should call the Set to Update the instance' { Set-TargetResource @testParams - ##TODO - Replace the Update-Cmdlet by the appropriate one - Should -Invoke -CommandName Update-Cmdlet -Exactly 1 + Should -Invoke -CommandName Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest -Exactly 1 } } - Context -Name 'ReverseDSC Tests' -Fixture { BeforeAll { $Global:CurrentModeIsExport = $true $Global:PartialExportFileName = "$(New-Guid).partial.ps1" $testParams = @{ - Credential = $Credential; + Credential = $Credential } - ##TODO - Mock the Get-Cmdlet to return an instance - Mock -CommandName Get-Cmdlet -MockWith { + Mock -CommandName Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest -MockWith { return @{ - + Action = "AdminAssign"; + Id = '12345-12345-12345-12345-12345' + DirectoryScopeId = "/"; + IsValidationOnly = $False; + PrincipalId = "123456"; + RoleDefinitionId = "12345"; + ScheduleInfo = @{ + startDateTime = [System.DateTime]::Parse('2023-09-01T02:40:44Z') + expiration = @{ + endDateTime = [System.DateTime]::Parse('2025-10-31T02:40:09Z') + type = 'afterDateTime' + } + }; + TargetScheduleId = "12345-12345-12345-12345-12345" } } } diff --git a/Tests/Unit/Stubs/Microsoft365.psm1 b/Tests/Unit/Stubs/Microsoft365.psm1 index ac7c4b04d9..5e512d8f32 100644 --- a/Tests/Unit/Stubs/Microsoft365.psm1 +++ b/Tests/Unit/Stubs/Microsoft365.psm1 @@ -33371,6 +33371,83 @@ function Get-MgBetaRoleManagementDirectoryRoleEligibilityScheduleRequest $HttpPipelineAppend ) } +function Get-MgBetaRoleManagementDirectoryRoleEAssignmentScheduleRequest +{ + [CmdletBinding()] + param( + [Parameter()] + [System.String[]] + $Property, + + [Parameter()] + [System.String] + $UnifiedRoleEligibilityScheduleRequestId, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ProxyUseDefaultCredentials, + + [Parameter()] + [System.Int32] + $PageSize, + + [Parameter()] + [PSObject] + $HttpPipelinePrepend, + + [Parameter()] + [System.Int32] + $Skip, + + [Parameter()] + [PSObject] + $InputObject, + + [Parameter()] + [System.Int32] + $Top, + + [Parameter()] + [System.String] + $CountVariable, + + [Parameter()] + [System.Uri] + $Proxy, + + [Parameter()] + [System.String[]] + $Sort, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $All, + + [Parameter()] + [System.String] + $Filter, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ProxyCredential, + + [Parameter()] + [System.String] + $Search, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Break, + + [Parameter()] + [System.String[]] + $ExpandProperty, + + [Parameter()] + [PSObject] + $HttpPipelineAppend + ) +} function New-MgBetaEntitlementManagementAccessPackage { [CmdletBinding()] @@ -34654,6 +34731,136 @@ function New-MgBetaRoleManagementDirectoryRoleEligibilityScheduleRequest $HttpPipelineAppend ) } + +function New-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest +{ + [CmdletBinding()] + param( + [Parameter()] + [System.String] + $Justification, + + [Parameter()] + [PSObject] + $Principal, + + [Parameter()] + [System.DateTime] + $CreatedDateTime, + + [Parameter()] + [System.String] + $Action, + + [Parameter()] + [System.Collections.Hashtable] + $AdditionalProperties, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ProxyUseDefaultCredentials, + + [Parameter()] + [PSObject] + $ScheduleInfo, + + [Parameter()] + [PSObject] + $DirectoryScope, + + [Parameter()] + [PSObject] + $TargetSchedule, + + [Parameter()] + [System.String] + $ApprovalId, + + [Parameter()] + [PSObject] + $HttpPipelinePrepend, + + [Parameter()] + [System.String] + $CustomData, + + [Parameter()] + [PSObject] + $CreatedBy, + + [Parameter()] + [System.String] + $PrincipalId, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $IsValidationOnly, + + [Parameter()] + [System.DateTime] + $CompletedDateTime, + + [Parameter()] + [PSObject] + $TicketInfo, + + [Parameter()] + [System.Uri] + $Proxy, + + [Parameter()] + [PSObject] + $BodyParameter, + + [Parameter()] + [System.String] + $Status, + + [Parameter()] + [System.String] + $Id, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Confirm, + + [Parameter()] + [System.String] + $TargetScheduleId, + + [Parameter()] + [System.String] + $RoleDefinitionId, + + [Parameter()] + [PSObject] + $RoleDefinition, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ProxyCredential, + + [Parameter()] + [PSObject] + $AppScope, + + [Parameter()] + [System.String] + $DirectoryScopeId, + + [Parameter()] + [System.String] + $AppScopeId, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Break, + + [Parameter()] + [PSObject] + $HttpPipelineAppend + ) +} function Remove-MgBetaEntitlementManagementAccessPackage { [CmdletBinding()] @@ -43390,91 +43597,6 @@ function Get-MgBetaRoleManagementDirectoryRoleEligibilitySchedule $HttpPipelineAppend ) } -function Get-MgBetaRoleManagementDirectoryRoleEligibilityScheduleRequest -{ - [CmdletBinding()] - param( - [Parameter()] - [System.String[]] - $Property, - - [Parameter()] - [System.String] - $UnifiedRoleEligibilityScheduleRequestId, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $ProxyUseDefaultCredentials, - - [Parameter()] - [System.Int32] - $PageSize, - - [Parameter()] - [PSObject] - $HttpPipelinePrepend, - - [Parameter()] - [System.Int32] - $Skip, - - [Parameter()] - [PSObject] - $InputObject, - - [Parameter()] - [System.Int32] - $Top, - - [Parameter()] - [System.String] - $CountVariable, - - [Parameter()] - [System.Uri] - $Proxy, - - [Parameter()] - [System.String[]] - $Sort, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $All, - - [Parameter()] - [System.String] - $Filter, - - [Parameter()] - [System.Management.Automation.PSCredential] - $ProxyCredential, - - [Parameter()] - [System.String] - $Search, - - [Parameter()] - [System.String] - $ResponseHeadersVariable, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $Break, - - [Parameter()] - [System.String[]] - $ExpandProperty, - - [Parameter()] - [System.Collections.IDictionary] - $Headers, - - [Parameter()] - [PSObject] - $HttpPipelineAppend - ) -} function Get-MgBetaRoleManagementEntitlementManagement { [CmdletBinding()] @@ -44960,143 +45082,6 @@ function New-MgBetaRoleManagementDirectoryRoleEligibilitySchedule $HttpPipelineAppend ) } -function New-MgBetaRoleManagementDirectoryRoleEligibilityScheduleRequest -{ - [CmdletBinding()] - param( - [Parameter()] - [System.String] - $Justification, - - [Parameter()] - [PSObject] - $Principal, - - [Parameter()] - [System.DateTime] - $CreatedDateTime, - - [Parameter()] - [System.String] - $Action, - - [Parameter()] - [System.Collections.Hashtable] - $AdditionalProperties, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $ProxyUseDefaultCredentials, - - [Parameter()] - [PSObject] - $ScheduleInfo, - - [Parameter()] - [PSObject] - $DirectoryScope, - - [Parameter()] - [PSObject] - $TargetSchedule, - - [Parameter()] - [System.String] - $ApprovalId, - - [Parameter()] - [PSObject] - $HttpPipelinePrepend, - - [Parameter()] - [System.String] - $CustomData, - - [Parameter()] - [PSObject] - $CreatedBy, - - [Parameter()] - [System.String] - $PrincipalId, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $IsValidationOnly, - - [Parameter()] - [System.DateTime] - $CompletedDateTime, - - [Parameter()] - [PSObject] - $TicketInfo, - - [Parameter()] - [System.Uri] - $Proxy, - - [Parameter()] - [PSObject] - $BodyParameter, - - [Parameter()] - [System.String] - $Status, - - [Parameter()] - [System.String] - $Id, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $Confirm, - - [Parameter()] - [System.String] - $TargetScheduleId, - - [Parameter()] - [System.String] - $RoleDefinitionId, - - [Parameter()] - [PSObject] - $RoleDefinition, - - [Parameter()] - [System.Management.Automation.PSCredential] - $ProxyCredential, - - [Parameter()] - [PSObject] - $AppScope, - - [Parameter()] - [System.String] - $DirectoryScopeId, - - [Parameter()] - [System.String] - $ResponseHeadersVariable, - - [Parameter()] - [System.String] - $AppScopeId, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $Break, - - [Parameter()] - [System.Collections.IDictionary] - $Headers, - - [Parameter()] - [PSObject] - $HttpPipelineAppend - ) -} function New-MgBetaRoleManagementEntitlementManagementRoleAssignment { [CmdletBinding()] From 4e29d3ea31acc772fce25e7a5474cc6ee6910e44 Mon Sep 17 00:00:00 2001 From: Nik Charlebois Date: Tue, 19 Nov 2024 10:05:54 -0500 Subject: [PATCH 4/5] Update Microsoft365.psm1 --- Tests/Unit/Stubs/Microsoft365.psm1 | 87 ++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/Tests/Unit/Stubs/Microsoft365.psm1 b/Tests/Unit/Stubs/Microsoft365.psm1 index 5e512d8f32..fb374ff977 100644 --- a/Tests/Unit/Stubs/Microsoft365.psm1 +++ b/Tests/Unit/Stubs/Microsoft365.psm1 @@ -104401,3 +104401,90 @@ function Get-MigrationUser ) } #endregion +#region Microsoft.Graph.Authentication +function Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule +{ + [CmdletBinding()] + param( + [Parameter()] + [System.String[]] + $Property, + + [Parameter()] + [PSObject] + $InputObject, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ProxyUseDefaultCredentials, + + [Parameter()] + [System.Int32] + $PageSize, + + [Parameter()] + [PSObject] + $HttpPipelinePrepend, + + [Parameter()] + [System.Int32] + $Skip, + + [Parameter()] + [System.Int32] + $Top, + + [Parameter()] + [System.String] + $CountVariable, + + [Parameter()] + [System.Uri] + $Proxy, + + [Parameter()] + [System.String[]] + $Sort, + + [Parameter()] + [System.String] + $UnifiedRoleAssignmentScheduleId, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $All, + + [Parameter()] + [System.String] + $Filter, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ProxyCredential, + + [Parameter()] + [System.String] + $Search, + + [Parameter()] + [System.String] + $ResponseHeadersVariable, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Break, + + [Parameter()] + [System.String[]] + $ExpandProperty, + + [Parameter()] + [System.Collections.IDictionary] + $Headers, + + [Parameter()] + [PSObject] + $HttpPipelineAppend + ) +} +#endregion From 31ae7ff7bd07e0dfb269e991f5a47459c3998543 Mon Sep 17 00:00:00 2001 From: Nik Charlebois Date: Tue, 19 Nov 2024 10:29:57 -0500 Subject: [PATCH 5/5] Update Microsoft365.psm1 --- Tests/Unit/Stubs/Microsoft365.psm1 | 88 ++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/Tests/Unit/Stubs/Microsoft365.psm1 b/Tests/Unit/Stubs/Microsoft365.psm1 index fb374ff977..e29324881e 100644 --- a/Tests/Unit/Stubs/Microsoft365.psm1 +++ b/Tests/Unit/Stubs/Microsoft365.psm1 @@ -104488,3 +104488,91 @@ function Get-MgBetaRoleManagementDirectoryRoleAssignmentSchedule ) } #endregion + +#region Microsoft.Graph.Authentication +function Get-MgBetaRoleManagementDirectoryRoleAssignmentScheduleRequest +{ + [CmdletBinding()] + param( + [Parameter()] + [System.String[]] + $Property, + + [Parameter()] + [PSObject] + $InputObject, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $ProxyUseDefaultCredentials, + + [Parameter()] + [System.String] + $UnifiedRoleAssignmentScheduleRequestId, + + [Parameter()] + [System.Int32] + $PageSize, + + [Parameter()] + [PSObject] + $HttpPipelinePrepend, + + [Parameter()] + [System.Int32] + $Skip, + + [Parameter()] + [System.Int32] + $Top, + + [Parameter()] + [System.String] + $CountVariable, + + [Parameter()] + [System.Uri] + $Proxy, + + [Parameter()] + [System.String[]] + $Sort, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $All, + + [Parameter()] + [System.String] + $Filter, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ProxyCredential, + + [Parameter()] + [System.String] + $Search, + + [Parameter()] + [System.String] + $ResponseHeadersVariable, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Break, + + [Parameter()] + [System.String[]] + $ExpandProperty, + + [Parameter()] + [System.Collections.IDictionary] + $Headers, + + [Parameter()] + [PSObject] + $HttpPipelineAppend + ) +} +#endregion