diff --git a/CHANGELOG.md b/CHANGELOG.md index 8275a583fb..ad6175a1d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * AADApplication * Fixed an issue trying to retrieve the beta instance. * Added support for OnPremisesPublishing. + * Added support for ApplicationTemplate. * AADAuthenticationRequirement * Initial release. * AADConnectorGroupApplicationProxy diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 index e2834628f8..4b4c6f68e7 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 @@ -92,6 +92,10 @@ function Get-TargetResource [Microsoft.Management.Infrastructure.CimInstance] $OnPremisesPublishing, + [Parameter()] + [System.String] + $ApplicationTemplateId, + [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] @@ -157,7 +161,7 @@ function Get-TargetResource } else { - $AADApp = Get-MgApplication -Filter "AppId eq '$AppId'" + $AADApp = Get-MgBetaApplication -Filter "AppId eq '$AppId'" } } } @@ -176,7 +180,7 @@ function Get-TargetResource } else { - $AADApp = Get-MgApplication -Filter "DisplayName eq '$($DisplayName)'" + $AADApp = [Array](Get-MgBetaApplication -Filter "DisplayName eq '$($DisplayName)'") } } if ($null -ne $AADApp -and $AADApp.Count -gt 1) @@ -192,9 +196,8 @@ function Get-TargetResource { Write-Verbose -Message 'An instance of Azure AD App was retrieved.' - - $AADBetaApp= Get-MgBetaApplication -Property "id,displayName,appId,authenticationBehaviors" -ApplicationId $AADApp.Id -ErrorAction SilentlyContinue - $AADAppKeyCredentials = Get-MgApplication -Property "keyCredentials" -ApplicationId $AADApp.Id -ErrorAction SilentlyContinue + $AADBetaApp= Get-MgBetaApplication -Property "id,displayName,appId,authenticationBehaviors,additionalProperties" -ApplicationId $AADApp.Id -ErrorAction SilentlyContinue + $AADAppKeyCredentials = Get-MgBetaApplication -Property "keyCredentials" -ApplicationId $AADApp.Id -ErrorAction SilentlyContinue $complexAuthenticationBehaviors = @{} if ($null -ne $AADBetaApp.authenticationBehaviors.blockAzureADGraphAccess) @@ -475,7 +478,8 @@ function Get-TargetResource PasswordCredentials = $complexPasswordCredentials AppRoles = $complexAppRoles Permissions = $permissionsObj - OnPremisesPublishing = $onPremisesPublishingValue + OnPremisesPublishing = $onPremisesPublishingValue + ApplicationTemplateId = $AADApp.AdditionalProperties.applicationTemplateId Ensure = 'Present' Credential = $Credential ApplicationId = $ApplicationId @@ -601,6 +605,10 @@ function Set-TargetResource [Microsoft.Management.Infrastructure.CimInstance] $OnPremisesPublishing, + [Parameter()] + [System.String] + $ApplicationTemplateId, + [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] @@ -806,10 +814,44 @@ function Set-TargetResource Write-Verbose -Message "Multiple instances of a deleted application with name {$DisplayName} wehre found. Creating a new instance since we can't determine what instance to restore." } } + + # Create from Template + $createdFromTemplate = $false + if ($Ensure -eq 'Present' -and $currentAADApp.Ensure -eq 'Absent' -and -not $skipToUpdate -and ` + -not [System.String]::IsNullOrEmpty($ApplicationTemplateId) -and ` + $ApplicationTemplateId -ne '8adf8e6e-67b2-4cf2-a259-e3dc5476c621') + { + $skipToUpdate = $true + Write-Verbose -Message "Creating application {$DisplayName} from Application Template {$ApplicationTemplateId}" + $newApp = Invoke-MgBetaInstantiateApplicationTemplate -DisplayName $DisplayName ` + -ApplicationTemplateId $ApplicationTemplateId + $currentAADApp = @{ + AppId = $newApp.Application.AppId + Id = $newApp.Application.AppId + DisplayName = $newApp.Application.DisplayName + ObjectId = $newApp.Application.AdditionalProperties.objectId + } + + $createdFromTemplate = $true + + do + { + Write-Verbose -Message 'Waiting for 10 seconds' + Start-Sleep -Seconds 10 + $appEntity = Get-MgApplication -ApplicationId $currentAADApp.AppId -ErrorAction SilentlyContinue + $tries++ + } until ($null -eq $appEntity -or $tries -le 12) + } + Write-Host "Ensure = $Ensure" + Write-Host "ApplicationTemplateId = $ApplicationTemplateId" + Write-Host "skipToUpdate = $skipToUpdate" + Write-Host "currentAADApp.Ensure = $($currentAADApp.Ensure))" if ($Ensure -eq 'Present' -and $currentAADApp.Ensure -eq 'Absent' -and -not $skipToUpdate) { - Write-Verbose -Message "Creating New AzureAD Application {$DisplayName} with values:`r`n$($currentParameters | Out-String)" $currentParameters.Remove('ObjectId') | Out-Null + $currentParameters.Remove('ApplicationTemplateId') | Out-Null + Write-Verbose -Message "Creating New AzureAD Application {$DisplayName} with values:`r`n$($currentParameters | Out-String)" + $currentAADApp = New-MgApplication @currentParameters Write-Verbose -Message "Azure AD Application {$DisplayName} was successfully created" $needToUpdatePermissions = $true @@ -831,15 +873,21 @@ function Set-TargetResource elseif (($Ensure -eq 'Present' -and $currentAADApp.Ensure -eq 'Present') -or $skipToUpdate) { $currentParameters.Remove('ObjectId') | Out-Null + $currentParameters.Remove('ApplicationTemplateId') | Out-Null - if (-not $skipToUpdate) + if (-not $skipToUpdate -or $createdFromTemplate) { $AppIdValue = $currentAADApp.ObjectId } + $currentParameters.Add('ApplicationId', $AppIdValue) Write-Verbose -Message "Updating existing AzureAD Application {$DisplayName} with values:`r`n$($currentParameters | Out-String)" Update-MgApplication @currentParameters - $currentAADApp.Add('ID', $AppIdValue) + + if (-not $currentAADApp.ContainsKey('ID')) + { + $currentAADApp.Add('ID', $AppIdValue) + } $needToUpdatePermissions = $true $needToUpdateAuthenticationBehaviors = $true $needToUpdateKeyCredentials = $true @@ -1188,6 +1236,10 @@ function Test-TargetResource [Microsoft.Management.Infrastructure.CimInstance] $OnPremisesPublishing, + [Parameter()] + [System.String] + $ApplicationTemplateId, + [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] @@ -1305,7 +1357,6 @@ function Test-TargetResource $ValuesToCheck.Remove('AppId') | Out-Null $ValuesToCheck.Remove('Permissions') | Out-Null - $TestResult = Test-M365DSCParameterState -CurrentValues $CurrentValues ` -Source $($MyInvocation.MyCommand.Source) ` -DesiredValues $PSBoundParameters ` @@ -1383,7 +1434,7 @@ function Export-TargetResource try { $Script:ExportMode = $true - [array] $Script:exportedInstances = Get-MgApplication -Filter $Filter -All -ErrorAction Stop + [array] $Script:exportedInstances = Get-MgBetaApplication -Filter $Filter -All -ErrorAction Stop foreach ($AADApp in $Script:exportedInstances) { if ($null -ne $Global:M365DSCExportResourceInstancesCount) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.schema.mof index ae3c0625d2..21e278cc40 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.schema.mof +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.schema.mof @@ -155,6 +155,7 @@ class MSFT_AADApplication : OMI_BaseResource [Write, Description("Specifies the URLs that user tokens are sent to for sign in, or the redirect URIs that OAuth 2.0 authorization codes and access tokens are sent to.")] String ReplyURLs[]; [Write, Description("UPN or ObjectID values of the app's owners.")] String Owners[]; [Write, Description("Represents the set of properties required for configuring Application Proxy for this application. Configuring these properties allows you to publish your on-premises application for secure remote access."), EmbeddedInstance("MSFT_AADApplicationOnPremisesPublishing")] String OnPremisesPublishing; + [Write, Description("Identifier of the associated Application Template.")] String ApplicationTemplateId; [Write, Description("Specify if the Azure AD App should exist or not."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; [Write, Description("Credentials for the Microsoft Graph delegated permissions."), EmbeddedInstance("MSFT_Credential")] string Credential; [Write, Description("Id of the Azure Active Directory application to authenticate with.")] String ApplicationId; diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADApplication.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADApplication.Tests.ps1 index 55637d70cd..2a713b43f4 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADApplication.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADApplication.Tests.ps1 @@ -90,14 +90,14 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Credential = $Credential } - Mock -CommandName Get-MgApplication -MockWith { + Mock -CommandName Get-MgBetaApplication -MockWith { return $null } } It 'Should return values from the get method' { (Get-TargetResource @testParams).Ensure | Should -Be 'Absent' - Should -Invoke -CommandName 'Get-MgApplication' -Exactly 1 + Should -Invoke -CommandName 'Get-MgBetaApplication' -Exactly 1 } It 'Should return false from the test method' { Test-TargetResource @testParams | Should -Be $false @@ -126,7 +126,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Credential = $Credential } - Mock -CommandName Get-MgApplication -MockWith { + Mock -CommandName Get-MgBetaApplication -MockWith { $AADApp = New-Object PSCustomObject $AADApp | Add-Member -MemberType NoteProperty -Name DisplayName -Value 'App1' $AADApp | Add-Member -MemberType NoteProperty -Name Id -Value '5dcb2237-c61b-4258-9c85-eae2aaeba9d6' @@ -147,7 +147,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { It 'Should return values from the get method' { (Get-TargetResource @testParams).Ensure | Should -Be 'Present' - Should -Invoke -CommandName 'Get-MgApplication' -Exactly 2 + Should -Invoke -CommandName 'Get-MgBetaApplication' -Exactly 3 } It 'Should return false from the test method' { @@ -248,25 +248,12 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { PermissionIds = @('12345-12345-12345-12345-12345') } -ClientOnly ) - + } -ClientOnly Ensure = 'Present' Credential = $Credential } - Mock -CommandName Get-MgBetaApplication -MockWith { - $AADApp = New-Object PSCustomObject - $AADApp | Add-Member -MemberType NoteProperty -Name DisplayName -Value 'App1' - $AADApp | Add-Member -MemberType NoteProperty -Name Id -Value '5dcb2237-c61b-4258-9c85-eae2aaeba9d6' - $AADApp | Add-Member -MemberType NoteProperty -Name AppId -Value '5dcb2237-c61b-4258-9c85-eae2aaeba9d6' - $AADApp | Add-Member -MemberType NoteProperty -Name AuthenticationBehaviors -Value @{ - blockAzureADGraphAccess = $false - removeUnverifiedEmailClaim = $true - requireClientServicePrincipal = $false - } - return $AADApp - } - Mock -CommandName Get-MgApplication -MockWith { $AADApp = New-Object PSCustomObject $AADApp | Add-Member -MemberType NoteProperty -Name DisplayName -Value 'App1' $AADApp | Add-Member -MemberType NoteProperty -Name Id -Value '5dcb2237-c61b-4258-9c85-eae2aaeba9d6' @@ -349,13 +336,18 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { $AADApp | Add-Member -MemberType NoteProperty -Name IdentifierUris -Value 'https://app.contoso.com' $AADApp | Add-Member -MemberType NoteProperty -Name Oauth2RequirePostResponse -Value $false $AADApp | Add-Member -MemberType NoteProperty -Name PublicClient -Value $false + $AADApp | Add-Member -MemberType NoteProperty -Name AuthenticationBehaviors -Value @{ + blockAzureADGraphAccess = $false + removeUnverifiedEmailClaim = $true + requireClientServicePrincipal = $false + } return $AADApp } } It 'Should return Values from the get method' { Get-TargetResource @testParams - Should -Invoke -CommandName 'Get-MgApplication' -Exactly 2 + Should -Invoke -CommandName 'Get-MgBetaApplication' -Exactly 3 } It 'Should return true from the test method' { @@ -380,7 +372,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Credential = $Credential } - Mock -CommandName Get-MgApplication -MockWith { + Mock -CommandName Get-MgBetaApplication -MockWith { $AADApp = New-Object PSCustomObject $AADApp | Add-Member -MemberType NoteProperty -Name DisplayName -Value 'App1' $AADApp | Add-Member -MemberType NoteProperty -Name Id -Value '5dcb2237-c61b-4258-9c85-eae2aaeba9d6' @@ -400,7 +392,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { It 'Should return values from the get method' { Get-TargetResource @testParams - Should -Invoke -CommandName 'Get-MgApplication' -Exactly 2 + Should -Invoke -CommandName 'Get-MgBetaApplication' -Exactly 3 } It 'Should return false from the test method' { @@ -434,28 +426,20 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Credential = $Credential } - Mock -CommandName Get-MgApplication -MockWith { - return $null - } - Mock -CommandName Get-MgBetaApplication -MockWith { - return @{ - id = '12345-12345-12345-12345-12345' - appId = '12345-12345-12345-12345-12345' - DisplayName = 'App1' - AuthenticationBehaviours = @{ - blockAzureADGraphAccess = $false - removeUnverifiedEmailClaim = $true - requireClientServicePrincipal = $false + return @( + @{ + id = '12345-12345-12345-12345-12345' + appId = '12345-12345-12345-12345-12345' + DisplayName = 'App1' } - - } + ) } } It 'Should return values from the get method' { Get-TargetResource @testParams - Should -Invoke -CommandName 'Get-MgApplication' -Exactly 1 + Should -Invoke -CommandName 'Get-MgBetaApplication' -Exactly 3 } It 'Should return false from the test method' { @@ -464,7 +448,6 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { It 'Should call the new method' { Set-TargetResource @testParams - Should -Invoke -CommandName 'New-MgApplication' -Exactly 1 Should -Invoke -CommandName 'Update-MgBetaApplication' -Exactly 1 } @@ -505,14 +488,14 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Credential = $Credential } - Mock -CommandName Get-MgApplication -MockWith { + Mock -CommandName Get-MgBetaApplication -MockWith { return $null } } It 'Should return values from the get method' { Get-TargetResource @testParams - Should -Invoke -CommandName 'Get-MgApplication' -Exactly 1 + Should -Invoke -CommandName 'Get-MgBetaApplication' -Exactly 1 } It 'Should return false from the test method' { @@ -533,7 +516,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Credential = $Credential } - Mock -CommandName Get-MgApplication -MockWith { + Mock -CommandName Get-MgBetaApplication -MockWith { $AADApp = New-Object PSCustomObject $AADApp | Add-Member -MemberType NoteProperty -Name DisplayName -Value 'App1' $AADApp | Add-Member -MemberType NoteProperty -Name Id -Value '5dcb2237-c61b-4258-9c85-eae2aaeba9d6'