diff --git a/docs/deployment-guides/command-line-tools.md b/docs/deployment-guides/command-line-tools.md index 1bcb690b7..e9789170a 100644 --- a/docs/deployment-guides/command-line-tools.md +++ b/docs/deployment-guides/command-line-tools.md @@ -32,7 +32,7 @@ The following prerequisites are required on the target Azure subscription(s): ### Decide on a Resource Prefix -Resource Groups and resource names are derived from the required parameter `resourcePrefix`. Pick a unqiue resource prefix that is 3-6 alphanumeric characters in length without whitespaces. +Resource Groups and resource names are derived from the required parameter `resourcePrefix`. Pick a unqiue resource prefix that is 1-6 alphanumeric characters in length without whitespaces. ### One Subscription or Multiple diff --git a/src/bicep/add-ons/azure-virtual-desktop/artifacts/Disable-Autoscale.ps1 b/src/bicep/add-ons/azure-virtual-desktop/artifacts/Disable-Autoscale.ps1 new file mode 100644 index 000000000..2b57637bf --- /dev/null +++ b/src/bicep/add-ons/azure-virtual-desktop/artifacts/Disable-Autoscale.ps1 @@ -0,0 +1,41 @@ +Param( + [string]$HostPoolResourceId, + [string]$ResourceGroupName, + [string]$ResourceManagerUri, + [string]$ScalingPlanName, + [string]$SubscriptionId, + [string]$UserAssignedIdentityClientId +) + +$ErrorActionPreference = 'Stop' +$WarningPreference = 'SilentlyContinue' + +# Fix the resource manager URI since only AzureCloud contains a trailing slash +$ResourceManagerUriFixed = if($ResourceManagerUri[-1] -eq '/'){$ResourceManagerUri} else {$ResourceManagerUri + '/'} + +# Get an access token for Azure resources +$AzureManagementAccessToken = (Invoke-RestMethod ` + -Headers @{Metadata="true"} ` + -Uri $('http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' + $ResourceManagerUriFixed + '&client_id=' + $UserAssignedIdentityClientId)).access_token + +# Set header for Azure Management API +$AzureManagementHeader = @{ + 'Content-Type'='application/json' + 'Authorization'='Bearer ' + $AzureManagementAccessToken +} + +# Check if the scaling plan exists: https://learn.microsoft.com/rest/api/desktopvirtualization/scaling-plans/list-by-resource-group +$ScalingPlanExists = (Invoke-RestMethod ` + -Headers $AzureManagementHeader ` + -Method 'GET' ` + -Uri $($ResourceManagerUriFixed + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $ResourceGroupName + '/providers/Microsoft.DesktopVirtualization/scalingPlans?api-version=2023-09-05')).value | Where-Object {$_.name -eq $ScalingPlanName} + +# Disable autoscale for the host pool: https://learn.microsoft.com/rest/api/desktopvirtualization/scaling-plans/update +if ($ScalingPlanExists) +{ + Invoke-RestMethod ` + -Body (@{properties = @{hostPoolReferences = @(@{hostPoolArmPath = $HostPoolResourceId; scalingPlanEnabled = $false})}} | ConvertTo-Json -Depth 3) ` + -Headers $AzureManagementHeader ` + -Method 'PATCH' ` + -Uri $($ResourceManagerUriFixed + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $ResourceGroupName + '/providers/Microsoft.DesktopVirtualization/scalingPlans/' + $ScalingPlanName + '?api-version=2023-09-05') | Out-Null +} \ No newline at end of file diff --git a/src/bicep/add-ons/azure-virtual-desktop/artifacts/Set-AzureMarketplaceTerms.ps1 b/src/bicep/add-ons/azure-virtual-desktop/artifacts/Set-AzureMarketplaceTerms.ps1 index 0c3873cbd..dbdf82f11 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/artifacts/Set-AzureMarketplaceTerms.ps1 +++ b/src/bicep/add-ons/azure-virtual-desktop/artifacts/Set-AzureMarketplaceTerms.ps1 @@ -2,7 +2,9 @@ param ( [string]$ImageOffer, [string]$ImagePublisher, [string]$ImageSku, - [string]$ResourceManagerUri + [string]$ResourceManagerUri, + [string]$SubscriptionId, + [string]$UserAssignedIdentityClientId ) # Fix the resource manager URI since only AzureCloud contains a trailing slash diff --git a/src/bicep/add-ons/azure-virtual-desktop/artifacts/Set-HostPoolRegistrationToken.ps1 b/src/bicep/add-ons/azure-virtual-desktop/artifacts/Set-HostPoolRegistrationToken.ps1 deleted file mode 100644 index 87e183781..000000000 --- a/src/bicep/add-ons/azure-virtual-desktop/artifacts/Set-HostPoolRegistrationToken.ps1 +++ /dev/null @@ -1,57 +0,0 @@ -Param( - [string]$HostPoolName, - [string]$HostPoolResourceGroupName, - [string]$KeyVaultUri, - [string]$ResourceManagerUri, - [string]$SubscriptionId, - [string]$UserAssignedIdentityClientId -) - -$ErrorActionPreference = 'Stop' -$WarningPreference = 'SilentlyContinue' - -# Fix the resource manager URI since only AzureCloud contains a trailing slash -$ResourceManagerUriFixed = if($ResourceManagerUri[-1] -eq '/'){$ResourceManagerUri} else {$ResourceManagerUri + '/'} - -# Get an access token for Azure resources -$AzureManagementAccessToken = (Invoke-RestMethod ` - -Headers @{Metadata="true"} ` - -Uri $('http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' + $ResourceManagerUriFixed + '&client_id=' + $UserAssignedIdentityClientId)).access_token - -# Set header for Azure Management API -$AzureManagementHeader = @{ - 'Content-Type'='application/json' - 'Authorization'='Bearer ' + $AzureManagementAccessToken -} - -# Use the access token to update the host pool registration token -Invoke-RestMethod ` - -Body (@{properties = @{registrationInfo = @{expirationTime = $(Get-Date).AddMinutes(90); registrationTokenOperation = "Update" }}} | ConvertTo-Json) ` - -Headers $AzureManagementHeader ` - -Method 'PATCH' ` - -Uri $($ResourceManagerUriFixed + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '?api-version=2022-02-10-preview') | Out-Null - -# Use the access token to get the host pool registration token -$HostPoolRegistrationToken = (Invoke-RestMethod ` - -Headers $AzureManagementHeader ` - -Method 'POST' ` - -Uri $($ResourceManagerUriFixed + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '/retrieveRegistrationToken?api-version=2022-02-10-preview')).token - -# Get an access token for the Azure key vault -$KeyVaultAccessToken = (Invoke-RestMethod ` - -Headers @{Metadata="true"} ` - -Method 'GET' ` - -Uri $('http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' + $KeyVaultUri + '&client_id=' + $UserAssignedIdentityClientId)).access_token - -# Set header for Azure Key Vault API -$KeyVaultHeader = @{ - 'Content-Type'='application/json' - 'Authorization'='Bearer ' + $KeyVaultAccessToken -} - -# Create a key vault secret with the host pool registration token -Invoke-RestMethod ` - -Body (@{value = $HostPoolRegistrationToken} | ConvertTo-Json) ` - -Headers $KeyVaultHeader ` - -Method 'PUT' ` - -Uri $($KeyVaultUri + '/secrets/avdHostPoolRegistrationToken?api-version=7.4') | Out-Null \ No newline at end of file diff --git a/src/bicep/add-ons/azure-virtual-desktop/artifacts/scaling-tool/profile.ps1 b/src/bicep/add-ons/azure-virtual-desktop/artifacts/scaling-tool/profile.ps1 deleted file mode 100644 index 33267a961..000000000 --- a/src/bicep/add-ons/azure-virtual-desktop/artifacts/scaling-tool/profile.ps1 +++ /dev/null @@ -1 +0,0 @@ -# Authentication is provided in the script \ No newline at end of file diff --git a/src/bicep/add-ons/azure-virtual-desktop/artifacts/scaling-tool/requirements.psd1 b/src/bicep/add-ons/azure-virtual-desktop/artifacts/scaling-tool/requirements.psd1 deleted file mode 100644 index 6bf9b0f28..000000000 --- a/src/bicep/add-ons/azure-virtual-desktop/artifacts/scaling-tool/requirements.psd1 +++ /dev/null @@ -1,8 +0,0 @@ -# This file enables modules to be automatically managed by the Functions service. -# See https://aka.ms/functionsmanageddependency for additional information. -# -@{ - # For latest supported version, go to 'https://www.powershellgallery.com/packages/Az'. - # To use the Az module in your function app, please uncomment the line below. - # 'Az' = '7.*' -} \ No newline at end of file diff --git a/src/bicep/add-ons/azure-virtual-desktop/artifacts/scaling-tool/run.ps1 b/src/bicep/add-ons/azure-virtual-desktop/artifacts/scaling-tool/run.ps1 deleted file mode 100644 index 48f428d1c..000000000 --- a/src/bicep/add-ons/azure-virtual-desktop/artifacts/scaling-tool/run.ps1 +++ /dev/null @@ -1,759 +0,0 @@ -param($Timer) - -try -{ - [string]$BeginPeakTime = $env:BeginPeakTime - [string]$EndPeakTime = $env:EndPeakTime - [string]$EnvironmentName = $env:EnvironmentName - [string]$HostPoolName = $env:HostPoolName - [string]$HostPoolResourceGroupName = $env:HostPoolResourceGroupName - [int]$LimitSecondsToForceLogOffUser = $env:LimitSecondsToForceLogOffUser - [string]$LogOffMessageBody = $env:LogOffMessageBody - [string]$LogOffMessageTitle = $env:LogOffMessageTitle - [string]$MaintenanceTagName = $env:MaintenanceTagName - [int]$MinimumNumberOfRDSH = $env:MinimumNumberOfRDSH - [string]$ResourceManagerUrl = $env:ResourceManagerUrl - [double]$SessionThresholdPerCPU = $env:SessionThresholdPerCPU - [string]$SubscriptionId = $env:SubscriptionId - [string]$TenantId = $env:TenantId - [string]$TimeDifference = $env:TimeDifference - [string[]]$DesiredRunningStates = @('Available', 'NeedsAssistance') - [string[]]$TimeDiffHrsMin = "$($TimeDifference):0".Split(':') - - - #region Functions - function Get-LocalDateTime - { - return (Get-Date).ToUniversalTime().AddHours($TimeDiffHrsMin[0]).AddMinutes($TimeDiffHrsMin[1]) - } - - function Write-Log - { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $false)] - [switch]$Err, - - [Parameter(Mandatory = $true)] - [string]$HostPoolName, - - [Parameter(Mandatory = $true)] - [string]$Message, - - [Parameter(Mandatory = $false)] - [switch]$Warn - ) - - [string]$MessageTimeStamp = (Get-LocalDateTime).ToString('yyyy-MM-dd HH:mm:ss') - $Message = "[$($MyInvocation.ScriptLineNumber)] [$($HostPoolName)] $Message" - [string]$WriteMessage = "[$($MessageTimeStamp)] $Message" - - if ($Err) - { - Write-Error $WriteMessage - $Message = "ERROR: $Message" - } - elseif ($Warn) - { - Write-Warning $WriteMessage - $Message = "WARN: $Message" - } - else - { - Write-Output $WriteMessage - } - } - - function Set-nVMsToStartOrStop - { - param ( - [Parameter(Mandatory = $true)] - [string]$HostPoolName, - - [Parameter(Mandatory = $false)] - [switch]$InPeakHours, - - [Parameter(Mandatory = $true)] - [int]$MaxUserSessionsPerVM, - - [Parameter(Mandatory = $true)] - [int]$nRunningCores, - - [Parameter(Mandatory = $true)] - [int]$nRunningVMs, - - [Parameter(Mandatory = $true)] - [int]$nUserSessions, - - [Parameter(Mandatory = $true)] - [hashtable]$Res - ) - - # check if need to adjust min num of running session hosts required if the number of user sessions is close to the max allowed by the min num of running session hosts required - [double]$MaxUserSessionsThreshold = 0.9 - [int]$MaxUserSessionsThresholdCapacity = [math]::Floor($MinimumNumberOfRDSH * $MaxUserSessionsPerVM * $MaxUserSessionsThreshold) - if ($nUserSessions -gt $MaxUserSessionsThresholdCapacity) - { - $MinimumNumberOfRDSH = [math]::Ceiling($nUserSessions / ($MaxUserSessionsPerVM * $MaxUserSessionsThreshold)) - Write-Log -HostPoolName $HostPoolName -Message "Number of user sessions is more than $($MaxUserSessionsThreshold * 100) % of the max number of sessions allowed with minimum number of running session hosts required ($MaxUserSessionsThresholdCapacity). Adjusted minimum number of running session hosts required to $MinimumNumberOfRDSH" - } - - # Check if minimum number of session hosts are running - if ($nRunningVMs -lt $MinimumNumberOfRDSH) - { - $res.nVMsToStart = $MinimumNumberOfRDSH - $nRunningVMs - Write-Log -HostPoolName $HostPoolName -Message "Number of running session host is less than minimum required. Need to start $($res.nVMsToStart) VMs" - } - - if ($InPeakHours) - { - [double]$nUserSessionsPerCore = $nUserSessions / $nRunningCores - # In peak hours: check if current capacity is meeting the user demands - if ($nUserSessionsPerCore -gt $SessionThresholdPerCPU) - { - $res.nCoresToStart = [math]::Ceiling(($nUserSessions / $SessionThresholdPerCPU) - $nRunningCores) - Write-Log -HostPoolName $HostPoolName -Message "[In peak hours] Number of user sessions per Core is more than the threshold. Need to start $($res.nCoresToStart) cores" - } - - return - } - - if ($nRunningVMs -gt $MinimumNumberOfRDSH) - { - # Calculate the number of session hosts to stop - $res.nVMsToStop = $nRunningVMs - $MinimumNumberOfRDSH - Write-Log -HostPoolName $HostPoolName -Message "[Off peak hours] Number of running session host is greater than minimum required. Need to stop $($res.nVMsToStop) VMs" - } - } - - function TryUpdateSessionHostDrainMode - { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [bool]$AllowNewSession, - - [Parameter(Mandatory = $true)] - [hashtable]$Header, - - [Parameter(Mandatory = $true)] - [string]$HostPoolName, - - [Parameter(Mandatory = $true)] - [string]$HostPoolResourceGroupName, - - [Parameter(Mandatory = $true)] - [string]$ResourceManagerUrl, - - [Parameter(Mandatory = $true)] - [string]$SessionHostName, - - [Parameter(Mandatory = $true)] - [string]$SubscriptionId - ) - Begin { } - Process - { - Write-Log -HostPoolName $HostPoolName -Message "Update session host '$SessionHostName' to set allow new sessions to $AllowNewSession" - try - { - $Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '/sessionHosts/' + $SessionHostName + '?api-version=2022-02-10-preview' - Invoke-RestMethod -Headers $Header -Body (@{properties = @{allowNewSession = $AllowNewSession}} | ConvertTo-Json) -Method 'Patch' -Uri $Uri | Out-Null - } - catch - { - Write-Log -HostPoolName $HostPoolName -Warn -Message "Failed to update the session host '$SessionHostName' to set allow new sessions to $($AllowNewSession): $($PSItem | Format-List -Force | Out-String)" - } - } - End { } - } - - function TryForceLogOffUser - { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [hashtable]$Header, - - [Parameter(Mandatory = $true)] - [string]$HostPoolName, - - [Parameter(Mandatory = $true)] - [string]$HostPoolResourceGroupName, - - [Parameter(Mandatory = $true)] - [string]$ResourceManagerUrl, - - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - $Session, - - [Parameter(Mandatory = $true)] - [string]$SubscriptionId - ) - Begin { } - Process - { - [string[]]$Toks = $Session.Name.Split('/') - [string]$SessionHostName = $Toks[1] - [string]$SessionID = $Toks[-1] - [string]$User = $Session.ActiveDirectoryUserName - - try - { - Write-Log -HostPoolName $HostPoolName -Message "Force log off user: '$User', session ID: $SessionID" - - $Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '/sessionHosts/' + $SessionHostName + '/userSessions/' + $SessionID + '?api-version=2022-02-10-preview&force=True' - Invoke-RestMethod -Headers $Header -Method 'Delete' -Uri $Uri | Out-Null - } - catch - { - Write-Log -HostPoolName $HostPoolName -Warn -Message "Failed to force log off user: '$User', session ID: $SessionID $($PSItem | Format-List -Force | Out-String)" - } - } - End { } - } - - function TryResetSessionHostDrainModeAndUserSessions - { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [hashtable]$Header, - - [Parameter(Mandatory = $true)] - [string]$HostPoolName, - - [Parameter(Mandatory = $true)] - [string]$HostPoolResourceGroupName, - - [Parameter(Mandatory = $true)] - [string]$ResourceManagerUrl, - - [Parameter(Mandatory = $true)] - [string]$SessionHostName, - - [Parameter(Mandatory = $true)] - [int]$SessionHostSessions, - - [Parameter(Mandatory = $true)] - [string]$SubscriptionId - ) - Begin { } - Process - { - TryUpdateSessionHostDrainMode -AllowNewSession $true -Header $Header -HostPoolName $HostPoolName -HostPoolResourceGroupName $HostPoolResourceGroupName -ResourceManagerUrl $ResourceManagerUrl -SessionHostName $SessionHostName -SubscriptionId $SubscriptionId - - if ($SessionHostSessions -eq 0) - { - return - } - - Write-Log -HostPoolName $HostPoolName -Warn -Message "Session host '$SessionHostName' still has $SessionHostSessions) sessions left behind in broker DB" - - Write-Log -HostPoolName $HostPoolName -Message "Get all user sessions from session host '$SessionHostName'" - try - { - $Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '/sessionHosts/' + $SessionHostName + '/userSessions?api-version=2022-02-10-preview' - $UserSessions = Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri - } - catch - { - Write-Log -HostPoolName $HostPoolName -Warn -Message "Failed to retrieve user sessions of session host '$SessionHostName': $($PSItem | Format-List -Force | Out-String)" - return - } - - Write-Log -HostPoolName $HostPoolName -Message "Force log off $($UserSessions.Count) users on session host: '$SessionHostName'" - $UserSessions | TryForceLogOffUser -Header $Header -HostPoolName $HostPoolName -HostPoolResourceGroupName $HostPoolResourceGroupName -ResourceManagerUrl $ResourceManagerUrl -SubscriptionId $SubscriptionId - } - End { } - } - #endregion Functions - - - # Note: https://stackoverflow.com/questions/41674518/powershell-setting-security-protocol-to-tls-1-2 - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - - - #region Azure Authentication - $AccessToken = $null - try - { - $TokenAuthURI = $env:IDENTITY_ENDPOINT + '?resource=' + $ResourceManagerUrl + '&api-version=2019-08-01' - $TokenResponse = Invoke-RestMethod -Method Get -Headers @{"X-IDENTITY-HEADER"="$env:IDENTITY_HEADER"} -Uri $TokenAuthURI - $AccessToken = $TokenResponse.access_token - $Header = @{ - 'Content-Type'='application/json' - 'Authorization'='Bearer ' + $AccessToken - } - } - catch - { - throw [System.Exception]::new('Failed to authenticate Azure with application ID, tenant ID, subscription ID', $PSItem.Exception) - } - Write-Log -HostPoolName $HostPoolName -Message "Successfully authenticated with Azure using a managed identity" - #endregion Azure Authentication - - - #region validate host pool, validate / update HostPool load balancer type, ensure there is at least 1 session host, get num of user sessions - # Validate and get host pool info - $HostPool = $null - try - { - Write-Log -HostPoolName $HostPoolName -Message "Get host pool information" - $Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '?api-version=2022-02-10-preview' - $HostPool = Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri - - if (!$HostPool) - { - throw $HostPool - } - } - catch - { - throw [System.Exception]::new("Failed to get host pool info of '$HostPoolName' in resource group '$HostPoolResourceGroupName'. Ensure that you have entered the correct values", $PSItem.Exception) - } - - # Ensure HostPool load balancer type is not persistent - if ($HostPool.properties.loadBalancerType -ieq 'Persistent') - { - throw "HostPool '$HostPoolName' is configured with 'Persistent' load balancer type. Scaling tool only supports these load balancer types: BreadthFirst, DepthFirst" - } - - Write-Log -HostPoolName $HostPoolName -Message 'Get session hosts' - $Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '/sessionHosts?api-version=2022-02-10-preview' - $SessionHosts = (Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri).value - - if (!$SessionHosts) - { - Write-Log -HostPoolName $HostPoolName -Message "There are no session hosts in the host pool '$HostPoolName'. Ensure that hostpool has session hosts" - Write-Log -HostPoolName $HostPoolName -Message 'End' - return - } - - Write-Log -HostPoolName $HostPoolName -Message 'Get number of user sessions in host pool' - $Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '/userSessions?api-version=2022-02-10-preview' - [int]$nUserSessions = (Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri).value.Count - - # Set up breadth 1st load balacing type - # Note: breadth 1st is enforced on AND off peak hours to simplify the things with scaling in the start/end of peak hours - if (!$SkipUpdateLoadBalancerType -and $HostPool.properties.loadBalancerType -ine 'BreadthFirst') - { - Write-Log -HostPoolName $HostPoolName -Message "Update HostPool with 'BreadthFirst' load balancer type (current: '$($HostPool.properties.loadBalancerType)')" - - $Body = @{ - properties = @{ - loadBalancerType = 'BreadthFirst' - } - } - $Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '?api-version=2022-02-10-preview' - $HostPool = Invoke-RestMethod -Headers $Header -Body $($Body | ConvertTo-Json) -Method 'Patch' -Uri $Uri - } - Write-Log -HostPoolName $HostPoolName -Message "Number of session hosts in the HostPool: $($SessionHosts.Count)" - #endregion - - - # region Peak Hours # - # Convert local time, begin peak time & end peak time from UTC to local time - $CurrentDateTime = Get-LocalDateTime - $BeginPeakDateTime = [datetime]::Parse($CurrentDateTime.ToShortDateString() + ' ' + $BeginPeakTime) - $EndPeakDateTime = [datetime]::Parse($CurrentDateTime.ToShortDateString() + ' ' + $EndPeakTime) - - # Adjust peak times to make sure begin peak time is always before end peak time - if ($EndPeakDateTime -lt $BeginPeakDateTime) - { - if ($CurrentDateTime -lt $EndPeakDateTime) - { - $BeginPeakDateTime = $BeginPeakDateTime.AddDays(-1) - } - else - { - $EndPeakDateTime = $EndPeakDateTime.AddDays(1) - } - } - - Write-Log -HostPoolName $HostPoolName -Message "Using current time: $($CurrentDateTime.ToString('yyyy-MM-dd HH:mm:ss')), begin peak time: $($BeginPeakDateTime.ToString('yyyy-MM-dd HH:mm:ss')), end peak time: $($EndPeakDateTime.ToString('yyyy-MM-dd HH:mm:ss'))" - - [bool]$InPeakHours = ($BeginPeakDateTime -le $CurrentDateTime -and $CurrentDateTime -le $EndPeakDateTime) - if ($InPeakHours) - { - Write-Log -HostPoolName $HostPoolName -Message 'In peak hours' - } - else - { - Write-Log -HostPoolName $HostPoolName -Message 'Off peak hours' - } - # endregion Peak Hours # - - - #region get session hosts, VMs & user sessions info and compute workload - # Note: session host is considered "running" if its running AND is in desired states AND allowing new sessions - # Number of session hosts that are running, are in desired states and allowing new sessions - [int]$nRunningVMs = 0 - # Number of cores that are running, are in desired states and allowing new sessions - [int]$nRunningCores = 0 - # Array that contains all the virtual machine objects that are session hosts except the ones that are tagged for maintenance - $VMs = @() - # Object that contains the number of cores for each VM size SKU - $VMSizeCores = @{} - # Number of user sessions reported by each session host that is running, is in desired state and allowing new sessions - [int]$nUserSessionsFromAllRunningVMs = 0 - - # Populate all session hosts objects - foreach ($SessionHost in $SessionHosts) - { - [string]$VirtualMachineResourceId = $SessionHost.properties.resourceId - [string]$VirtualMachineName = $VirtualMachineResourceId.Split('/')[8] - [string]$VirtualMachineResourceGroupName = $VirtualMachineResourceId.Split('/')[4] - $Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $VirtualMachineResourceGroupName + '/providers/Microsoft.Compute/virtualMachines/' + $VirtualMachineName + '?api-version=2024-03-01&$expand=instanceView' - $VirtualMachine = Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri - - # Throw an error if the virtual machine for the session host does not exist - if ($VirtualMachine.error) - { - throw "The virtual machine for session host '$VirtualMachineName' does not exist" - } - # Ignore session hosts tagged for maintenance or missing virtual machine - elseif($VirtualMachine.tags.Keys -contains $MaintenanceTagName) - { - Write-Log -HostPoolName $HostPoolName -Message "VM '$VirtualMachineName' is in maintenance and will be ignored" - continue - } - else - { - $VMs += $VirtualMachine - } - - $PowerState = $VirtualMachine.properties.instanceView.statuses[1].displayStatus - Write-Log -HostPoolName $HostPoolName -Message "Session host: '$($VirtualMachineName)', power state: '$PowerState', status: '$($SessionHost.properties.status)', update state: '$($SessionHost.properties.updateState)', sessions: $($SessionHost.properties.sessions), allow new session: $($SessionHost.properties.allowNewSession)" - - # Get the number of cores for VM size SKU - if (!$VMSizeCores.ContainsKey($VirtualMachine.properties.hardwareProfile.vmSize)) - { - Write-Log -HostPoolName $HostPoolName -Message "Get VM sizes in $($VirtualMachine.location)" - - $Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/providers/Microsoft.Compute/locations/' + $VirtualMachine.location + '/vmSizes?api-version=2024-03-01' - $VMSizes = (Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri).value - - foreach ($VMSize in $VMSizes) - { - if (!$VMSizeCores.ContainsKey($VMSize.name)) - { - $VMSizeCores.Add($VMSize.name, $VMSize.numberOfCores) - } - } - } - - if ($PowerState -ieq 'VM running') - { - if ($SessionHost.properties.status -notin $DesiredRunningStates) - { - Write-Log -HostPoolName $HostPoolName -Warn -Message 'VM is in running state but session host is not and so it will be ignored (this could be because the VM was just started and has not connected to broker yet)' - } - if (!$SessionHost.properties.allowNewSession) - { - Write-Log -HostPoolName $HostPoolName -Warn -Message 'VM is in running state but session host is not allowing new sessions and so it will be ignored' - } - - if ($SessionHost.properties.status -in $DesiredRunningStates -and $SessionHost.properties.allowNewSession) - { - ++$nRunningVMs - $nRunningCores += $VMSizeCores[$VirtualMachine.properties.hardwareProfile.vmSize] - $nUserSessionsFromAllRunningVMs += $SessionHost.properties.sessions - } - } - else - { - if ($SessionHost.properties.status -in $DesiredRunningStates) - { - Write-Log -HostPoolName $HostPoolName -Warn -Message "VM is not in running state but session host is (this could be because the VM was just stopped and broker doesn't know that yet)" - } - } - } - - if ($nUserSessionsFromAllRunningVMs -ne $nUserSessions) - { - Write-Log -HostPoolName $HostPoolName -Warn -Message "Sum of user sessions reported by every running session host ($nUserSessionsFromAllRunningVMs) is not equal to the total number of user sessions reported by the host pool ($nUserSessions)" - } - - - if (!$nRunningCores) - { - $nRunningCores = 1 - } - - Write-Log -HostPoolName $HostPoolName -Message "Number of running session hosts: $nRunningVMs of total $($VMs.Count)" - Write-Log -HostPoolName $HostPoolName -Message "Number of user sessions: $nUserSessions of total allowed $($nRunningVMs * $HostPool.properties.maxSessionLimit)" - Write-Log -HostPoolName $HostPoolName -Message "Number of user sessions per Core: $($nUserSessions / $nRunningCores), threshold: $SessionThresholdPerCPU" - Write-Log -HostPoolName $HostPoolName -Message "Minimum number of running session hosts required: $MinimumNumberOfRDSH" - - # Check if minimum num of running session hosts required is higher than max allowed - if ($VMs.Count -le $MinimumNumberOfRDSH) - { - Write-Log -HostPoolName $HostPoolName -Warn -Message 'Minimum number of RDSH is set higher than or equal to total number of session hosts' - } - #endregion - - - #region determine number of session hosts to start/stop if any - # Now that we have all the info about the session hosts & their usage, figure how many session hosts to start/stop depending on in/off peak hours and the demand [Ops = operations to perform] - $Ops = @{ - nVMsToStart = 0 - nCoresToStart = 0 - nVMsToStop = 0 - } - - Set-nVMsToStartOrStop -HostPoolName $HostPoolName -nRunningVMs $nRunningVMs -nRunningCores $nRunningCores -nUserSessions $nUserSessions -MaxUserSessionsPerVM $HostPool.properties.maxSessionLimit -InPeakHours:$InPeakHours -Res $Ops - #endregion - - - #region start any session hosts if need to - # Check if we have any session hosts to start - if ($Ops.nVMsToStart -or $Ops.nCoresToStart) - { - if ($nRunningVMs -eq $VMs.Count) - { - Write-Log -HostPoolName $HostPoolName -Message 'All session hosts are running' - Write-Log -HostPoolName $HostPoolName -Message 'End' - return - } - - # Object that contains names of session hosts that will be started - # $StartSessionHostFullNames = @{ } - # Array that contains jobs of starting the session hosts - [array]$StartedVMs = @() - - Write-Log -HostPoolName $HostPoolName -Message 'Find session hosts that are stopped and allowing new sessions' - foreach ($SessionHost in $SessionHosts) - { - $VM = $VMs | Where-Object { $_.id -ieq $SessionHost.properties.resourceId } - if (!$Ops.nVMsToStart -and !$Ops.nCoresToStart) - { - # Done with starting session hosts that needed to be - break - } - if ($VM.properties.instanceView.statuses[1].displayStatus -ieq 'VM running') - { - continue - } - if ($SessionHost.properties.updateState -ine 'Succeeded') - { - Write-Log -HostPoolName $HostPoolName -Warn -Message "Session host '$($VM.name)' may not be healthy" - } - - if (!$SessionHost.properties.allowNewSession) - { - Write-Log -HostPoolName $HostPoolName -Warn -Message "Session host '$($VM.name)' is not allowing new sessions and so it will not be started" - continue - } - - Write-Log -HostPoolName $HostPoolName -Message "Start session host '$($VM.name)'" - - $Uri = $ResourceManagerUrl + $VM.id.TrimStart('/') + '/start?api-version=2023-09-01' - Invoke-RestMethod -Headers $Header -Method 'Post' -Uri $Uri | Out-Null - $StartedVMs += $VM - - --$Ops.nVMsToStart - if ($Ops.nVMsToStart -lt 0) - { - $Ops.nVMsToStart = 0 - } - - $Ops.nCoresToStart -= $VMSizeCores[$VM.properties.hardwareProfile.vmSize] - if ($Ops.nCoresToStart -lt 0) - { - $Ops.nCoresToStart = 0 - } - } - - # Check if there were enough number of session hosts to start - if ($Ops.nVMsToStart -or $Ops.nCoresToStart) - { - Write-Log -HostPoolName $HostPoolName -Warn -Message "Not enough session hosts to start. Still need to start maximum of either $($Ops.nVMsToStart) VMs or $($Ops.nCoresToStart) cores" - } - - # Wait for session hosts to start - while($StartedVMs.Count -gt 0) - { - foreach($StartedVM in $StartedVMs) - { - $Uri = $ResourceManagerUrl + $StartedVM.id.TrimStart('/') + '?api-version=2024-03-01' - $VMAgentStatus = (Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri).properties.instanceView.vmAgent - if ($VMAgentStatus) - { - Write-Log -HostPoolName $HostPoolName -Message "Session host '$($StartedVM.name)' is running" - $StartedVMs = $StartedVMs -ne $StartedVM - } - } - Start-Sleep -Seconds 30 - } - - Write-Log -HostPoolName $HostPoolName -Message 'All session hosts have started' - Write-Log -HostPoolName $HostPoolName -Message 'End' - return - } - #endregion - - - #region stop any session hosts if need to - if (!$Ops.nVMsToStop) - { - Write-Log -HostPoolName $HostPoolName -Message 'No need to start/stop any session hosts' - Write-Log -HostPoolName $HostPoolName -Message 'End' - return - } - - # Object that contains names of session hosts that will be stopped - $VMsToStop = @() - [array]$VMsToStopAfterLogOffTimeOut = @() - - Write-Log -HostPoolName $HostPoolName -Message 'Find session hosts that are running and allowing new sessions, sort them by number of user sessions' - foreach ($SessionHost in ($SessionHosts | Where-Object { $_.properties.allowNewSession } | Sort-Object { $_.properties.sessions })) - { - $VM = $VMs | Where-Object { $_.id -ieq $SessionHost.properties.resourceId } - if ($VM.properties.instanceView.statuses[1].displayStatus -ieq 'VM running') - { - if (!$Ops.nVMsToStop) - { - # Done with stopping session hosts that needed to be - break - } - - if ($SessionHost.properties.sessions -gt 0 -and !$LimitSecondsToForceLogOffUser) - { - Write-Log -HostPoolName $HostPoolName -Warn -Message "Session host '$($VM.name)' has $($SessionHost.properties.sessions) sessions but limit seconds to force log off user is set to 0, so will not stop any more session hosts (https://aka.ms/wvdscale#how-the-scaling-tool-works)" - # Note: why break ? Because the list this loop iterates through is sorted by number of sessions, if it hits this, the rest of items in the loop will also hit this - break - } - - TryUpdateSessionHostDrainMode -AllowNewSession $false -Header $Header -HostPoolName $HostPoolName -HostPoolResourceGroupName $HostPoolResourceGroupName -ResourceManagerUrl $ResourceManagerUrl -SessionHostName $VM.name -SubscriptionId $SubscriptionId - - # Note: check if there were new user sessions since session host info was 1st fetched - if ($SessionHost.properties.sessions -gt 0 -and !$LimitSecondsToForceLogOffUser) - { - Write-Log -HostPoolName $HostPoolName -Warn -Message "Session host '$($VM.name)' has $($SessionHost.properties.sessions) sessions but limit seconds to force log off user is set to 0, so will not stop any more session hosts (https://aka.ms/wvdscale#how-the-scaling-tool-works)" - TryUpdateSessionHostDrainMode -AllowNewSession $true -Header $Header -HostPoolName $HostPoolName -HostPoolResourceGroupName $HostPoolResourceGroupName -ResourceManagerUrl $ResourceManagerUrl -SessionHostName $VM.name -SubscriptionId $SubscriptionId - continue - } - - if ($SessionHost.properties.sessions -gt 0) - { - Write-Log -HostPoolName $HostPoolName -Message "Get all user sessions from session host '$($VM.name)'" - try - { - $Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '/sessionHosts/' + $VM.name + '/userSessions?api-version=2022-02-10-preview' - $UserSessions = Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri - } - catch - { - Write-Log -HostPoolName $HostPoolName -Warn -Message "Failed to retrieve user sessions of session host '$($VM.name)': $($PSItem | Format-List -Force | Out-String)" - } - - Write-Log -HostPoolName $HostPoolName -Message "Send log off message to active user sessions on session host: '$($VM.name)'" - foreach ($UserSession in $UserSessions) - { - if($UserSession.properties.sessionState -ine 'Active') - { - continue - } - - [string]$SessionID = $UserSession.name.Split('/')[-1] - [string]$User = $UserSession.properties.activeDirectoryUserName - - try - { - Write-Log -HostPoolName $HostPoolName -Message "Send a log off message to user: '$User', session ID: $SessionID" - - $Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '/sessionHosts/' + $VM.name + '/userSessions/' + $SessionID + '/sendMessage?api-version=2022-02-10-preview' - Invoke-RestMethod -Headers $Header -Method 'Post' -Uri $Uri -Body (@{ 'messageTitle' = $LogOffMessageTitle; 'messageBody' = "$LogOffMessageBody You will be logged off in $LimitSecondsToForceLogOffUser seconds" } | ConvertTo-Json) | Out-Null - } - catch - { - Write-Log -HostPoolName $HostPoolName -Warn -Message "Failed to send a log off message to user: '$User', session ID: $SessionID $($PSItem | Format-List -Force | Out-String)" - } - } - $VMsToStopAfterLogOffTimeOut += $VM - } - else - { - Write-Log -HostPoolName $HostPoolName -Message "Stop session host '$($VM.name)'" - $Uri = $ResourceManagerUrl + $VM.id.TrimStart('/') + '/deallocate?api-version=2023-09-01' - Invoke-RestMethod -Headers $Header -Method 'Post' -Uri $Uri | Out-Null - $VMsToStop += $VM - } - - --$Ops.nVMsToStop - if ($Ops.nVMsToStop -lt 0) { - $Ops.nVMsToStop = 0 - } - } - } - - if ($VMsToStopAfterLogOffTimeOut) - { - Write-Log -HostPoolName $HostPoolName -Message "Wait $LimitSecondsToForceLogOffUser seconds for users to log off" - - Start-Sleep -Seconds $LimitSecondsToForceLogOffUser - - Write-Log -HostPoolName $HostPoolName -Message "Force log off users and stop remaining $($VMsToStopAfterLogOffTimeOut.Count) session hosts" - foreach ($VM in $VMsToStopAfterLogOffTimeOut) - { - $SessionHost = $SessionHosts | Where-Object { $_.properties.resourceId -ieq $VM.id } - Write-Log -HostPoolName $HostPoolName -Message "Force log off $($SessionHost.sessions) users on session host: '$($VM.name)'" - $VM.UserSessions | TryForceLogOffUser -Header $Header -HostPoolName $HostPoolName -HostPoolResourceGroupName $HostPoolResourceGroupName -ResourceManagerUrl $ResourceManagerUrl -SubscriptionId $SubscriptionId - - Write-Log -HostPoolName $HostPoolName -Message "Stop session host '$($VM.name)'" - $Uri = $ResourceManagerUrl + $VM.id.TrimStart('/') + '/deallocate?api-version=2023-09-01' - Invoke-RestMethod -Headers $Header -Method 'Post' -Uri $Uri | Out-Null - $VMsToStop += $VM - } - } - - # Check if there were enough number of session hosts to stop - if ($Ops.nVMsToStop) - { - Write-Log -HostPoolName $HostPoolName -Warn -Message "Not enough session hosts to stop. Still need to stop $($Ops.nVMsToStop) VMs" - } - - # Wait for the session hosts to stop / deallocate - Write-Log -HostPoolName $HostPoolName -Message "Wait for session hosts to stop / deallocate" - while($VMsToStop.Count -gt 0) - { - foreach($VMToStop in $VMsToStop) - { - $Uri = $ResourceManagerUrl + $VMToStop.id.TrimStart('/') + '?$expand=instanceView&api-version=2024-03-01' - $VMPowerState = (Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri).properties.instanceView.statuses[1].displayStatus - if ($VMPowerState -eq 'VM deallocated') - { - Write-Log -HostPoolName $HostPoolName -Message "Session host '$($VMToStop.name)' is stopping" - $SessionHost = $SessionHosts | Where-Object { $_.properties.resourceId -ieq $VMToStop.id } - TryResetSessionHostDrainModeAndUserSessions -Header $Header -HostPoolName $HostPoolName -HostPoolResourceGroupName $HostPoolResourceGroupName -ResourceManagerUrl $ResourceManagerUrl -SessionHostName $VMToStop.name -SessionHostSessions $SessionHost.properties.sessions -SubscriptionId $SubscriptionId - $VMsToStop = $VMsToStop -ne $VMToStop - } - } - Start-Sleep -Seconds 30 - } - - Write-Log -HostPoolName $HostPoolName -Message 'All required session hosts have stopped.' - Write-Log -HostPoolName $HostPoolName -Message 'End' - return - #endregion -} -catch -{ - $ErrContainer = $PSItem - # $ErrContainer = $_ - - [string]$ErrMsg = $ErrContainer | Format-List -Force | Out-String - $ErrMsg += "Version: $Version`n" - - if (Get-Command 'Write-Log' -ErrorAction:SilentlyContinue) - { - Write-Log -HostPoolName $HostPoolName -Err -Message $ErrMsg -ErrorAction:Continue - } - else - { - Write-Error $ErrMsg -ErrorAction:Continue - } - - throw [System.Exception]::new($ErrMsg, $ErrContainer.Exception) -} \ No newline at end of file diff --git a/src/bicep/add-ons/azure-virtual-desktop/docs/features.md b/src/bicep/add-ons/azure-virtual-desktop/docs/features.md index e8e997b56..7c9b52175 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/docs/features.md +++ b/src/bicep/add-ons/azure-virtual-desktop/docs/features.md @@ -5,13 +5,13 @@ ## Features - [**Auto Increase Premium File Share Quota**](./features/autoIncreasePremiumFileShareQuota.md#auto-increase-premium-file-share-quota) +- [**Autoscale**](./features/autoscale.md#autoscale) - [**Backups**](./features/backups.md#backups) - [**Drain Mode**](./features/drainMode.md#drain-mode) - [**FSLogix**](./features/fslogix.md#fslogix) - [**GPU Drivers & Settings**](./features/gpu.md#gpu-drivers--settings) - [**High Availability**](./features/highAvailability.md#high-availability) - [**Monitoring**](./features/monitoring.md#monitoring) -- [**Scaling Tool**](./features/scalingTool.md#scaling-tool) - [**Server-Side Encryption with Customer Managed Keys**](./features/serverSideEncryption.md#server-side-encryption) - [**SMB Multichannel**](./features/smbMultiChannel.md#smb-multichannel) - [**Start VM On Connect**](./features/startVmOnConnect.md#start-vm-on-connect) diff --git a/src/bicep/add-ons/azure-virtual-desktop/docs/features/autoIncreasePremiumFileShareQuota.md b/src/bicep/add-ons/azure-virtual-desktop/docs/features/autoIncreasePremiumFileShareQuota.md index 11ef532bc..619931fcd 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/docs/features/autoIncreasePremiumFileShareQuota.md +++ b/src/bicep/add-ons/azure-virtual-desktop/docs/features/autoIncreasePremiumFileShareQuota.md @@ -5,13 +5,13 @@ ## Features - [**Auto Increase Premium File Share Quota**](./autoIncreasePremiumFileShareQuota.md#auto-increase-premium-file-share-quota) +- [**Autoscale**](./autoscale.md#autoscale) - [**Backups**](./backups.md#backups) - [**Drain Mode**](./drainMode.md#drain-mode) - [**FSLogix**](./fslogix.md#fslogix) - [**GPU Drivers & Settings**](./gpu.md#gpu-drivers--settings) - [**High Availability**](./highAvailability.md#high-availability) - [**Monitoring**](./monitoring.md#monitoring) -- [**Scaling Tool**](./scalingTool.md#scaling-tool) - [**Server-Side Encryption with Customer Managed Keys**](./serverSideEncryption.md#server-side-encryption) - [**SMB Multichannel**](./smbMultiChannel.md#smb-multichannel) - [**Start VM On Connect**](./startVmOnConnect.md#start-vm-on-connect) diff --git a/src/bicep/add-ons/azure-virtual-desktop/docs/features/scalingTool.md b/src/bicep/add-ons/azure-virtual-desktop/docs/features/autoscale.md similarity index 58% rename from src/bicep/add-ons/azure-virtual-desktop/docs/features/scalingTool.md rename to src/bicep/add-ons/azure-virtual-desktop/docs/features/autoscale.md index 1c1b5b3b2..accccdc53 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/docs/features/scalingTool.md +++ b/src/bicep/add-ons/azure-virtual-desktop/docs/features/autoscale.md @@ -5,31 +5,26 @@ ## Features - [**Auto Increase Premium File Share Quota**](./autoIncreasePremiumFileShareQuota.md#auto-increase-premium-file-share-quota) +- [**Autoscale**](./autoscale.md#autoscale) - [**Backups**](./backups.md#backups) - [**Drain Mode**](./drainMode.md#drain-mode) - [**FSLogix**](./fslogix.md#fslogix) - [**GPU Drivers & Settings**](./gpu.md#gpu-drivers--settings) - [**High Availability**](./highAvailability.md#high-availability) - [**Monitoring**](./monitoring.md#monitoring) -- [**Scaling Tool**](./scalingTool.md#scaling-tool) - [**Server-Side Encryption with Customer Managed Keys**](./serverSideEncryption.md#server-side-encryption) - [**SMB Multichannel**](./smbMultiChannel.md#smb-multichannel) - [**Start VM On Connect**](./startVmOnConnect.md#start-vm-on-connect) - [**Trusted Launch**](./trustedLaunch.md#trusted-launch) - [**Validation**](./validation.md#validation) -### Scaling Tool +### Autoscale -This feature is deployed if selected to help save on cost. Based on the desired configuration, session hosts will scale up during peak hours and shutdown after peak hours. It is recommended to use policies to manage idle and disconnected over using the built-in capability in this tool. In this solution, a managed identity is deployed on the Automation Account to reduce the privileges needed for tool. +This feature is deployed to help reduce the cost of your pooled or personal host pools by turning off session hosts during your off peak hours. It is recommended to use policies to manage idle and disconnected sessions, over using the built-in capability in this tool. Many of the settings have been preconfigured. Adjust the scaling plan resource post deployment to fine the settings. -**Reference:** [Scaling Tool - Microsoft Docs](https://docs.microsoft.com/en-us/azure/virtual-desktop/scaling-automation-logic-apps) +**Reference:** [Autoscale - Microsoft Docs](https://learn.microsoft.com/azure/virtual-desktop/autoscale-scenarios) **Deployed Resources:** -- Automation Account - - Diagnostic Setting (optional) - - Job Schedules - - Runbook - - Schedules - - System Assigned Identity +- Scaling Plan - Role Assignment diff --git a/src/bicep/add-ons/azure-virtual-desktop/docs/features/backups.md b/src/bicep/add-ons/azure-virtual-desktop/docs/features/backups.md index db7d05855..ce2e1eae2 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/docs/features/backups.md +++ b/src/bicep/add-ons/azure-virtual-desktop/docs/features/backups.md @@ -5,13 +5,13 @@ ## Features - [**Auto Increase Premium File Share Quota**](./autoIncreasePremiumFileShareQuota.md#auto-increase-premium-file-share-quota) +- [**Autoscale**](./autoscale.md#autoscale) - [**Backups**](./backups.md#backups) - [**Drain Mode**](./drainMode.md#drain-mode) - [**FSLogix**](./fslogix.md#fslogix) - [**GPU Drivers & Settings**](./gpu.md#gpu-drivers--settings) - [**High Availability**](./highAvailability.md#high-availability) - [**Monitoring**](./monitoring.md#monitoring) -- [**Scaling Tool**](./scalingTool.md#scaling-tool) - [**Server-Side Encryption with Customer Managed Keys**](./serverSideEncryption.md#server-side-encryption) - [**SMB Multichannel**](./smbMultiChannel.md#smb-multichannel) - [**Start VM On Connect**](./startVmOnConnect.md#start-vm-on-connect) @@ -22,7 +22,7 @@ This optional feature enables backups to protect user profile data. When selected, if the host pool is "pooled" and the storage solution is Azure Files, the solution will protect the file share. If the host pool is "personal", the solution will protect the virtual machines. -**Reference:** [Azure Backup - Microsoft Docs](https://docs.microsoft.com/en-us/azure/backup/backup-overview) +**Reference:** [Azure Backup - Microsoft Docs](https://learn.microsoft.com/azure/backup/backup-overview) **Deployed Resources:** diff --git a/src/bicep/add-ons/azure-virtual-desktop/docs/features/drainMode.md b/src/bicep/add-ons/azure-virtual-desktop/docs/features/drainMode.md index dcce7240a..501457147 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/docs/features/drainMode.md +++ b/src/bicep/add-ons/azure-virtual-desktop/docs/features/drainMode.md @@ -5,13 +5,13 @@ ## Features - [**Auto Increase Premium File Share Quota**](./autoIncreasePremiumFileShareQuota.md#auto-increase-premium-file-share-quota) +- [**Autoscale**](./autoscale.md#autoscale) - [**Backups**](./backups.md#backups) - [**Drain Mode**](./drainMode.md#drain-mode) - [**FSLogix**](./fslogix.md#fslogix) - [**GPU Drivers & Settings**](./gpu.md#gpu-drivers--settings) - [**High Availability**](./highAvailability.md#high-availability) - [**Monitoring**](./monitoring.md#monitoring) -- [**Scaling Tool**](./scalingTool.md#scaling-tool) - [**Server-Side Encryption with Customer Managed Keys**](./serverSideEncryption.md#server-side-encryption) - [**SMB Multichannel**](./smbMultiChannel.md#smb-multichannel) - [**Start VM On Connect**](./startVmOnConnect.md#start-vm-on-connect) @@ -22,7 +22,7 @@ When this optional feature is deployed, the sessions hosts will be put in drain mode to ensure the end users cannot access them until they have been validated. -**Reference:** [Drain Mode - Microsoft Docs](https://docs.microsoft.com/en-us/azure/virtual-desktop/drain-mode) +**Reference:** [Drain Mode - Microsoft Docs](https://learn.microsoft.com/azure/virtual-desktop/drain-mode) **Deployed Resources:** diff --git a/src/bicep/add-ons/azure-virtual-desktop/docs/features/fslogix.md b/src/bicep/add-ons/azure-virtual-desktop/docs/features/fslogix.md index cd49b4a07..90c2d0deb 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/docs/features/fslogix.md +++ b/src/bicep/add-ons/azure-virtual-desktop/docs/features/fslogix.md @@ -5,13 +5,13 @@ ## Features - [**Auto Increase Premium File Share Quota**](./autoIncreasePremiumFileShareQuota.md#auto-increase-premium-file-share-quota) +- [**Autoscale**](./autoscale.md#autoscale) - [**Backups**](./backups.md#backups) - [**Drain Mode**](./drainMode.md#drain-mode) - [**FSLogix**](./fslogix.md#fslogix) - [**GPU Drivers & Settings**](./gpu.md#gpu-drivers--settings) - [**High Availability**](./highAvailability.md#high-availability) - [**Monitoring**](./monitoring.md#monitoring) -- [**Scaling Tool**](./scalingTool.md#scaling-tool) - [**Server-Side Encryption with Customer Managed Keys**](./serverSideEncryption.md#server-side-encryption) - [**SMB Multichannel**](./smbMultiChannel.md#smb-multichannel) - [**Start VM On Connect**](./startVmOnConnect.md#start-vm-on-connect) @@ -29,7 +29,7 @@ Azure Files and Azure NetApp Files are the only two SMB storage services availab - Profile Container (Recommended) - Profile & Office Container -**Reference:** [FSLogix - Microsoft Docs](https://docs.microsoft.com/en-us/fslogix/overview) +**Reference:** [FSLogix - Microsoft Docs](https://learn.microsoft.com/fslogix/overview) **Deployed Resources:** diff --git a/src/bicep/add-ons/azure-virtual-desktop/docs/features/gpu.md b/src/bicep/add-ons/azure-virtual-desktop/docs/features/gpu.md index 39ee0e90f..e7742e699 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/docs/features/gpu.md +++ b/src/bicep/add-ons/azure-virtual-desktop/docs/features/gpu.md @@ -5,13 +5,13 @@ ## Features - [**Auto Increase Premium File Share Quota**](./autoIncreasePremiumFileShareQuota.md#auto-increase-premium-file-share-quota) +- [**Autoscale**](./autoscale.md#autoscale) - [**Backups**](./backups.md#backups) - [**Drain Mode**](./drainMode.md#drain-mode) - [**FSLogix**](./fslogix.md#fslogix) - [**GPU Drivers & Settings**](./gpu.md#gpu-drivers--settings) - [**High Availability**](./highAvailability.md#high-availability) - [**Monitoring**](./monitoring.md#monitoring) -- [**Scaling Tool**](./scalingTool.md#scaling-tool) - [**Server-Side Encryption with Customer Managed Keys**](./serverSideEncryption.md#server-side-encryption) - [**SMB Multichannel**](./smbMultiChannel.md#smb-multichannel) - [**Start VM On Connect**](./startVmOnConnect.md#start-vm-on-connect) @@ -22,7 +22,7 @@ When an appropriate VM size (Nv, Nvv3, Nvv4, or NCasT4_v3 series) is selected, this solution will automatically deploy the appropriate virtual machine extension to install the graphics driver and configure the recommended registry settings. -**Reference:** [Configure GPU Acceleration - Microsoft Docs](https://docs.microsoft.com/en-us/azure/virtual-desktop/configure-vm-gpu) +**Reference:** [Configure GPU Acceleration - Microsoft Docs](https://learn.microsoft.com/azure/virtual-desktop/configure-vm-gpu) **Deployed Resources:** diff --git a/src/bicep/add-ons/azure-virtual-desktop/docs/features/highAvailability.md b/src/bicep/add-ons/azure-virtual-desktop/docs/features/highAvailability.md index a359b7380..f1ac3779c 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/docs/features/highAvailability.md +++ b/src/bicep/add-ons/azure-virtual-desktop/docs/features/highAvailability.md @@ -5,13 +5,13 @@ ## Features - [**Auto Increase Premium File Share Quota**](./autoIncreasePremiumFileShareQuota.md#auto-increase-premium-file-share-quota) +- [**Autoscale**](./autoscale.md#autoscale) - [**Backups**](./backups.md#backups) - [**Drain Mode**](./drainMode.md#drain-mode) - [**FSLogix**](./fslogix.md#fslogix) - [**GPU Drivers & Settings**](./gpu.md#gpu-drivers--settings) - [**High Availability**](./highAvailability.md#high-availability) - [**Monitoring**](./monitoring.md#monitoring) -- [**Scaling Tool**](./scalingTool.md#scaling-tool) - [**Server-Side Encryption with Customer Managed Keys**](./serverSideEncryption.md#server-side-encryption) - [**SMB Multichannel**](./smbMultiChannel.md#smb-multichannel) - [**Start VM On Connect**](./startVmOnConnect.md#start-vm-on-connect) @@ -22,7 +22,7 @@ This optional feature will deploy the selected availability option and only provides high availability for "pooled" host pools since it is a load balanced solution. Virtual machines can be deployed in either Availability Zones or Availability Sets, to provide a higher SLA for your solution. SLA: 99.99% for Availability Zones, 99.95% for Availability Sets. -**Reference:** [Availability options for Azure Virtual Machines - Microsoft Docs](https://docs.microsoft.com/en-us/azure/virtual-machines/availability) +**Reference:** [Availability options for Azure Virtual Machines - Microsoft Docs](https://learn.microsoft.com/azure/virtual-machines/availability) **Deployed Resources:** diff --git a/src/bicep/add-ons/azure-virtual-desktop/docs/features/monitoring.md b/src/bicep/add-ons/azure-virtual-desktop/docs/features/monitoring.md index 3edd151df..a93a988c6 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/docs/features/monitoring.md +++ b/src/bicep/add-ons/azure-virtual-desktop/docs/features/monitoring.md @@ -5,13 +5,13 @@ ## Features - [**Auto Increase Premium File Share Quota**](./autoIncreasePremiumFileShareQuota.md#auto-increase-premium-file-share-quota) +- [**Autoscale**](./autoscale.md#autoscale) - [**Backups**](./backups.md#backups) - [**Drain Mode**](./drainMode.md#drain-mode) - [**FSLogix**](./fslogix.md#fslogix) - [**GPU Drivers & Settings**](./gpu.md#gpu-drivers--settings) - [**High Availability**](./highAvailability.md#high-availability) - [**Monitoring**](./monitoring.md#monitoring) -- [**Scaling Tool**](./scalingTool.md#scaling-tool) - [**Server-Side Encryption with Customer Managed Keys**](./serverSideEncryption.md#server-side-encryption) - [**SMB Multichannel**](./smbMultiChannel.md#smb-multichannel) - [**Start VM On Connect**](./startVmOnConnect.md#start-vm-on-connect) @@ -22,7 +22,7 @@ This feature deploys the required resources to enable the Insights workbook in the Azure Virtual Desktop blade in the Azure Portal. -**Reference:** [Azure Monitor for AVD - Microsoft Docs](https://docs.microsoft.com/en-us/azure/virtual-desktop/azure-monitor) +**Reference:** [Azure Monitor for AVD - Microsoft Docs](https://learn.microsoft.com/azure/virtual-desktop/azure-monitor) **Deployed Resources:** diff --git a/src/bicep/add-ons/azure-virtual-desktop/docs/features/serverSideEncryption.md b/src/bicep/add-ons/azure-virtual-desktop/docs/features/serverSideEncryption.md index 6bc21e374..31e7ac125 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/docs/features/serverSideEncryption.md +++ b/src/bicep/add-ons/azure-virtual-desktop/docs/features/serverSideEncryption.md @@ -5,13 +5,13 @@ ## Features - [**Auto Increase Premium File Share Quota**](./autoIncreasePremiumFileShareQuota.md#auto-increase-premium-file-share-quota) +- [**Autoscale**](./autoscale.md#autoscale) - [**Backups**](./backups.md#backups) - [**Drain Mode**](./drainMode.md#drain-mode) - [**FSLogix**](./fslogix.md#fslogix) - [**GPU Drivers & Settings**](./gpu.md#gpu-drivers--settings) - [**High Availability**](./highAvailability.md#high-availability) - [**Monitoring**](./monitoring.md#monitoring) -- [**Scaling Tool**](./scalingTool.md#scaling-tool) - [**Server-Side Encryption with Customer Managed Keys**](./serverSideEncryption.md#server-side-encryption) - [**SMB Multichannel**](./smbMultiChannel.md#smb-multichannel) - [**Start VM On Connect**](./startVmOnConnect.md#start-vm-on-connect) diff --git a/src/bicep/add-ons/azure-virtual-desktop/docs/features/smbMultiChannel.md b/src/bicep/add-ons/azure-virtual-desktop/docs/features/smbMultiChannel.md index 91a5e0db4..d82d8df0b 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/docs/features/smbMultiChannel.md +++ b/src/bicep/add-ons/azure-virtual-desktop/docs/features/smbMultiChannel.md @@ -5,13 +5,13 @@ ## Features - [**Auto Increase Premium File Share Quota**](./autoIncreasePremiumFileShareQuota.md#auto-increase-premium-file-share-quota) +- [**Autoscale**](./autoscale.md#autoscale) - [**Backups**](./backups.md#backups) - [**Drain Mode**](./drainMode.md#drain-mode) - [**FSLogix**](./fslogix.md#fslogix) - [**GPU Drivers & Settings**](./gpu.md#gpu-drivers--settings) - [**High Availability**](./highAvailability.md#high-availability) - [**Monitoring**](./monitoring.md#monitoring) -- [**Scaling Tool**](./scalingTool.md#scaling-tool) - [**Server-Side Encryption with Customer Managed Keys**](./serverSideEncryption.md#server-side-encryption) - [**SMB Multichannel**](./smbMultiChannel.md#smb-multichannel) - [**Start VM On Connect**](./startVmOnConnect.md#start-vm-on-connect) @@ -22,4 +22,4 @@ This feature is automatically enabled when Azure Files Premium is selected for FSLogix storage. This feature is only supported with Azure Files Premium and it allows multiple connections to an SMB share from an SMB client. -**Reference:** [SMB Multichannel Performance - Microsoft Docs](https://docs.microsoft.com/en-us/azure/storage/files/storage-files-smb-multichannel-performance) +**Reference:** [SMB Multichannel Performance - Microsoft Docs](https://learn.microsoft.com/azure/storage/files/storage-files-smb-multichannel-performance) diff --git a/src/bicep/add-ons/azure-virtual-desktop/docs/features/startVmOnConnect.md b/src/bicep/add-ons/azure-virtual-desktop/docs/features/startVmOnConnect.md index cefb92fd5..4fef2021e 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/docs/features/startVmOnConnect.md +++ b/src/bicep/add-ons/azure-virtual-desktop/docs/features/startVmOnConnect.md @@ -5,13 +5,13 @@ ## Features - [**Auto Increase Premium File Share Quota**](./autoIncreasePremiumFileShareQuota.md#auto-increase-premium-file-share-quota) +- [**Autoscale**](./autoscale.md#autoscale) - [**Backups**](./backups.md#backups) - [**Drain Mode**](./drainMode.md#drain-mode) - [**FSLogix**](./fslogix.md#fslogix) - [**GPU Drivers & Settings**](./gpu.md#gpu-drivers--settings) - [**High Availability**](./highAvailability.md#high-availability) - [**Monitoring**](./monitoring.md#monitoring) -- [**Scaling Tool**](./scalingTool.md#scaling-tool) - [**Server-Side Encryption with Customer Managed Keys**](./serverSideEncryption.md#server-side-encryption) - [**SMB Multichannel**](./smbMultiChannel.md#smb-multichannel) - [**Start VM On Connect**](./startVmOnConnect.md#start-vm-on-connect) @@ -22,7 +22,7 @@ This optional feature allows your end users to turn on a session host when all the session hosts have been stopped / deallocated. This is done automatically when the end user opens the AVD client and attempts to access a resource. Start VM On Connect compliments scaling solutions by ensuring the session hosts can be turned off to reduce cost but made available when needed. -**Reference:** [Start VM On Connect - Microsoft Docs](https://docs.microsoft.com/en-us/azure/virtual-desktop/start-virtual-machine-connect?tabs=azure-portal) +**Reference:** [Start VM On Connect - Microsoft Docs](https://learn.microsoft.com/azure/virtual-desktop/start-virtual-machine-connect?tabs=azure-portal) **Deployed Resources:** diff --git a/src/bicep/add-ons/azure-virtual-desktop/docs/features/trustedLaunch.md b/src/bicep/add-ons/azure-virtual-desktop/docs/features/trustedLaunch.md index 3c6ff14ec..890efeade 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/docs/features/trustedLaunch.md +++ b/src/bicep/add-ons/azure-virtual-desktop/docs/features/trustedLaunch.md @@ -5,13 +5,13 @@ ## Features - [**Auto Increase Premium File Share Quota**](./autoIncreasePremiumFileShareQuota.md#auto-increase-premium-file-share-quota) +- [**Autoscale**](./autoscale.md#autoscale) - [**Backups**](./backups.md#backups) - [**Drain Mode**](./drainMode.md#drain-mode) - [**FSLogix**](./fslogix.md#fslogix) - [**GPU Drivers & Settings**](./gpu.md#gpu-drivers--settings) - [**High Availability**](./highAvailability.md#high-availability) - [**Monitoring**](./monitoring.md#monitoring) -- [**Scaling Tool**](./scalingTool.md#scaling-tool) - [**Server-Side Encryption with Customer Managed Keys**](./serverSideEncryption.md#server-side-encryption) - [**SMB Multichannel**](./smbMultiChannel.md#smb-multichannel) - [**Start VM On Connect**](./startVmOnConnect.md#start-vm-on-connect) @@ -31,7 +31,7 @@ It is a security best practice to enable this feature to protect your virtual ma - rootkits - kernel-level malware -**Reference:** [Trusted Launch - Microsoft Docs](https://docs.microsoft.com/en-us/azure/virtual-machines/trusted-launch) +**Reference:** [Trusted Launch - Microsoft Docs](https://learn.microsoft.com/azure/virtual-machines/trusted-launch) **Deployed Resources:** diff --git a/src/bicep/add-ons/azure-virtual-desktop/docs/features/validation.md b/src/bicep/add-ons/azure-virtual-desktop/docs/features/validation.md index 72a54046c..e394c81cb 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/docs/features/validation.md +++ b/src/bicep/add-ons/azure-virtual-desktop/docs/features/validation.md @@ -5,13 +5,13 @@ ## Features - [**Auto Increase Premium File Share Quota**](./autoIncreasePremiumFileShareQuota.md#auto-increase-premium-file-share-quota) +- [**Autoscale**](./autoscale.md#autoscale) - [**Backups**](./backups.md#backups) - [**Drain Mode**](./drainMode.md#drain-mode) - [**FSLogix**](./fslogix.md#fslogix) - [**GPU Drivers & Settings**](./gpu.md#gpu-drivers--settings) - [**High Availability**](./highAvailability.md#high-availability) - [**Monitoring**](./monitoring.md#monitoring) -- [**Scaling Tool**](./scalingTool.md#scaling-tool) - [**Server-Side Encryption with Customer Managed Keys**](./serverSideEncryption.md#server-side-encryption) - [**SMB Multichannel**](./smbMultiChannel.md#smb-multichannel) - [**Start VM On Connect**](./startVmOnConnect.md#start-vm-on-connect) diff --git a/src/bicep/add-ons/azure-virtual-desktop/docs/prerequisites.md b/src/bicep/add-ons/azure-virtual-desktop/docs/prerequisites.md index f213f93fb..0822f1d24 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/docs/prerequisites.md +++ b/src/bicep/add-ons/azure-virtual-desktop/docs/prerequisites.md @@ -8,7 +8,7 @@ To successfully deploy this solution, you will need to ensure the following prer ### Required -- **Licenses:** ensure you have the [required licensing for AVD](https://learn.microsoft.com/en-us/azure/virtual-desktop/overview#requirements). +- **Licenses:** ensure you have the [required licensing for AVD](https://learn.microsoft.com/azure/virtual-desktop/overview#requirements). - **Azure Permissions:** ensure the principal deploying the solution has "Owner" and "Key Vault Administrator" roles assigned on the target Azure subscription. This solution contains many role assignments at different scopes and deploys a key vault with keys and secrets to enhance security. - **Security Group:** create a security group for your AVD users. - AD DS: create the group in ADUC and ensure the group has synchronized to Azure AD. diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/common/function.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/common/function.bicep deleted file mode 100644 index 347a13908..000000000 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/common/function.bicep +++ /dev/null @@ -1,27 +0,0 @@ -param files object -param functionAppName string -param functionName string -param schedule string - -resource functionApp 'Microsoft.Web/sites@2020-12-01' existing = { - name: functionAppName -} - -resource function 'Microsoft.Web/sites/functions@2020-12-01' = { - parent: functionApp - name: functionName - properties: { - config: { - disabled: false - bindings: [ - { - name: 'Timer' - type: 'timerTrigger' - direction: 'in' - schedule: schedule - } - ] - } - files: files - } -} diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/controlPlane/controlPlane.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/controlPlane/controlPlane.bicep deleted file mode 100644 index a244f5168..000000000 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/controlPlane/controlPlane.bicep +++ /dev/null @@ -1,100 +0,0 @@ -targetScope = 'subscription' - -param activeDirectorySolution string -param avdPrivateDnsZoneResourceId string -param customImageId string -param customRdpProperty string -param deploymentNameSuffix string -param deploymentUserAssignedIdentityClientId string -param desktopFriendlyName string -param diskSku string -param domainName string -param enableAvdInsights bool -param hostPoolPublicNetworkAccess string -param hostPoolType string -param imageOffer string -param imagePublisher string -param imageSku string -param imageVersionResourceId string -param locationControlPlane string -param locationVirtualMachines string -param logAnalyticsWorkspaceResourceId string -param managementVirtualMachineName string -param maxSessionLimit int -param mlzTags object -param namingConvention object -param resourceGroupControlPlane string -param resourceGroupManagement string -param roleDefinitions object -param securityPrincipalObjectIds array -param serviceToken string -param sessionHostNamePrefix string -param subnetResourceId string -param tags object -param validationEnvironment bool -param virtualMachineSize string - -var galleryImageOffer = empty(imageVersionResourceId) ? '"${imageOffer}"' : 'null' -var galleryImagePublisher = empty(imageVersionResourceId) ? '"${imagePublisher}"' : 'null' -var galleryImageSku = empty(imageVersionResourceId) ? '"${imageSku}"' : 'null' -var galleryItemId = empty(imageVersionResourceId) ? '"${imagePublisher}.${imageOffer}${imageSku}"' : 'null' -var hostPoolName = namingConvention.hostPool -var imageType = empty(imageVersionResourceId) ? '"Gallery"' : '"CustomImage"' - -module hostPool 'hostPool.bicep' = { - name: 'deploy-vdpool-${deploymentNameSuffix}' - scope: resourceGroup(resourceGroupControlPlane) - params: { - activeDirectorySolution: activeDirectorySolution - avdPrivateDnsZoneResourceId: avdPrivateDnsZoneResourceId - customImageId: customImageId - customRdpProperty: customRdpProperty - diskSku: diskSku - domainName: domainName - enableAvdInsights: enableAvdInsights - galleryImageOffer: galleryImageOffer - galleryImagePublisher: galleryImagePublisher - galleryImageSku: galleryImageSku - galleryItemId: galleryItemId - hostPoolDiagnosticSettingName: namingConvention.hostPoolDiagnosticSetting - hostPoolName: hostPoolName - hostPoolNetworkInterfaceName: namingConvention.hostPoolNetworkInterface - hostPoolPrivateEndpointName: namingConvention.hostPoolPrivateEndpoint - hostPoolPublicNetworkAccess: hostPoolPublicNetworkAccess - hostPoolType: hostPoolType - imageType: imageType - location: locationControlPlane - logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId - maxSessionLimit: maxSessionLimit - mlzTags: mlzTags - sessionHostNamePrefix: sessionHostNamePrefix - subnetResourceId: subnetResourceId - tags: tags - validationEnvironment: validationEnvironment - virtualMachineSize: virtualMachineSize - } -} - -module applicationGroup 'applicationGroup.bicep' = { - name: 'deploy-vdag-${deploymentNameSuffix}' - scope: resourceGroup(resourceGroupControlPlane) - params: { - deploymentNameSuffix: deploymentNameSuffix - deploymentUserAssignedIdentityClientId: deploymentUserAssignedIdentityClientId - desktopApplicationGroupName: replace(namingConvention.applicationGroup, serviceToken, 'desktop') - hostPoolResourceId: hostPool.outputs.resourceId - locationControlPlane: locationControlPlane - locationVirtualMachines: locationVirtualMachines - mlzTags: mlzTags - resourceGroupManagement: resourceGroupManagement - roleDefinitions: roleDefinitions - securityPrincipalObjectIds: securityPrincipalObjectIds - desktopFriendlyName: desktopFriendlyName - tags: tags - virtualMachineName: managementVirtualMachineName - } -} - -output applicationGroupResourceId string = applicationGroup.outputs.resourceId -output hostPoolName string = hostPool.outputs.name -output hostPoolResourceId string = hostPool.outputs.resourceId diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/fslogix/azureFiles/azureFiles.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/fslogix/azureFiles/azureFiles.bicep index 43ba76a2e..ff4f3f0b6 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/fslogix/azureFiles/azureFiles.bicep +++ b/src/bicep/add-ons/azure-virtual-desktop/modules/fslogix/azureFiles/azureFiles.bicep @@ -12,10 +12,7 @@ param encryptionUserAssignedIdentityResourceId string param fileShares array param fslogixShareSizeInGB int param fslogixContainerType string -param fslogixStorageService string -param functionAppName string -param hostPoolName string -param hostPoolType string +param hostPoolResourceId string param keyVaultUri string param location string param managementVirtualMachineName string @@ -24,9 +21,7 @@ param namingConvention object param netbios string param organizationalUnitPath string param recoveryServicesVaultName string -param resourceGroupControlPlane string param resourceGroupManagement string -param resourceGroupStorage string param securityPrincipalObjectIds array param securityPrincipalNames array param serviceToken string @@ -52,10 +47,10 @@ var smbSettings = { } var storageAccountNamePrefix = uniqueString(replace(namingConvention.storageAccount, serviceToken, 'file-fslogix'), resourceGroup().id) var storageRedundancy = availability == 'availabilityZones' ? '_ZRS' : '_LRS' -var tagsPrivateEndpoints = union({'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}'}, contains(tags, 'Microsoft.Network/privateEndpoints') ? tags['Microsoft.Network/privateEndpoints'] : {}, mlzTags) -var tagsStorageAccounts = union({'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}'}, contains(tags, 'Microsoft.Storage/storageAccounts') ? tags['Microsoft.Storage/storageAccounts'] : {}, mlzTags) -var tagsRecoveryServicesVault = union({'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}'}, contains(tags, 'Microsoft.recoveryServices/vaults') ? tags['Microsoft.recoveryServices/vaults'] : {}, mlzTags) -var tagsVirtualMachines = union({'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}'}, contains(tags, 'Microsoft.Compute/virtualMachines') ? tags['Microsoft.Compute/virtualMachines'] : {}, mlzTags) +var tagsPrivateEndpoints = union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Network/privateEndpoints'] ?? {}, mlzTags) +var tagsStorageAccounts = union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Storage/storageAccounts'] ?? {}, mlzTags) +var tagsRecoveryServicesVault = union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.recoveryServices/vaults'] ?? {}, mlzTags) +var tagsVirtualMachines = union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Compute/virtualMachines'] ?? {}, mlzTags) resource storageAccounts 'Microsoft.Storage/storageAccounts@2022-09-01' = [for i in range(0, storageCount): { name: take('${storageAccountNamePrefix}${padLeft(i + storageIndex, 2, '0')}', 15) @@ -161,19 +156,20 @@ module shares 'shares.bicep' = [for i in range(0, storageCount): { storageSku: storageSku } dependsOn: [ + fileServices roleAssignment ] }] resource privateEndpoints 'Microsoft.Network/privateEndpoints@2023-04-01' = [for i in range(0, storageCount): { - name: '${replace(namingConvention.storageAccountPrivateEndpoint, serviceToken, 'file-fslogix')}-${padLeft(i + storageIndex, 2, '0')}' + name: '${namingConvention.storageAccountFilePrivateEndpoint}-${padLeft(i + storageIndex, 2, '0')}' location: location tags: tagsPrivateEndpoints properties: { - customNetworkInterfaceName: '${replace(namingConvention.storageAccountNetworkInterface, serviceToken, 'file-fslogix')}-${padLeft(i + storageIndex, 2, '0')}' + customNetworkInterfaceName: '${namingConvention.storageAccountFileNetworkInterface}-${padLeft(i + storageIndex, 2, '0')}' privateLinkServiceConnections: [ { - name: '${replace(namingConvention.storageAccountPrivateEndpoint, serviceToken, 'file-fslogix')}-${padLeft(i + storageIndex, 2, '0')}' + name: '${namingConvention.storageAccountFilePrivateEndpoint}-${padLeft(i + storageIndex, 2, '0')}' properties: { privateLinkServiceId: storageAccounts[i].id groupIds: [ @@ -206,8 +202,9 @@ resource privateDnsZoneGroups 'Microsoft.Network/privateEndpoints/privateDnsZone ] }] -module ntfsPermissions '../runCommand.bicep' = if (contains(activeDirectorySolution, 'DomainServices')) { - name: 'deploy-fslogix-ntfs-permissions-${deploymentNameSuffix}' +// Sets NTFS permissions on the file shares +module ntfsPermissions '../runCommand.bicep' = { + name: 'set-ntfs-permissions-${deploymentNameSuffix}' scope: resourceGroup(resourceGroupManagement) params: { domainJoinPassword: domainJoinPassword @@ -245,7 +242,7 @@ module ntfsPermissions '../runCommand.bicep' = if (contains(activeDirectorySolut } { name: 'StorageAccountResourceGroupName' - value: resourceGroupStorage + value: resourceGroup().name } { name: 'StorageCount' @@ -283,38 +280,21 @@ module ntfsPermissions '../runCommand.bicep' = if (contains(activeDirectorySolut ] } -module recoveryServices 'recoveryServices.bicep' = if (enableRecoveryServices && contains(hostPoolType, 'Pooled')) { - name: 'deploy-backup-azure-files-${deploymentNameSuffix}' +// Deploys backup items for Azure Files +module recoveryServices 'recoveryServices.bicep' = if (enableRecoveryServices) { + name: 'deploy-backup-${deploymentNameSuffix}' scope: resourceGroup(resourceGroupManagement) params: { deploymentNameSuffix: deploymentNameSuffix fileShares: fileShares location: location recoveryServicesVaultName: recoveryServicesVaultName - resourceGroupStorage: resourceGroupStorage + resourceGroupStorage: resourceGroup().name storageAccountNamePrefix: storageAccountNamePrefix storageCount: storageCount storageIndex: storageIndex tagsRecoveryServicesVault: tagsRecoveryServicesVault } - dependsOn: [ - shares - ] -} - -module autoIncreaseStandardFileShareQuota '../../common/function.bicep' = if (fslogixStorageService == 'AzureFiles Premium' && storageCount > 0) { - name: 'deploy-file-share-scaling-${deploymentNameSuffix}' - scope: resourceGroup(resourceGroupManagement) - params: { - files: { - 'requirements.psd1': loadTextContent('../../../artifacts/auto-increase-file-share/requirements.psd1') - 'run.ps1': loadTextContent('../../../artifacts/auto-increase-file-share/run.ps1') - '../profile.ps1': loadTextContent('../../../artifacts/auto-increase-file-share/profile.ps1') - } - functionAppName: functionAppName - functionName: 'auto-increase-file-share-quota' - schedule: '0 */15 * * * *' - } dependsOn: [ ntfsPermissions ] diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/fslogix/fslogix.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/fslogix/fslogix.bicep index 298b9c194..3a7fb800d 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/fslogix/fslogix.bicep +++ b/src/bicep/add-ons/azure-virtual-desktop/modules/fslogix/fslogix.bicep @@ -1,12 +1,12 @@ targetScope = 'subscription' -param existingSharedActiveDirectoryConnection bool param activeDirectorySolution string param availability string param azureFilesPrivateDnsZoneResourceId string param subnets array param deploymentNameSuffix string param deploymentUserAssignedIdentityClientId string +param deploymentUserAssignedIdentityPrincipalId string param dnsServers string @secure() param domainJoinPassword string @@ -14,12 +14,13 @@ param domainJoinPassword string param domainJoinUserPrincipalName string param domainName string param encryptionUserAssignedIdentityResourceId string +param existingSharedActiveDirectoryConnection bool param fileShares array -param fslogixShareSizeInGB int param fslogixContainerType string +param fslogixShareSizeInGB int param fslogixStorageService string -param functionAppName string -param hostPoolType string +param functionAppPrincipalId string +param hostPoolResourceId string param keyVaultUri string param location string param managementVirtualMachineName string @@ -28,9 +29,8 @@ param namingConvention object param netbios string param organizationalUnitPath string param recoveryServices bool -param resourceGroupControlPlane string param resourceGroupManagement string -param resourceGroupStorage string +param resourceGroupName string param securityPrincipalObjectIds array param securityPrincipalNames array param serviceToken string @@ -43,15 +43,43 @@ param storageService string param subnetResourceId string param tags object -var hostPoolName = namingConvention.hostPool +var tagsNetAppAccount = union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.NetApp/netAppAccounts'] ?? {}, mlzTags) +var tagsVirtualMachines = union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Compute/virtualMachines'] ?? {}, mlzTags) -var tagsNetAppAccount = union({'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}'}, contains(tags, 'Microsoft.NetApp/netAppAccounts') ? tags['Microsoft.NetApp/netAppAccounts'] : {}, mlzTags) -var tagsVirtualMachines = union({'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}'}, contains(tags, 'Microsoft.Compute/virtualMachines') ? tags['Microsoft.Compute/virtualMachines'] : {}, mlzTags) +resource resourceGroup 'Microsoft.Resources/resourceGroups@2023-07-01' = { + name: resourceGroupName + location: location + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Resources/resourceGroups'] ?? {}, mlzTags) +} + +// Role Assignment for FSLogix +// Purpose: assigns the Storage Account Contributor role to the managed identity on the +// management virtual machine storage resource group to domain join storage account(s) & set NTFS permissions on the file share(s) +module roleAssignment_Storage '../common/roleAssignments/resourceGroup.bicep' = { + name: 'assign-role-storage-${deploymentNameSuffix}' + scope: resourceGroup + params: { + principalId: deploymentUserAssignedIdentityPrincipalId + principalType: 'ServicePrincipal' + roleDefinitionId: '17d1049b-9a84-46fb-8f53-869881c3d3ab' + } +} + +// Required role assignment for the funciton to manage the quota on Azure Files Premium +module roleAssignments_resourceGroup '../common/roleAssignments/resourceGroup.bicep' = if (fslogixStorageService == 'AzureFiles Premium') { + name: 'set-role-assignment-${deploymentNameSuffix}' + scope: resourceGroup + params: { + principalId: functionAppPrincipalId + principalType: 'ServicePrincipal' + roleDefinitionId: '17d1049b-9a84-46fb-8f53-869881c3d3ab' // Storage Account Contributor + } +} // Azure NetApp Files for Fslogix -module azureNetAppFiles 'azureNetAppFiles.bicep' = if (storageService == 'AzureNetAppFiles' && contains(activeDirectorySolution, 'DomainServices')) { +module azureNetAppFiles 'azureNetAppFiles.bicep' = if (storageService == 'AzureNetAppFiles') { name: 'deploy-anf-${deploymentNameSuffix}' - scope: resourceGroup(resourceGroupStorage) + scope: resourceGroup params: { existingSharedActiveDirectoryConnection: existingSharedActiveDirectoryConnection delegatedSubnetResourceId: filter(subnets, subnet => contains(subnet.name, 'AzureNetAppFiles'))[0].id @@ -78,9 +106,9 @@ module azureNetAppFiles 'azureNetAppFiles.bicep' = if (storageService == 'AzureN } // Azure Files for FSLogix -module azureFiles 'azureFiles/azureFiles.bicep' = if (storageService == 'AzureFiles' && contains(activeDirectorySolution, 'DomainServices')) { +module azureFiles 'azureFiles/azureFiles.bicep' = if (storageService == 'AzureFiles') { name: 'deploy-azure-files-${deploymentNameSuffix}' - scope: resourceGroup(resourceGroupStorage) + scope: resourceGroup params: { activeDirectorySolution: activeDirectorySolution availability: availability @@ -94,9 +122,7 @@ module azureFiles 'azureFiles/azureFiles.bicep' = if (storageService == 'AzureFi fileShares: fileShares fslogixContainerType: fslogixContainerType fslogixShareSizeInGB: fslogixShareSizeInGB - fslogixStorageService: fslogixStorageService - functionAppName: functionAppName - hostPoolType: hostPoolType + hostPoolResourceId: hostPoolResourceId keyVaultUri: keyVaultUri location: location managementVirtualMachineName: managementVirtualMachineName @@ -105,7 +131,6 @@ module azureFiles 'azureFiles/azureFiles.bicep' = if (storageService == 'AzureFi organizationalUnitPath: organizationalUnitPath recoveryServicesVaultName: namingConvention.recoveryServicesVault resourceGroupManagement: resourceGroupManagement - resourceGroupStorage: resourceGroupStorage securityPrincipalNames: securityPrincipalNames securityPrincipalObjectIds: securityPrincipalObjectIds serviceToken: serviceToken @@ -116,9 +141,7 @@ module azureFiles 'azureFiles/azureFiles.bicep' = if (storageService == 'AzureFi storageSku: storageSku subnetResourceId: subnetResourceId tags: tags - hostPoolName: hostPoolName mlzTags: mlzTags - resourceGroupControlPlane: resourceGroupControlPlane } } diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/logic.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/logic.bicep deleted file mode 100644 index f158639b3..000000000 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/logic.bicep +++ /dev/null @@ -1,103 +0,0 @@ -targetScope = 'subscription' - -param activeDirectorySolution string -param deploymentLocations array -param diskSku string -param domainName string -param fileShareNames object -param fslogixContainerType string -param fslogixStorageService string -param hostPoolType string -param imageOffer string -param imagePublisher string -param imageSku string -param imageVersionResourceId string -param locations object -param locationVirtualMachines string -param networkName string -param resourceGroupControlPlane string -param resourceGroupFeedWorkspace string -param resourceGroupHosts string -param resourceGroupManagement string -param resourceGroupsNetwork array -param resourceGroupStorage string -param securityPrincipals array -param serviceName string -param sessionHostCount int -param sessionHostIndex int -param virtualMachineNamePrefix string -param virtualMachineSize string - -// BATCH SESSION HOSTS -// The following variables are used to determine the batches to deploy any number of AVD session hosts. -var maxResourcesPerTemplateDeployment = 88 // This is the max number of session hosts that can be deployed from the sessionHosts.bicep file in each batch / for loop. Math: (800 - ) / -var divisionValue = sessionHostCount / maxResourcesPerTemplateDeployment // This determines if any full batches are required. -var divisionRemainderValue = sessionHostCount % maxResourcesPerTemplateDeployment // This determines if any partial batches are required. -var sessionHostBatchCount = divisionRemainderValue > 0 ? divisionValue + 1 : divisionValue // This determines the total number of batches needed, whether full and / or partial. - -// BATCH AVAILABILITY SETS -// The following variables are used to determine the number of availability sets. -var maxAvSetMembers = 200 // This is the max number of session hosts that can be deployed in an availability set. -var beginAvSetRange = sessionHostIndex / maxAvSetMembers // This determines the availability set to start with. -var endAvSetRange = (sessionHostCount + sessionHostIndex) / maxAvSetMembers // This determines the availability set to end with. -var availabilitySetsCount = length(range(beginAvSetRange, (endAvSetRange - beginAvSetRange) + 1)) - -// OTHER LOGIC & COMPUTED VALUES -var customImageId = empty(imageVersionResourceId) ? 'null' : '"${imageVersionResourceId}"' -var fileShares = fileShareNames[fslogixContainerType] -var fslogix = fslogixStorageService == 'None' || !contains(activeDirectorySolution, 'DomainServices') ? false : true -var galleryImageOffer = empty(imageVersionResourceId) ? '"${imageOffer}"' : 'null' -var galleryImagePublisher = empty(imageVersionResourceId) ? '"${imagePublisher}"' : 'null' -var galleryImageSku = empty(imageVersionResourceId) ? '"${imageSku}"' : 'null' -var galleryItemId = empty(imageVersionResourceId) ? '"${imagePublisher}.${imageOffer}${imageSku}"' : 'null' -var imageType = empty(imageVersionResourceId) ? '"Gallery"' : '"CustomImage"' -var netbios = split(domainName, '.')[0] -var pooledHostPool = split(hostPoolType, ' ')[0] == 'Pooled' ? true : false -var resourceGroups = union(resourceGroupsCommon, resourceGroupsNetworking, resourceGroupsStorage) -var resourceGroupsCommon = [ - resourceGroupControlPlane - resourceGroupFeedWorkspace - resourceGroupHosts - resourceGroupManagement -] -var resourceGroupsNetworking = length(deploymentLocations) == 2 ? resourceGroupsNetwork : [ - resourceGroupsNetwork[0] -] -var resourceGroupsStorage = fslogix ? [ - resourceGroupStorage -] : [] -var roleDefinitions = { - DesktopVirtualizationPowerOnContributor: '489581de-a3bd-480d-9518-53dea7416b33' - DesktopVirtualizationUser: '1d18fff3-a72a-46b5-b4a9-0b38a3cd7e63' - Reader: 'acdd72a7-3385-48ef-bd42-f606fba81ae7' - VirtualMachineUserLogin: 'fb879df8-f326-4884-b1cf-06f3ad86be52' -} -var securityPrincipalsCount = length(securityPrincipals) -var sessionHostNamePrefix = replace(virtualMachineNamePrefix, '${serviceName}${networkName}', '') -var smbServerLocation = locations[locationVirtualMachines].abbreviation -var storageSku = fslogixStorageService == 'None' ? 'None' : split(fslogixStorageService, ' ')[1] -var storageService = split(fslogixStorageService, ' ')[0] -var storageSuffix = environment().suffixes.storage -var timeDifference = locations[locationVirtualMachines].timeDifference -var timeZone = locations[locationVirtualMachines].timeZone -var vmTemplate = '{"domain":"${domainName}","galleryImageOffer":${galleryImageOffer},"galleryImagePublisher":${galleryImagePublisher},"galleryImageSKU":${galleryImageSku},"imageType":${imageType},"customImageId":${customImageId},"namePrefix":"${sessionHostNamePrefix}","osDiskType":"${diskSku}","vmSize":{"id":"${virtualMachineSize}","cores":null,"ram":null,"rdmaEnabled": false,"supportsMemoryPreservingMaintenance": true},"galleryItemId":${galleryItemId},"hibernate":false,"diskSizeGB":0,"securityType":"TrustedLaunch","secureBoot":true,"vTPM":true,"vmInfrastructureType":"Cloud","virtualProcessorCount":null,"memoryGB":null,"maximumMemoryGB":null,"minimumMemoryGB":null,"dynamicMemoryConfig":false}' - -output availabilitySetsCount int = availabilitySetsCount -output beginAvSetRange int = beginAvSetRange -output divisionRemainderValue int = divisionRemainderValue -output fileShares array = fileShares -output fslogix bool = fslogix -output maxResourcesPerTemplateDeployment int = maxResourcesPerTemplateDeployment -output netbios string = netbios -output pooledHostPool bool = pooledHostPool -output resourceGroups array = resourceGroups -output roleDefinitions object = roleDefinitions -output sessionHostBatchCount int = sessionHostBatchCount -output securityPrincipalsCount int = securityPrincipalsCount -output smbServerLocation string = smbServerLocation -output storageSku string = storageSku -output storageService string = storageService -output storageSuffix string = storageSuffix -output timeDifference string = timeDifference -output timeZone string = timeZone -output vmTemplate string = vmTemplate diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/controlPlane/applicationGroup.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/management/applicationGroup.bicep similarity index 62% rename from src/bicep/add-ons/azure-virtual-desktop/modules/controlPlane/applicationGroup.bicep rename to src/bicep/add-ons/azure-virtual-desktop/modules/management/applicationGroup.bicep index bfb966310..149f3e530 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/controlPlane/applicationGroup.bicep +++ b/src/bicep/add-ons/azure-virtual-desktop/modules/management/applicationGroup.bicep @@ -6,27 +6,23 @@ param hostPoolResourceId string param locationControlPlane string param locationVirtualMachines string param mlzTags object -param resourceGroupManagement string -param roleDefinitions object param securityPrincipalObjectIds array param tags object param virtualMachineName string -resource applicationGroup 'Microsoft.DesktopVirtualization/applicationGroups@2021-03-09-preview' = { +resource applicationGroup 'Microsoft.DesktopVirtualization/applicationGroups@2023-09-05' = { name: desktopApplicationGroupName location: locationControlPlane - tags: union({ - 'cm-resource-parent': hostPoolResourceId - }, contains(tags, 'Microsoft.DesktopVirtualization/applicationGroups') ? tags['Microsoft.DesktopVirtualization/applicationGroups'] : {}, mlzTags) + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.DesktopVirtualization/applicationGroups'] ?? {}, mlzTags) properties: { hostPoolArmPath: hostPoolResourceId applicationGroupType: 'Desktop' } } -// Adds a friendly name to the SessionDesktop application for the desktop application group +// Run Command to update the Application +// Purpose: executes a script to update the friendly name on the application module applicationFriendlyName '../common/runCommand.bicep' = if (!empty(desktopFriendlyName)) { - scope: resourceGroup(resourceGroupManagement) name: 'deploy-vdapp-friendly-name-${deploymentNameSuffix}' params: { location: locationVirtualMachines @@ -54,26 +50,22 @@ module applicationFriendlyName '../common/runCommand.bicep' = if (!empty(desktop } { name: 'UserAssignedIdentityClientId' - value:deploymentUserAssignedIdentityClientId + value: deploymentUserAssignedIdentityClientId } ] script: loadTextContent('../../artifacts/Update-AvdDesktop.ps1') - tags: union( - { - 'cm-resource-parent': hostPoolResourceId - }, - contains(tags, 'Microsoft.Compute/virtualMachines') ? tags['Microsoft.Compute/virtualMachines'] : {}, - mlzTags - ) + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Compute/virtualMachines'] ?? {}, mlzTags) virtualMachineName: virtualMachineName } } -resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for i in range(0, length(securityPrincipalObjectIds)): { +// Role Assignment for AVD access +// Purpose: assigns the Desktop Virtualization User role to the application group for the specified security principals +resource roleAssignment_ApplicationGroup 'Microsoft.Authorization/roleAssignments@2022-04-01' = [for i in range(0, length(securityPrincipalObjectIds)): { + name: guid(securityPrincipalObjectIds[i], '1d18fff3-a72a-46b5-b4a9-0b38a3cd7e63', applicationGroup.id) scope: applicationGroup - name: guid(securityPrincipalObjectIds[i], roleDefinitions.DesktopVirtualizationUser, desktopApplicationGroupName) properties: { - roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitions.DesktopVirtualizationUser) + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '1d18fff3-a72a-46b5-b4a9-0b38a3cd7e63') principalId: securityPrincipalObjectIds[i] } }] diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/management/automationAccount.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/management/automationAccount.bicep deleted file mode 100644 index 927ad90c3..000000000 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/management/automationAccount.bicep +++ /dev/null @@ -1,126 +0,0 @@ -param automationAccountDiagnosticSettingName string -param automationAccountName string -param automationAccountNetworkInterfaceName string -param automationAccountPrivateDnsZoneResourceId string -param automationAccountPrivateEndpointName string -param hostPoolName string -param location string -param logAnalyticsWorkspaceResourceId string -param mlzTags object -param monitoring bool -param resourceGroupControlPlane string -param subnetResourceId string -param tags object -param virtualMachineName string - -resource virtualMachine 'Microsoft.Compute/virtualMachines@2023-07-01' existing = { - name: virtualMachineName -} - -resource automationAccount 'Microsoft.Automation/automationAccounts@2021-06-22' = { - name: automationAccountName - location: location - tags: union({'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}'}, contains(tags, 'Microsoft.Automation/automationAccounts') ? tags['Microsoft.Automation/automationAccounts'] : {}, mlzTags) - identity: { - type: 'SystemAssigned' - } - properties: { - sku: { - name: 'Free' - } - } -} - -resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-04-01' = { - name: automationAccountPrivateEndpointName - location: location - tags: union({'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}'}, contains(tags, 'Microsoft.Network/privateEndpoints') ? tags['Microsoft.Network/privateEndpoints'] : {}, mlzTags) - properties: { - customNetworkInterfaceName: automationAccountNetworkInterfaceName - privateLinkServiceConnections: [ - { - name: automationAccountPrivateEndpointName - properties: { - privateLinkServiceId: automationAccount.id - groupIds: [ - 'DSCAndHybridWorker' - ] - } - } - ] - subnet: { - id: subnetResourceId - } - } -} - -resource privateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-05-01' = { - parent: privateEndpoint - name: 'default' - properties: { - privateDnsZoneConfigs: [ - { - name: replace(split(automationAccountPrivateDnsZoneResourceId, '/')[8], '.', '-') - properties: { - privateDnsZoneId: automationAccountPrivateDnsZoneResourceId - } - } - ] - } -} - -resource hybridRunbookWorkerGroup 'Microsoft.Automation/automationAccounts/hybridRunbookWorkerGroups@2022-08-08' = { - parent: automationAccount - name: 'Scaling Tool' -} - -resource hybridRunbookWorker 'Microsoft.Automation/automationAccounts/hybridRunbookWorkerGroups/hybridRunbookWorkers@2022-08-08' = { - parent: hybridRunbookWorkerGroup - name: guid(hybridRunbookWorkerGroup.id) - properties: { - vmResourceId: virtualMachine.id - } -} - -resource extension_HybridWorker 'Microsoft.Compute/virtualMachines/extensions@2022-03-01' = { - parent: virtualMachine - name: 'HybridWorkerForWindows' - location: location - tags: union({'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}'}, contains(tags, 'Microsoft.Compute/virtualMachines') ? tags['Microsoft.Compute/virtualMachines'] : {}, mlzTags) - properties: { - publisher: 'Microsoft.Azure.Automation.HybridWorker' - type: 'HybridWorkerForWindows' - typeHandlerVersion: '1.1' - autoUpgradeMinorVersion: true - enableAutomaticUpgrade: true - settings: { - AutomationAccountURL: automationAccount.properties.automationHybridServiceUrl - } - } -} - -// Enables logging in a log analytics workspace for alerting and dashboards -resource diagnosticSetting 'Microsoft.Insights/diagnosticsettings@2017-05-01-preview' = if (monitoring) { - scope: automationAccount - name: automationAccountDiagnosticSettingName - properties: { - logs: [ - { - category: 'DscNodeStatus' - enabled: true - } - { - category: 'JobLogs' - enabled: true - } - { - category: 'JobStreams' - enabled: true - } - ] - workspaceId: logAnalyticsWorkspaceResourceId - } -} - -output name string = automationAccount.name -output hybridRunbookWorkerGroupName string = hybridRunbookWorkerGroup.name diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/management/diskAccess.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/management/diskAccess.bicep index db57de786..4eb8fc219 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/management/diskAccess.bicep +++ b/src/bicep/add-ons/azure-virtual-desktop/modules/management/diskAccess.bicep @@ -1,26 +1,21 @@ -param hostPoolName string +param hostPoolResourceId string param location string param mlzTags object param namingConvention object -param resourceGroupControlPlane string param subnetResourceId string param tags object resource diskAccess 'Microsoft.Compute/diskAccesses@2021-04-01' = { name: namingConvention.diskAccess location: location - tags: union({ - 'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}' - }, contains(tags, 'Microsoft.Compute/diskAccesses') ? tags['Microsoft.Compute/diskAccesses'] : {}, mlzTags) + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Compute/diskAccesses'] ?? {}, mlzTags) properties: {} } resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-04-01' = { name: namingConvention.diskAccessPrivateEndpoint location: location - tags: union({ - 'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}' - }, contains(tags, 'Microsoft.Network/privateEndpoints') ? tags['Microsoft.Network/privateEndpoints'] : {}, mlzTags) + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Network/privateEndpoints'] ?? {}, mlzTags) properties: { customNetworkInterfaceName: namingConvention.diskAccessNetworkInterface privateLinkServiceConnections: [ @@ -40,5 +35,4 @@ resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-04-01' = { } } - output resourceId string = diskAccess.id diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/management/functionApp.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/management/functionApp.bicep index bf2487656..9d4ccc520 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/management/functionApp.bicep +++ b/src/bicep/add-ons/azure-virtual-desktop/modules/management/functionApp.bicep @@ -1,74 +1,63 @@ param delegatedSubnetResourceId string -param deployFslogix bool param deploymentNameSuffix string param enableApplicationInsights bool param environmentAbbreviation string -param hostPoolName string +param hostPoolResourceId string param keyExpirationInDays int = 30 param location string = resourceGroup().location param logAnalyticsWorkspaceResourceId string +param mlzTags object param namingConvention object param privateDnsZoneResourceIdPrefix string param privateDnsZones array param privateLinkScopeResourceId string param resourceAbbreviations object -param resourceGroupControlPlane string -param resourceGroupHosts string -param resourceGroupStorage string -param scalingBeginPeakTime string -param scalingEndPeakTime string -param scalingLimitSecondsToForceLogOffUser string -param scalingMinimumNumberOfRdsh string -param scalingSessionThresholdPerCPU string +param resourceGroupProfiles string param serviceToken string param subnetResourceId string param tags object -param timeDifference string var cloudSuffix = replace(replace(environment().resourceManager, 'https://management.', ''), '/', '') -var functionAppKeyword = environment().name == 'AzureCloud' || environment().name == 'AzureUSGovernment' - ? 'azurewebsites' - : 'appservice' +var functionAppKeyword = environment().name == 'AzureCloud' || environment().name == 'AzureUSGovernment' ? 'azurewebsites' : 'appservice' var functionAppScmPrivateDnsZoneResourceId = '${privateDnsZoneResourceIdPrefix}scm.${filter(privateDnsZones, name => contains(name, functionAppKeyword))[0]}' -var roleAssignments = union([ +var service = 'aipfsq' +var storageSubResources = [ { - roleDefinitionId: '40c5ff49-9181-41f8-ae61-143b0e78555e' // Desktop Virtualization Power On Off Contributor - scope: resourceGroupControlPlane + name: 'blob' + id: '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'blob'))[0]}' + nic: namingConvention.storageAccountBlobNetworkInterface + pe: namingConvention.storageAccountBlobPrivateEndpoint } { - roleDefinitionId: '40c5ff49-9181-41f8-ae61-143b0e78555e' // Desktop Virtualization Power On Off Contributor - scope: resourceGroupHosts + name: 'file' + id: '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'file'))[0]}' + nic: namingConvention.storageAccountFileNetworkInterface + pe: namingConvention.storageAccountFilePrivateEndpoint } -], deployFslogix ? [ { - roleDefinitionId: '17d1049b-9a84-46fb-8f53-869881c3d3ab' // Storage Account Contributor - scope: resourceGroupStorage + name: 'queue' + id: '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'queue'))[0]}' + nic: namingConvention.storageAccountQueueNetworkInterface + pe: namingConvention.storageAccountQueuePrivateEndpoint + } + { + name: 'table' + id: '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'table'))[0]}' + nic: namingConvention.storageAccountTableNetworkInterface + pe: namingConvention.storageAccountTablePrivateEndpoint } -] : []) -var service = 'mgmt' -var storagePrivateDnsZoneResourceIds = [ - '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'blob'))[0]}' - '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'file'))[0]}' - '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'queue'))[0]}' - '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'table'))[0]}' -] -var storageSubResources = [ - 'blob' - 'file' - 'queue' - 'table' ] resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { name: replace(namingConvention.userAssignedIdentity, serviceToken, service) location: location - tags: tags[?'Microsoft.ManagedIdentity/userAssignedIdentities'] ?? {} + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.ManagedIdentity/userAssignedIdentities'] ?? {}, mlzTags) } resource vault 'Microsoft.KeyVault/vaults@2022-07-01' = { - name: '${resourceAbbreviations.keyVaults}${uniqueString(replace(namingConvention.keyVault, service, 'cmk'), resourceGroup().id)}' + name: '${resourceAbbreviations.keyVaults}${uniqueString(replace(namingConvention.keyVault, serviceToken, service), resourceGroup().id)}' location: location - tags: tags[?'Microsoft.KeyVault/vaults'] ?? {} + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.KeyVault/vaults'] ?? {}, mlzTags) properties: { enabledForDeployment: false enabledForDiskEncryption: false @@ -105,7 +94,7 @@ resource roleAssignment_Encryption 'Microsoft.Authorization/roleAssignments@2020 resource privateEndpoint_vault 'Microsoft.Network/privateEndpoints@2023-04-01' = { name: replace(namingConvention.keyVaultPrivateEndpoint, serviceToken, service) location: location - tags: tags[?'Microsoft.Network/privateEndpoints'] ?? {} + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Network/privateEndpoints'] ?? {}, mlzTags) properties: { customNetworkInterfaceName: replace(namingConvention.keyVaultNetworkInterface, serviceToken, service) privateLinkServiceConnections: [ @@ -179,7 +168,7 @@ resource key_storageAccount 'Microsoft.KeyVault/vaults/keys@2022-07-01' = { resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = { name: uniqueString(replace(namingConvention.storageAccount, serviceToken, service), resourceGroup().id) location: location - tags: tags[?'Microsoft.Storage/storageAccounts'] ?? {} + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Storage/storageAccounts'] ?? {}, mlzTags) sku: { name: 'Standard_LRS' } @@ -255,30 +244,18 @@ resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2021-09-01' resource privateEndpoints_storage 'Microsoft.Network/privateEndpoints@2023-04-01' = [ for resource in storageSubResources: { - name: replace( - namingConvention.storageAccountPrivateEndpoint, - '${serviceToken}-${resourceAbbreviations.storageAccounts}', - '${resource}-${resourceAbbreviations.storageAccounts}-scale' - ) + name: replace(resource.pe, serviceToken, service) location: location - tags: tags[?'Microsoft.Network/privateEndpoints'] ?? {} + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Network/privateEndpoints'] ?? {}, mlzTags) properties: { - customNetworkInterfaceName: replace( - namingConvention.storageAccountNetworkInterface, - '${serviceToken}-${resourceAbbreviations.storageAccounts}', - '${resource}-${resourceAbbreviations.storageAccounts}-scale' - ) + customNetworkInterfaceName: replace(resource.nic, serviceToken, service) privateLinkServiceConnections: [ { - name: replace( - namingConvention.storageAccountPrivateEndpoint, - '${serviceToken}-${resourceAbbreviations.storageAccounts}', - '${resource}-${resourceAbbreviations.storageAccounts}-scale' - ) + name: replace(resource.pe, serviceToken, service) properties: { privateLinkServiceId: storageAccount.id groupIds: [ - resource + resource.name ] } } @@ -300,7 +277,7 @@ resource privateDnsZoneGroups_storage 'Microsoft.Network/privateEndpoints/privat name: 'ipconfig1' properties: { #disable-next-line use-resource-id-functions - privateDnsZoneId: storagePrivateDnsZoneResourceIds[i] + privateDnsZoneId: resource.id } } ] @@ -335,7 +312,7 @@ resource diagnosticSetting_storage_blob 'Microsoft.Insights/diagnosticsettings@2 resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = if (enableApplicationInsights) { name: replace(namingConvention.applicationInsights, serviceToken, service) location: location - tags: tags[?'Microsoft.Insights/components'] ?? {} + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Insights/components'] ?? {}, mlzTags) properties: { Application_Type: 'web' publicNetworkAccessForIngestion: 'Disabled' @@ -356,11 +333,11 @@ module privateLinkScope 'privateLinkScope.bicep' = if (enableApplicationInsights resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = { name: replace(namingConvention.appServicePlan, serviceToken, service) location: location - tags: tags[?'Microsoft.Web/serverfarms'] ?? {} + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Web/serverfarms'] ?? {}, mlzTags) sku: { - name: 'P0v3' + name: 'P1v3' tier: 'PremiumV3' - size: 'P0v3' + size: 'P1v3' family: 'Pv3' capacity: 1 } @@ -370,7 +347,7 @@ resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = { resource functionApp 'Microsoft.Web/sites@2023-01-01' = { name: uniqueString(replace(namingConvention.functionApp, serviceToken, service), resourceGroup().id) location: location - tags: tags[?'Microsoft.Web/sites'] ?? {} + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Web/sites'] ?? {}, mlzTags) kind: 'functionapp' identity: { type: 'SystemAssigned' @@ -411,53 +388,13 @@ resource functionApp 'Microsoft.Web/sites@2023-01-01' = { name: 'WEBSITE_LOAD_USER_PROFILE' value: '1' } - { - name: 'BeginPeakTime' - value: scalingBeginPeakTime - } - { - name: 'EndPeakTime' - value: scalingEndPeakTime - } - { - name: 'EnvironmentName' - value: environment().name - } { name: 'FileShareName' value: 'profile-containers' } - { - name: 'HostPoolName' - value: hostPoolName - } - { - name: 'HostPoolResourceGroupName' - value: resourceGroupControlPlane - } - { - name: 'LimitSecondsToForceLogOffUser' - value: scalingLimitSecondsToForceLogOffUser - } - { - name: 'LogOffMessageBody' - value: 'This session is about to be logged off. Please save your work.' - } - { - name: 'LogOffMessageTitle' - value: 'Session Log Off' - } - { - name: 'MaintenanceTagName' - value: 'Maintenance' - } - { - name: 'MinimumNumberOfRDSH' - value: scalingMinimumNumberOfRdsh - } { name: 'ResourceGroupName' - value: resourceGroupStorage + value: resourceGroupProfiles } { name: 'ResourceManagerUrl' @@ -466,10 +403,6 @@ resource functionApp 'Microsoft.Web/sites@2023-01-01' = { ? environment().resourceManager : '${environment().resourceManager}/' } - { - name: 'SessionThresholdPerCPU' - value: scalingSessionThresholdPerCPU - } { name: 'StorageSuffix' value: environment().suffixes.storage @@ -478,14 +411,6 @@ resource functionApp 'Microsoft.Web/sites@2023-01-01' = { name: 'SubscriptionId' value: subscription().subscriptionId } - { - name: 'TenantId' - value: subscription().tenantId - } - { - name: 'TimeDifference' - value: timeDifference - } ], enableApplicationInsights ? [ { name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' @@ -502,7 +427,7 @@ resource functionApp 'Microsoft.Web/sites@2023-01-01' = { } ftpsState: 'Disabled' netFrameworkVersion: 'v6.0' - powerShellVersion: '7.2' + powerShellVersion: '7.4' publicNetworkAccess: 'Disabled' use32BitWorkerProcess: false } @@ -515,6 +440,7 @@ resource functionApp 'Microsoft.Web/sites@2023-01-01' = { resource privateEndpoint_functionApp 'Microsoft.Network/privateEndpoints@2023-04-01' = { name: replace(namingConvention.functionAppPrivateEndpoint, serviceToken, service) location: location + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Network/privateEndpoints'] ?? {}, mlzTags) properties: { customNetworkInterfaceName: replace(namingConvention.functionAppNetworkInterface, serviceToken, service) privateLinkServiceConnections: [ @@ -550,18 +476,7 @@ resource privateDnsZoneGroup_functionApp 'Microsoft.Network/privateEndpoints/pri } } -module roleAssignments_resourceGroups '../common/roleAssignments/resourceGroup.bicep' = [ - for i in range(0, length(roleAssignments)): { - name: 'set-role-assignment-${i}-${deploymentNameSuffix}' - scope: resourceGroup(roleAssignments[i].scope) - params: { - principalId: functionApp.identity.principalId - principalType: 'ServicePrincipal' - roleDefinitionId: roleAssignments[i].roleDefinitionId - } - } -] - +// Required role assignment to support the zero trust deployment of a function app module roleAssignment_storageAccount '../common/roleAssignments/storageAccount.bicep' = { name: 'set-role-assignment-storage-${deploymentNameSuffix}' params: { @@ -583,4 +498,28 @@ module scmARecord 'aRecord.bicep' = { } } +resource function 'Microsoft.Web/sites/functions@2020-12-01' = { + parent: functionApp + name: 'auto-increase-file-share-quota' + properties: { + config: { + disabled: false + bindings: [ + { + name: 'Timer' + type: 'timerTrigger' + direction: 'in' + schedule: '0 */15 * * * *' + } + ] + } + files: { + 'requirements.psd1': loadTextContent('../../artifacts/auto-increase-file-share/requirements.psd1') + 'run.ps1': loadTextContent('../../artifacts/auto-increase-file-share/run.ps1') + '../profile.ps1': loadTextContent('../../artifacts/auto-increase-file-share/profile.ps1') + } + } +} + output functionAppName string = functionApp.name +output functionAppPrincipalId string = functionApp.identity.principalId diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/controlPlane/hostPool.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/management/hostPool.bicep similarity index 78% rename from src/bicep/add-ons/azure-virtual-desktop/modules/controlPlane/hostPool.bicep rename to src/bicep/add-ons/azure-virtual-desktop/modules/management/hostPool.bicep index e289ee130..40ba676bc 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/controlPlane/hostPool.bicep +++ b/src/bicep/add-ons/azure-virtual-desktop/modules/management/hostPool.bicep @@ -27,20 +27,18 @@ param time string = utcNow('u') param validationEnvironment bool param virtualMachineSize string -var customRdpProperty_Complete = contains(activeDirectorySolution, 'MicrosoftEntraId') ? '${customRdpProperty}targetisaadjoined:i:1;enablerdsaadauth:i:1;' : customRdpProperty +var customRdpProperty_Complete = contains(activeDirectorySolution, 'MicrosoftEntraId') ? '${customRdpProperty}enablerdsaadauth:i:1;' : customRdpProperty resource hostPool 'Microsoft.DesktopVirtualization/hostPools@2023-09-05' = { name: hostPoolName location: location - tags: union({ - 'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroup().name}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}' - }, contains(tags, 'Microsoft.DesktopVirtualization/hostPools') ? tags['Microsoft.DesktopVirtualization/hostPools'] : {}, mlzTags) + tags: union({'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroup().name}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}'}, tags[?'Microsoft.DesktopVirtualization/hostPools'] ?? {}, mlzTags) properties: { customRdpProperty: customRdpProperty_Complete - hostPoolType: split(hostPoolType, ' ')[0] - loadBalancerType: contains(hostPoolType, 'Pooled') ? split(hostPoolType, ' ')[1] : 'Persistent' + hostPoolType: hostPoolType + loadBalancerType: hostPoolType == 'Pooled' ? 'DepthFirst' : 'Persistent' maxSessionLimit: maxSessionLimit - personalDesktopAssignmentType: contains(hostPoolType, 'Personal') ? split(hostPoolType, ' ')[1] : null + personalDesktopAssignmentType: hostPoolType == 'Personal' ? 'Automatic' : null preferredAppGroupType: 'Desktop' publicNetworkAccess: hostPoolPublicNetworkAccess registrationInfo: { @@ -50,16 +48,13 @@ resource hostPool 'Microsoft.DesktopVirtualization/hostPools@2023-09-05' = { startVMOnConnect: true validationEnvironment: validationEnvironment vmTemplate: '{"domain":"${domainName}","galleryImageOffer":${galleryImageOffer},"galleryImagePublisher":${galleryImagePublisher},"galleryImageSKU":${galleryImageSku},"imageType":${imageType},"customImageId":${customImageId},"namePrefix":"${sessionHostNamePrefix}","osDiskType":"${diskSku}","vmSize":{"id":"${virtualMachineSize}","cores":null,"ram":null,"rdmaEnabled": false,"supportsMemoryPreservingMaintenance": true},"galleryItemId":${galleryItemId},"hibernate":false,"diskSizeGB":0,"securityType":"TrustedLaunch","secureBoot":true,"vTPM":true,"vmInfrastructureType":"Cloud","virtualProcessorCount":null,"memoryGB":null,"maximumMemoryGB":null,"minimumMemoryGB":null,"dynamicMemoryConfig":false}' - } } resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-04-01' = { name: hostPoolPrivateEndpointName location: location - tags: union({ - 'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroup().name}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}' - }, contains(tags, 'Microsoft.Network/privateEndpoints') ? tags['Microsoft.Network/privateEndpoints'] : {}, mlzTags) + tags: union({'cm-resource-parent': hostPool.id}, tags[?'Microsoft.Network/privateEndpoints'] ?? {}, mlzTags) properties: { customNetworkInterfaceName: hostPoolNetworkInterfaceName privateLinkServiceConnections: [ diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/management/jobSchedules.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/management/jobSchedules.bicep deleted file mode 100644 index 0e5efb72e..000000000 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/management/jobSchedules.bicep +++ /dev/null @@ -1,56 +0,0 @@ -param automationAccountName string -param environment string -param fslogixContainerType string -param resourceGroupName string -param runbookName string -param storageAccountName string -param subscriptionId string -param timestamp string = utcNow('yyyyMMddHHmmss') - -resource automationAccount 'Microsoft.Automation/automationAccounts@2022-08-08' existing = { - name: automationAccountName -} - -resource jobSchedules_ProfileContainers 'Microsoft.Automation/automationAccounts/jobSchedules@2022-08-08' = [for i in range(0, 4): { - parent: automationAccount - #disable-next-line use-stable-resource-identifiers - name: guid(timestamp, runbookName, storageAccountName, 'ProfileContainers', string(i)) - properties: { - parameters: { - environment: environment - FileShareName: 'profile-containers' - resourceGroupName: resourceGroupName - storageAccountName: storageAccountName - subscriptionId: subscriptionId - } - runbook: { - name: runbookName - } - runOn: null - schedule: { - name: '${storageAccountName}_ProfileContainers_${(i + 1) * 15}min' - } - } -}] - -resource jobSchedules_OfficeContainers 'Microsoft.Automation/automationAccounts/jobSchedules@2022-08-08' = [for i in range(0, 4): if (contains(fslogixContainerType, 'Office')) { - parent: automationAccount - #disable-next-line use-stable-resource-identifiers - name: guid(timestamp, runbookName, storageAccountName, 'OfficeContainers', string(i)) - properties: { - parameters: { - environment: environment - FileShareName: 'office-containers' - resourceGroupName: resourceGroupName - storageAccountName: storageAccountName - subscriptionId: subscriptionId - } - runbook: { - name: runbookName - } - runOn: null - schedule: { - name: '${storageAccountName}_OfficeContainers_${(i + 1) * 15}min' - } - } -}] diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/management/management.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/management/management.bicep index c5838a94f..874468670 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/management/management.bicep +++ b/src/bicep/add-ons/azure-virtual-desktop/modules/management/management.bicep @@ -2,8 +2,12 @@ targetScope = 'subscription' param activeDirectorySolution string param avdObjectId string +param avdPrivateDnsZoneResourceId string +param customImageId string +param customRdpProperty string param deployFslogix bool param deploymentNameSuffix string +param desktopFriendlyName string param diskEncryptionSetResourceId string param diskSku string @secure() @@ -14,10 +18,17 @@ param enableApplicationInsights bool param enableAvdInsights bool param environmentAbbreviation string param fslogixStorageService string +param hostPoolPublicNetworkAccess string param hostPoolType string +param imageOffer string +param imagePublisher string +param imageSku string +param imageVersionResourceId string +param locationControlPlane string param locationVirtualMachines string param logAnalyticsWorkspaceRetention int param logAnalyticsWorkspaceSku string +param maxSessionLimit int param mlzTags object param namingConvention object param organizationalUnitPath string @@ -27,121 +38,154 @@ param privateLinkScopeResourceId string param recoveryServices bool param recoveryServicesGeo string param resourceAbbreviations object -param resourceGroupControlPlane string -param resourceGroupHosts string -param resourceGroupManagement string -param resourceGroupStorage string -param roleDefinitions object -param scalingBeginPeakTime string -param scalingEndPeakTime string -param scalingLimitSecondsToForceLogOffUser string -param scalingMinimumNumberOfRdsh string -param scalingSessionThresholdPerCPU string -param scalingTool bool +param resourceGroupName string +param resourceGroupProfiles string +param securityPrincipalObjectIds array param serviceToken string +param sessionHostNamePrefix string param storageService string param subnetResourceId string param subnets array param tags object -param timeDifference string param timeZone string +param validationEnvironment bool @secure() param virtualMachinePassword string +param virtualMachineSize string param virtualMachineUsername string +var galleryImageOffer = empty(imageVersionResourceId) ? '"${imageOffer}"' : 'null' +var galleryImagePublisher = empty(imageVersionResourceId) ? '"${imagePublisher}"' : 'null' +var galleryImageSku = empty(imageVersionResourceId) ? '"${imageSku}"' : 'null' +var galleryItemId = empty(imageVersionResourceId) ? '"${imagePublisher}.${imageOffer}${imageSku}"' : 'null' var hostPoolName = namingConvention.hostPool -var roleAssignments = union( - [ - { - roleDefinitionId: '86240b0e-9422-4c43-887b-b61143f32ba8' // Desktop Virtualization Application Group Contributor (Purpose: updates the friendly name for the desktop) - resourceGroup: resourceGroupControlPlane - subscription: subscription().subscriptionId - } - { - roleDefinitionId: '2ad6aaab-ead9-4eaa-8ac5-da422f562408' // Desktop Virtualization Session Host Operator (Purpose: sets drain mode on the AVD session hosts) - resourceGroup: resourceGroupControlPlane - subscription: subscription().subscriptionId - } - { - roleDefinitionId: 'a959dbd1-f747-45e3-8ba6-dd80f235f97c' // Desktop Virtualization Virtual Machine Contributor (Purpose: remove the management virtual machine) - resourceGroup: resourceGroupManagement - subscription: subscription().subscriptionId - } - ], - deployFslogix - ? [ - { - roleDefinitionId: '17d1049b-9a84-46fb-8f53-869881c3d3ab' // Storage Account Contributor (Purpose: domain join storage account & set NTFS permissions on the file share) - resourceGroup: resourceGroupStorage - subscription: subscription().subscriptionId - } - ] - : [] -) +var imageType = empty(imageVersionResourceId) ? '"Gallery"' : '"CustomImage"' var userAssignedIdentityNamePrefix = namingConvention.userAssignedIdentity -var virtualNetworkName = split(subnetResourceId, '/')[8] -var virtualNetworkResourceGroupName = split(subnetResourceId, '/')[4] + +resource resourceGroup 'Microsoft.Resources/resourceGroups@2023-07-01' = { + name: resourceGroupName + location: locationControlPlane + tags: union({'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupName}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}'}, tags[?'Microsoft.Resources/resourceGroups'] ?? {}, mlzTags) +} + +// Role Assignment for Autoscale +// Purpose: assigns the Desktop Virtualization Power On Off Contributor role to the +// Azure Virtual Desktop service to scale the host pool +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(avdObjectId, '40c5ff49-9181-41f8-ae61-143b0e78555e', subscription().id) + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '40c5ff49-9181-41f8-ae61-143b0e78555e') + principalId: avdObjectId + } +} + +// Monitoring Resources for AVD Insights +// This module deploys a Log Analytics Workspace with a Data Collection Rule +module monitoring 'monitoring.bicep' = if (enableApplicationInsights || enableAvdInsights) { + name: 'deploy-monitoring-${deploymentNameSuffix}' + scope: resourceGroup + params: { + deploymentNameSuffix: deploymentNameSuffix + enableAvdInsights: enableAvdInsights + hostPoolResourceId: '${subscription().id}}/resourceGroups/${resourceGroup.name}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}' + location: locationVirtualMachines + logAnalyticsWorkspaceRetention: logAnalyticsWorkspaceRetention + logAnalyticsWorkspaceSku: logAnalyticsWorkspaceSku + mlzTags: mlzTags + namingConvention: namingConvention + privateLinkScopeResourceId: privateLinkScopeResourceId + serviceToken: serviceToken + tags: tags + } +} + +module hostPool 'hostPool.bicep' = { + name: 'deploy-vdpool-${deploymentNameSuffix}' + scope: resourceGroup + params: { + activeDirectorySolution: activeDirectorySolution + avdPrivateDnsZoneResourceId: avdPrivateDnsZoneResourceId + customImageId: customImageId + customRdpProperty: customRdpProperty + diskSku: diskSku + domainName: domainName + enableAvdInsights: enableAvdInsights + galleryImageOffer: galleryImageOffer + galleryImagePublisher: galleryImagePublisher + galleryImageSku: galleryImageSku + galleryItemId: galleryItemId + hostPoolDiagnosticSettingName: namingConvention.hostPoolDiagnosticSetting + hostPoolName: hostPoolName + hostPoolNetworkInterfaceName: namingConvention.hostPoolNetworkInterface + hostPoolPrivateEndpointName: namingConvention.hostPoolPrivateEndpoint + hostPoolPublicNetworkAccess: hostPoolPublicNetworkAccess + hostPoolType: hostPoolType + imageType: imageType + location: locationControlPlane + logAnalyticsWorkspaceResourceId: monitoring.outputs.logAnalyticsWorkspaceResourceId + maxSessionLimit: maxSessionLimit + mlzTags: mlzTags + sessionHostNamePrefix: sessionHostNamePrefix + subnetResourceId: subnetResourceId + tags: tags + validationEnvironment: validationEnvironment + virtualMachineSize: virtualMachineSize + } +} module diskAccess 'diskAccess.bicep' = { - scope: resourceGroup(resourceGroupManagement) + scope: resourceGroup name: 'deploy-disk-access-${deploymentNameSuffix}' params: { - hostPoolName: hostPoolName + hostPoolResourceId: hostPool.outputs.resourceId location: locationVirtualMachines mlzTags: mlzTags namingConvention: namingConvention - resourceGroupControlPlane: resourceGroupControlPlane subnetResourceId: subnetResourceId tags: tags } } // Sets an Azure policy to disable public network access to managed disks -module policy 'policy.bicep' = { +module policy '../management/policy.bicep' = { name: 'deploy-policy-disks-${deploymentNameSuffix}' params: { diskAccessResourceId: diskAccess.outputs.resourceId - location: locationVirtualMachines - resourceGroupName: resourceGroupHosts + location: locationControlPlane + resourceGroupName: resourceGroup.name } } module deploymentUserAssignedIdentity 'userAssignedIdentity.bicep' = { - scope: resourceGroup(resourceGroupManagement) + scope: resourceGroup name: 'deploy-id-deployment-${deploymentNameSuffix}' params: { location: locationVirtualMachines name: replace(userAssignedIdentityNamePrefix, serviceToken, 'deployment') - tags: union( - { - 'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}' - }, - contains(tags, 'Microsoft.ManagedIdentity/userAssignedIdentities') - ? tags['Microsoft.ManagedIdentity/userAssignedIdentities'] - : {}, - mlzTags - ) + tags: union({'cm-resource-parent': hostPool.outputs.resourceId}, tags[?'Microsoft.ManagedIdentity/userAssignedIdentities'] ?? {}, mlzTags) } } -module roleAssignments_deployment '../common/roleAssignments/resourceGroup.bicep' = [ - for i in range(0, length(roleAssignments)): { - scope: resourceGroup(roleAssignments[i].subscription, roleAssignments[i].resourceGroup) - name: 'deploy-role-assignment-${i}-${deploymentNameSuffix}' - params: { - principalId: deploymentUserAssignedIdentity.outputs.principalId - principalType: 'ServicePrincipal' - roleDefinitionId: roleAssignments[i].roleDefinitionId - } +// Role Assignment for the AVD host pool +// Purpose: assigns the Desktop Virtualization Contributor role to the managed identity on the +// management virtual machine to set the drain mode on the AVD session hosts and manage the scaling plan +module roleAssignment_Management '../common/roleAssignments/resourceGroup.bicep' = { + name: 'assign-role-mgmt-${deploymentNameSuffix}' + scope: resourceGroup + params: { + principalId: deploymentUserAssignedIdentity.outputs.principalId + principalType: 'ServicePrincipal' + roleDefinitionId: '082f0a83-3be5-4ba1-904c-961cca79b387' } -] +} -// Management VM -// The management VM is required to execute PowerShell scripts. +// Management Virtual Machine +// Purpose: deploys the management VM is required to execute PowerShell scripts. module virtualMachine 'virtualMachine.bicep' = { name: 'deploy-mgmt-vm-${deploymentNameSuffix}' - scope: resourceGroup(resourceGroupManagement) + scope: resourceGroup params: { + deploymentUserAssignedIdentityPrincipalId: deploymentUserAssignedIdentity.outputs.principalId deploymentUserAssignedIdentityResourceId: deploymentUserAssignedIdentity.outputs.resourceId diskEncryptionSetResourceId: diskEncryptionSetResourceId diskName: replace(namingConvention.virtualMachineDisk, serviceToken, 'mgt') @@ -149,125 +193,96 @@ module virtualMachine 'virtualMachine.bicep' = { domainJoinPassword: domainJoinPassword domainJoinUserPrincipalName: domainJoinUserPrincipalName domainName: domainName - hostPoolName: hostPoolName + hostPoolResourceId: hostPool.outputs.resourceId location: locationVirtualMachines mlzTags: mlzTags networkInterfaceName: replace(namingConvention.virtualMachineNetworkInterface, serviceToken, 'mgt') organizationalUnitPath: organizationalUnitPath - resourceGroupControlPlane: resourceGroupControlPlane - subnet: split(subnetResourceId, '/')[10] + subnetResourceId: subnetResourceId tags: tags virtualMachineName: replace(namingConvention.virtualMachine, serviceToken, 'mgt') virtualMachinePassword: virtualMachinePassword virtualMachineUsername: virtualMachineUsername - virtualNetwork: virtualNetworkName - virtualNetworkResourceGroup: virtualNetworkResourceGroupName } } -// Role Assignment required for Start VM On Connect -resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(avdObjectId, roleDefinitions.DesktopVirtualizationPowerOnContributor, subscription().id) - properties: { - roleDefinitionId: resourceId( - 'Microsoft.Authorization/roleDefinitions', - roleDefinitions.DesktopVirtualizationPowerOnContributor - ) - principalId: avdObjectId +module applicationGroup 'applicationGroup.bicep' = { + name: 'deploy-vdag-${deploymentNameSuffix}' + scope: resourceGroup + params: { + deploymentNameSuffix: deploymentNameSuffix + deploymentUserAssignedIdentityClientId: deploymentUserAssignedIdentity.outputs.clientId + desktopApplicationGroupName: namingConvention.applicationGroup + hostPoolResourceId: hostPool.outputs.resourceId + locationControlPlane: locationControlPlane + locationVirtualMachines: locationVirtualMachines + mlzTags: mlzTags + securityPrincipalObjectIds: securityPrincipalObjectIds + desktopFriendlyName: desktopFriendlyName + tags: tags + virtualMachineName: virtualMachine.outputs.name } } -// Monitoring Resources for AVD Insights -// This module deploys a Log Analytics Workspace with a Data Collection Rule -module monitoring 'monitoring.bicep' = if (enableApplicationInsights || enableAvdInsights) { - name: 'deploy-monitoring-${deploymentNameSuffix}' - scope: resourceGroup(resourceGroupManagement) +module recoveryServicesVault 'recoveryServicesVault.bicep' = if (recoveryServices) { + name: 'deploy-rsv-${deploymentNameSuffix}' + scope: resourceGroup params: { - deploymentNameSuffix: deploymentNameSuffix - enableAvdInsights: enableAvdInsights - hostPoolName: hostPoolName + azureBlobsPrivateDnsZoneResourceId: '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'blob'))[0]}' + azureQueueStoragePrivateDnsZoneResourceId: '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'queue'))[0]}' + deployFslogix: deployFslogix + hostPoolResourceId: hostPool.outputs.resourceId location: locationVirtualMachines - logAnalyticsWorkspaceRetention: logAnalyticsWorkspaceRetention - logAnalyticsWorkspaceSku: logAnalyticsWorkspaceSku mlzTags: mlzTags - namingConvention: namingConvention - privateLinkScopeResourceId: privateLinkScopeResourceId - resourceGroupControlPlane: resourceGroupControlPlane - serviceToken: serviceToken + recoveryServicesPrivateDnsZoneResourceId: '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => startsWith(name, 'privatelink.${recoveryServicesGeo}.backup.windowsazure'))[0]}' + recoveryServicesVaultName: namingConvention.recoveryServicesVault + recoveryServicesVaultNetworkInterfaceName: namingConvention.recoveryServicesVaultNetworkInterface + recoveryServicesVaultPrivateEndpointName: namingConvention.recoveryServicesVaultPrivateEndpoint + storageService: storageService + subnetId: subnetResourceId tags: tags + timeZone: timeZone } } -module functionApp 'functionApp.bicep' = if (scalingTool || fslogixStorageService == 'AzureFiles Premium') { +// Deploys the Auto Increase Premium File Share Quota solution on an Azure Function App +module functionApp '../management/functionApp.bicep' = if (fslogixStorageService == 'AzureFiles Premium') { name: 'deploy-function-app-${deploymentNameSuffix}' - scope: resourceGroup(resourceGroupManagement) + scope: resourceGroup params: { delegatedSubnetResourceId: filter(subnets, subnet => contains(subnet.name, 'FunctionAppOutbound'))[0].id - deployFslogix: deployFslogix deploymentNameSuffix: deploymentNameSuffix enableApplicationInsights: enableApplicationInsights environmentAbbreviation: environmentAbbreviation - hostPoolName: hostPoolName + hostPoolResourceId: hostPool.outputs.resourceId logAnalyticsWorkspaceResourceId: monitoring.outputs.logAnalyticsWorkspaceResourceId + mlzTags: mlzTags namingConvention: namingConvention privateDnsZoneResourceIdPrefix: privateDnsZoneResourceIdPrefix privateDnsZones: privateDnsZones privateLinkScopeResourceId: privateLinkScopeResourceId resourceAbbreviations: resourceAbbreviations - resourceGroupControlPlane: resourceGroupControlPlane - resourceGroupHosts: resourceGroupHosts - resourceGroupStorage: resourceGroupStorage - scalingBeginPeakTime: scalingBeginPeakTime - scalingEndPeakTime:scalingEndPeakTime - scalingLimitSecondsToForceLogOffUser: scalingLimitSecondsToForceLogOffUser - scalingMinimumNumberOfRdsh: scalingMinimumNumberOfRdsh - scalingSessionThresholdPerCPU: scalingSessionThresholdPerCPU + resourceGroupProfiles: resourceGroupProfiles serviceToken: serviceToken subnetResourceId: subnetResourceId tags: tags - timeDifference: timeDifference - } -} - -module recoveryServicesVault 'recoveryServicesVault.bicep' = if (recoveryServices && ((contains( - activeDirectorySolution, - 'DomainServices' -) && contains(hostPoolType, 'Pooled') && contains(fslogixStorageService, 'AzureFiles')) || contains( - hostPoolType, - 'Personal' -))) { - name: 'deploy-rsv-${deploymentNameSuffix}' - scope: resourceGroup(resourceGroupManagement) - params: { - azureBlobsPrivateDnsZoneResourceId: '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'blob'))[0]}' - azureQueueStoragePrivateDnsZoneResourceId: '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'queue'))[0]}' - deployFslogix: deployFslogix - hostPoolName: hostPoolName - location: locationVirtualMachines - mlzTags: mlzTags - recoveryServicesPrivateDnsZoneResourceId: '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => startsWith(name, 'privatelink.${recoveryServicesGeo}.backup.windowsazure'))[0]}' - recoveryServicesVaultName: namingConvention.recoveryServicesVault - recoveryServicesVaultNetworkInterfaceName: namingConvention.recoveryServicesVaultNetworkInterface - recoveryServicesVaultPrivateEndpointName: namingConvention.recoveryServicesVaultPrivateEndpoint - resourceGroupControlPlane: resourceGroupControlPlane - storageService: storageService - subnetId: subnetResourceId - tags: tags - timeZone: timeZone } } +output applicationGroupResourceId string = applicationGroup.outputs.resourceId output dataCollectionRuleResourceId string = enableAvdInsights ? monitoring.outputs.dataCollectionRuleResourceId : '' output deploymentUserAssignedIdentityClientId string = deploymentUserAssignedIdentity.outputs.clientId output deploymentUserAssignedIdentityPrincipalId string = deploymentUserAssignedIdentity.outputs.principalId output deploymentUserAssignedIdentityResourceId string = deploymentUserAssignedIdentity.outputs.resourceId -output functionAppName string = scalingTool || fslogixStorageService == 'AzureFiles Premium' ? functionApp.outputs.functionAppName : '' +output diskAccessPolicyDefinitionId string = policy.outputs.policyDefinitionId +output diskAccessPolicyDisplayName string = policy.outputs.policyDisplayName +output diskAccessResourceId string = diskAccess.outputs.resourceId +output functionAppPrincipalId string = fslogixStorageService == 'AzureFiles Premium' ? functionApp.outputs.functionAppPrincipalId : '' +output hostPoolName string = hostPool.outputs.name +output hostPoolResourceId string = hostPool.outputs.resourceId output logAnalyticsWorkspaceName string = enableApplicationInsights || enableAvdInsights ? monitoring.outputs.logAnalyticsWorkspaceName : '' -output logAnalyticsWorkspaceResourceId string = enableApplicationInsights || enableAvdInsights - ? monitoring.outputs.logAnalyticsWorkspaceResourceId - : '' -output recoveryServicesVaultName string = recoveryServices && ((contains(activeDirectorySolution, 'DomainServices') && contains(hostPoolType,'Pooled') && contains(fslogixStorageService, 'AzureFiles')) || contains(hostPoolType, 'Personal')) - ? recoveryServicesVault.outputs.name - : '' +output logAnalyticsWorkspaceResourceId string = enableApplicationInsights || enableAvdInsights ? monitoring.outputs.logAnalyticsWorkspaceResourceId : '' +output recoveryServicesVaultName string = recoveryServices ? recoveryServicesVault.outputs.name : '' +output resourceGroupName string = resourceGroup.name output virtualMachineName string = virtualMachine.outputs.name output virtualMachineResourceId string = virtualMachine.outputs.resourceId diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/management/monitoring.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/management/monitoring.bicep index 61c77a780..8490569b7 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/management/monitoring.bicep +++ b/src/bicep/add-ons/azure-virtual-desktop/modules/management/monitoring.bicep @@ -1,13 +1,12 @@ param deploymentNameSuffix string param enableAvdInsights bool -param hostPoolName string +param hostPoolResourceId string param location string param logAnalyticsWorkspaceRetention int param logAnalyticsWorkspaceSku string param mlzTags object param namingConvention object param privateLinkScopeResourceId string -param resourceGroupControlPlane string param service string = 'mgmt' param serviceToken string param tags object @@ -15,9 +14,7 @@ param tags object resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-06-01' = { name: replace(namingConvention.logAnalyticsWorkspace, serviceToken, service) location: location - tags: union({ - 'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}' - }, contains(tags, 'Microsoft.OperationalInsights/workspaces') ? tags['Microsoft.OperationalInsights/workspaces'] : {}, mlzTags) + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.OperationalInsights/workspaces'] ?? {}, mlzTags) properties: { sku: { name: logAnalyticsWorkspaceSku @@ -43,9 +40,7 @@ module privateLinkScope_logAnalyticsWorkspace 'privateLinkScope.bicep' = { resource dataCollectionRule 'Microsoft.Insights/dataCollectionRules@2022-06-01' = if (enableAvdInsights) { name: 'microsoft-avdi-${replace(namingConvention.dataCollectionRule, serviceToken, service)}' // The name must start with 'microsoft-avdi-' for proper integration with AVD Insights location: location - tags: union({ - 'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}' - }, contains(tags, 'Microsoft.Insights/dataCollectionRules') ? tags['Microsoft.Insights/dataCollectionRules'] : {}, mlzTags) + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Insights/dataCollectionRules'] ?? {}, mlzTags) kind: 'Windows' properties: { dataSources: { @@ -131,9 +126,7 @@ resource dataCollectionRule 'Microsoft.Insights/dataCollectionRules@2022-06-01' resource dataCollectionEndpoint 'Microsoft.Insights/dataCollectionEndpoints@2021-04-01' = if (enableAvdInsights) { name: replace(namingConvention.dataCollectionEndpoint, serviceToken, service) location: location - tags: union({ - 'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}' - }, contains(tags, 'Microsoft.Insights/dataCollectionEndpoints') ? tags['Microsoft.Insights/dataCollectionEndpoints'] : {}, mlzTags) + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Insights/dataCollectionEndpoints'] ?? {}, mlzTags) kind: 'Windows' properties: { networkAcls: { @@ -154,4 +147,3 @@ module privateLinkScope_dataCollectionEndpoint 'privateLinkScope.bicep' = if (en output logAnalyticsWorkspaceName string = logAnalyticsWorkspace.name output logAnalyticsWorkspaceResourceId string = logAnalyticsWorkspace.id output dataCollectionRuleResourceId string = enableAvdInsights ? dataCollectionRule.id : '' - diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/management/policy.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/management/policy.bicep index 6d0268c61..fc565c4a9 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/management/policy.bicep +++ b/src/bicep/add-ons/azure-virtual-desktop/modules/management/policy.bicep @@ -83,3 +83,6 @@ module policyAssignment 'policyAssignment.bicep' = { policyName: policyDefinition.properties.displayName } } + +output policyDefinitionId string = policyDefinition.id +output policyDisplayName string = policyDefinition.properties.displayName diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/management/recoveryServicesVault.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/management/recoveryServicesVault.bicep index 07545a3fd..5e3d85ec8 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/management/recoveryServicesVault.bicep +++ b/src/bicep/add-ons/azure-virtual-desktop/modules/management/recoveryServicesVault.bicep @@ -1,14 +1,13 @@ param azureBlobsPrivateDnsZoneResourceId string param azureQueueStoragePrivateDnsZoneResourceId string param deployFslogix bool -param hostPoolName string +param hostPoolResourceId string param location string param mlzTags object param recoveryServicesPrivateDnsZoneResourceId string param recoveryServicesVaultName string param recoveryServicesVaultNetworkInterfaceName string param recoveryServicesVaultPrivateEndpointName string -param resourceGroupControlPlane string param storageService string param subnetId string param tags object @@ -17,9 +16,7 @@ param timeZone string resource vault 'Microsoft.RecoveryServices/vaults@2022-03-01' = { name: recoveryServicesVaultName location: location - tags: union({ - 'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}' - }, contains(tags, 'Microsoft.RecoveryServices/vaults') ? tags['Microsoft.RecoveryServices/vaults'] : {}, mlzTags) + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.RecoveryServices/vaults'] ?? {}, mlzTags) sku: { name: 'RS0' tier: 'Standard' @@ -31,9 +28,7 @@ resource backupPolicy_Storage 'Microsoft.RecoveryServices/vaults/backupPolicies@ parent: vault name: 'AvdPolicyStorage' location: location - tags: union({ - 'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}' - }, contains(tags, 'Microsoft.RecoveryServices/vaults') ? tags['Microsoft.RecoveryServices/vaults'] : {}, mlzTags) + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.RecoveryServices/vaults'] ?? {}, mlzTags) properties: { backupManagementType: 'AzureStorage' schedulePolicy: { @@ -64,9 +59,7 @@ resource backupPolicy_Vm 'Microsoft.RecoveryServices/vaults/backupPolicies@2022- parent: vault name: 'AvdPolicyVm' location: location - tags: union({ - 'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}' - }, contains(tags, 'Microsoft.RecoveryServices/vaults') ? tags['Microsoft.RecoveryServices/vaults'] : {}, mlzTags) + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.RecoveryServices/vaults'] ?? {}, mlzTags) properties: { backupManagementType: 'AzureIaasVM' instantRpRetentionRangeInDays: 2 @@ -99,9 +92,7 @@ resource backupPolicy_Vm 'Microsoft.RecoveryServices/vaults/backupPolicies@2022- resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-04-01' = { name: recoveryServicesVaultPrivateEndpointName location: location - tags: union({ - 'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}' - }, contains(tags, 'Microsoft.Network/privateEndpoints') ? tags['Microsoft.Network/privateEndpoints'] : {}, mlzTags) + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Network/privateEndpoints'] ?? {}, mlzTags) properties: { customNetworkInterfaceName: recoveryServicesVaultNetworkInterfaceName privateLinkServiceConnections: [ diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/management/scalingPlan.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/management/scalingPlan.bicep new file mode 100644 index 000000000..b9f38bdbd --- /dev/null +++ b/src/bicep/add-ons/azure-virtual-desktop/modules/management/scalingPlan.bicep @@ -0,0 +1,276 @@ +@description('Required. Principal ID of the user assigned identity.') +param deploymentUserAssignedIdentityPrincipalId string + +@description('Optional. Enable AVD Insights.') +param enableAvdInsights bool = true + +@description('Required. Host pool resource ID for the Scaling Plan.') +param hostPoolResourceId string + +@description('Required. Location of the Scaling Plan. The location must match the location of the host pool.') +param location string + +@description('Optional. Resource ID of the Log Analytics workspace for the Scaling Plan.') +param logAnalyticsWorkspaceResourceId string = '' + +@allowed([ + 'Personal' + 'Pooled' +]) +@description('Required. Host pool type of the Scaling Plan.') +param hostPoolType string + +@description('Required. Name of the diagnostic setting for the Scaling Plan.') +param scalingPlanDiagnosticSettingName string + +@description('Required. Name of the Scaling Plan.') +param scalingPlanName string + +@description('Optional. Supported clouds.') +param supportedClouds array = [ + 'AzureCloud' + 'AzureUSGovernment' +] + +@description('Optional. Tags of the resource.') +param tags object = {} + +@description('Optional. Time zone of the Scaling Plan. Defaults to UTC.') +param timeZone string = 'UTC' + +@description('Required. Off peak start time for weekdays in HH:mm format.') +param weekdaysOffPeakStartTime string + +@description('Required. Off peak start time for weekends in HH:mm format.') +param weekendsOffPeakStartTime string + +@description('Required. Peak start time for weekdays in HH:mm format.') +param weekdaysPeakStartTime string + +@description('Required. Peak start time for weekends in HH:mm format.') +param weekendsPeakStartTime string + +var schedules = hostPoolType == 'Pooled' ? [ + { + daysOfWeek: [ + 'Monday' + 'Tuesday' + 'Wednesday' + 'Thursday' + 'Friday' + ] + offPeakLoadBalancingAlgorithm: 'DepthFirst' + offPeakStartTime: { + hour: split(weekdaysOffPeakStartTime, ':')[0] + minute: split(weekdaysOffPeakStartTime, ':')[1] + } + peakLoadBalancingAlgorithm: 'BreadthFirst' + peakStartTime: { + hour: split(weekdaysPeakStartTime, ':')[0] + minute: split(weekdaysPeakStartTime, ':')[1] + } + rampDownCapacityThresholdPct: 90 + rampDownForceLogoffUsers: false + rampDownLoadBalancingAlgorithm: 'DepthFirst' + rampDownMinimumHostsPct: 0 + rampDownNotificationMessage: 'Ramping down the AVD session hosts to support low demand.' + rampDownStartTime: { + hour: int(split(weekdaysOffPeakStartTime, ':')[0]) - 1 + minute: split(weekdaysOffPeakStartTime, ':')[1] + } + rampDownStopHostsWhen: 'ZeroSessions' + rampDownWaitTimeMinutes: 0 + rampUpCapacityThresholdPct: 70 + rampUpLoadBalancingAlgorithm: 'BreadthFirst' + rampUpMinimumHostsPct: 25 + rampUpStartTime: { + hour: int(split(weekdaysPeakStartTime, ':')[0]) - 1 + minute: split(weekdaysPeakStartTime, ':')[1] + } + } + { + daysOfWeek: [ + 'Saturday' + 'Sunday' + ] + offPeakLoadBalancingAlgorithm: 'DepthFirst' + offPeakStartTime: { + hour: split(weekendsOffPeakStartTime, ':')[0] + minute: split(weekendsOffPeakStartTime, ':')[1] + } + peakLoadBalancingAlgorithm: 'BreadthFirst' + peakStartTime: { + hour: split(weekendsPeakStartTime, ':')[0] + minute: split(weekendsPeakStartTime, ':')[1] + } + rampDownCapacityThresholdPct: 90 + rampDownForceLogoffUsers: false + rampDownLoadBalancingAlgorithm: 'DepthFirst' + rampDownMinimumHostsPct: 0 + rampDownNotificationMessage: 'Ramping down the AVD session hosts to support low demand.' + rampDownStartTime: { + hour: int(split(weekendsOffPeakStartTime, ':')[0]) - 1 + minute: split(weekendsOffPeakStartTime, ':')[1] + } + rampDownStopHostsWhen: 'ZeroSessions' + rampDownWaitTimeMinutes: 0 + rampUpCapacityThresholdPct: 90 + rampUpLoadBalancingAlgorithm: 'BreadthFirst' + rampUpMinimumHostsPct: 25 + rampUpStartTime: { + hour: int(split(weekendsPeakStartTime, ':')[0]) - 1 + minute: split(weekendsPeakStartTime, ':')[1] + } + } +] : [ + { + daysOfWeek: [ + 'Monday' + 'Tuesday' + 'Wednesday' + 'Thursday' + 'Friday' + ] + offPeakActionOnDisconnect: 'None' + offPeakActionOnLogoff: 'Deallocate' + offPeakMinutesToWaitOnDisconnect: 0 + offPeakMinutesToWaitOnLogoff: 0 + offPeakStartTime: { + hour: split(weekdaysOffPeakStartTime, ':')[0] + minute: split(weekdaysOffPeakStartTime, ':')[1] + } + offPeakStartVMOnConnect: 'Enable' + peakActionOnDisconnect: 'None' + peakActionOnLogoff: 'Deallocate' + peakMinutesToWaitOnDisconnect: 0 + peakMinutesToWaitOnLogoff: 0 + peakStartTime: { + hour: split(weekdaysPeakStartTime, ':')[0] + minute: split(weekdaysPeakStartTime, ':')[1] + } + peakStartVMOnConnect: 'Enable' + rampDownActionOnDisconnect: 'None' + rampDownActionOnLogoff: 'Deallocate' + rampDownMinutesToWaitOnDisconnect: 0 + rampDownMinutesToWaitOnLogoff: 0 + rampDownStartTime: { + hour: int(split(weekdaysOffPeakStartTime, ':')[0]) - 1 + minute: split(weekdaysOffPeakStartTime, ':')[1] + } + rampDownStartVMOnConnect: 'Enable' + rampUpActionOnDisconnect: 'None' + rampUpActionOnLogoff: 'None' + rampUpAutoStartHosts: 'WithAssignedUser' + rampUpMinutesToWaitOnDisconnect: 0 + rampUpMinutesToWaitOnLogoff: 0 + rampUpStartTime: { + hour: int(split(weekdaysPeakStartTime, ':')[0]) - 1 + minute: split(weekdaysPeakStartTime, ':')[1] + } + rampUpStartVMOnConnect: 'Enable' + } + { + daysOfWeek: [ + 'Saturday' + 'Sunday' + ] + rampUpStartTime: { + hour: int(split(weekendsPeakStartTime, ':')[0]) - 1 + minute: split(weekendsPeakStartTime, ':')[1] + } + peakStartTime: { + hour: split(weekendsPeakStartTime, ':')[0] + minute: split(weekendsPeakStartTime, ':')[1] + } + peakMinutesToWaitOnDisconnect: 0 + peakActionOnDisconnect: 'None' + peakMinutesToWaitOnLogoff: 0 + peakActionOnLogoff: 'Deallocate' + peakStartVMOnConnect: 'Enable' + rampDownStartTime: { + hour: int(split(weekendsOffPeakStartTime, ':')[0]) - 1 + minute: split(weekendsOffPeakStartTime, ':')[1] + } + rampDownMinutesToWaitOnDisconnect: 0 + rampDownActionOnDisconnect: 'None' + rampDownMinutesToWaitOnLogoff: 0 + rampDownActionOnLogoff: 'Deallocate' + rampDownStartVMOnConnect: 'Enable' + rampUpAutoStartHosts: 'WithAssignedUser' + rampUpStartVMOnConnect: 'Enable' + rampUpMinutesToWaitOnDisconnect: 0 + rampUpActionOnDisconnect: 'None' + rampUpMinutesToWaitOnLogoff: 0 + rampUpActionOnLogoff: 'None' + offPeakStartTime: { + hour: split(weekendsOffPeakStartTime, ':')[0] + minute: split(weekendsOffPeakStartTime, ':')[1] + } + offPeakMinutesToWaitOnDisconnect: 0 + offPeakActionOnDisconnect: 'None' + offPeakMinutesToWaitOnLogoff: 0 + offPeakActionOnLogoff: 'Deallocate' + offPeakStartVMOnConnect: 'Enable' + } +] + +resource scalingPlan 'Microsoft.DesktopVirtualization/scalingPlans@2023-09-05' = { + name: scalingPlanName + location: location + tags: tags + properties: { + timeZone: timeZone + hostPoolType: hostPoolType + exclusionTag: 'excludeFromAutoscale' + schedules: [] + hostPoolReferences: [ + { + hostPoolArmPath: hostPoolResourceId + scalingPlanEnabled: true + } + ] + } +} + +resource schedules_Pooled 'Microsoft.DesktopVirtualization/scalingPlans/pooledSchedules@2023-09-05' = [for i in range(0, length(schedules)): if (hostPoolType == 'Pooled') { + name: i == 0 ? 'Weekdays' : 'Weekends' + parent: scalingPlan + properties: schedules[i] +}] + +resource schedule_Personal 'Microsoft.DesktopVirtualization/scalingPlans/personalSchedules@2023-09-05' = [for i in range(0, length(schedules)): if (hostPoolType == 'Personal') { + name: i == 0 ? 'Weekdays' : 'Weekends' + parent: scalingPlan + properties: schedules[i] +}] + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(deploymentUserAssignedIdentityPrincipalId, '082f0a83-3be5-4ba1-904c-961cca79b387', scalingPlan.id) + scope: scalingPlan + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '082f0a83-3be5-4ba1-904c-961cca79b387') // Desktop Virtualization Contributor (Purpose: disable scaling plan when adding new hosts) + principalId: deploymentUserAssignedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + dependsOn: [ + schedule_Personal + schedules_Pooled + ] +} + +resource diagnosticSetting 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (enableAvdInsights && contains(supportedClouds, environment().name)) { + name: scalingPlanDiagnosticSettingName + properties: { + logs: [ + { + categoryGroup: 'allLogs' + enabled: true + } + ] + workspaceId: logAnalyticsWorkspaceResourceId + } + scope: scalingPlan + dependsOn: [ + roleAssignment + ] +} diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/management/scalingTool.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/management/scalingTool.bicep deleted file mode 100644 index cbdc32d71..000000000 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/management/scalingTool.bicep +++ /dev/null @@ -1,561 +0,0 @@ -param beginPeakTime string = '08:00' -param delegatedSubnetResourceId string -param endPeakTime string = '17:00' -param environmentAbbreviation string -param hostPoolName string -param hostPoolResourceGroupName string -param keyExpirationInDays int = 30 -param limitSecondsToForceLogOffUser string = '0' -param location string = resourceGroup().location -param logAnalyticsWorkspaceResourceId string = '' -param minimumNumberOfRdsh string = '0' -param namingConvention object -param privateDnsZoneResourceIdPrefix string -param privateDnsZones array -param privateLinkScopeResourceId string = '' -param resourceAbbreviations object -param serviceToken string -param sessionHostsResourceGroupName string -param sessionThresholdPerCPU string = '1' -param subnetResourceId string -param tags object -param timeDifference string -param timestamp string = utcNow('yyyyMMddhhmmss') - -var fileShareName = 'function-app' -var functionAppKeyword = environment().name == 'AzureCloud' || environment().name == 'AzureUSGovernment' ? 'azurewebsites' : 'appservice' -var privateLinkScopeResourceGroupName = empty(logAnalyticsWorkspaceResourceId) ? resourceGroup().name : split(privateLinkScopeResourceId, '/')[4] -var privateLinkScopeSubscriptionId = empty(logAnalyticsWorkspaceResourceId) ? subscription().subscriptionId : split(privateLinkScopeResourceId, '/')[2] -var roleAssignments = [ - hostPoolResourceGroupName - sessionHostsResourceGroupName -] -var storagePrivateDnsZoneResourceIds = [ - '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'blob'))[0]}' - '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'file'))[0]}' - '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'queue'))[0]}' - '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'table'))[0]}' -] -var storageSubResources = [ - 'blob' - 'file' - 'queue' - 'table' -] - -resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { - name: replace(namingConvention.userAssignedIdentityName, serviceToken, 'scale') - location: location - tags: contains(tags, 'Microsoft.ManagedIdentity/userAssignedIdentities') - ? tags['Microsoft.ManagedIdentity/userAssignedIdentities'] - : {} -} - -resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { - name: guid(userAssignedIdentity.id, 'e147488a-f6f5-4113-8e2d-b22465e65bf6', resourceGroup().id) - properties: { - roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') // Key Vault Crypto Service Encryption User - principalId: userAssignedIdentity.properties.principalId - principalType: 'ServicePrincipal' - } -} - -resource vault 'Microsoft.KeyVault/vaults@2022-07-01' = { - name: replace(namingConvention.keyVaultName, serviceToken, 'scale') - location: location - tags: contains(tags, 'Microsoft.KeyVault/vaults') ? tags['Microsoft.KeyVault/vaults'] : {} - properties: { - enabledForDeployment: false - enabledForDiskEncryption: false - enabledForTemplateDeployment: false - enablePurgeProtection: true - enableRbacAuthorization: true - enableSoftDelete: true - networkAcls: { - bypass: 'AzureServices' - defaultAction: 'Deny' - ipRules: [] - virtualNetworkRules: [] - } - publicNetworkAccess: 'Disabled' - sku: { - family: 'A' - name: 'premium' - } - softDeleteRetentionInDays: environmentAbbreviation == 'dev' || environmentAbbreviation == 'test' ? 7 : 90 - tenantId: subscription().tenantId - } -} - -resource privateEndpoint_vault 'Microsoft.Network/privateEndpoints@2023-04-01' = { - name: replace(namingConvention.keyVaultPrivateEndpoint, serviceToken, 'scale') - location: location - tags: contains(tags, 'Microsoft.Network/privateEndpoints') ? tags['Microsoft.Network/privateEndpoints'] : {} - properties: { - customNetworkInterfaceName: replace(namingConvention.keyVaultNetworkInterface, serviceToken, 'scale') - privateLinkServiceConnections: [ - { - name: replace(namingConvention.keyVaultPrivateEndpoint, serviceToken, 'scale') - properties: { - privateLinkServiceId: vault.id - groupIds: [ - 'vault' - ] - } - } - ] - subnet: { - id: subnetResourceId - } - } -} - -resource privateDnsZoneGroup_vault 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2021-08-01' = { - parent: privateEndpoint_vault - name: vault.name - properties: { - privateDnsZoneConfigs: [ - { - name: 'ipconfig1' - properties: { - #disable-next-line use-resource-id-functions - privateDnsZoneId: '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, 'vaultcore'))[0]}' - } - } - ] - } -} - -resource key_storageAccount 'Microsoft.KeyVault/vaults/keys@2022-07-01' = { - parent: vault - name: 'StorageEncryptionKey' - properties: { - attributes: { - enabled: true - } - keySize: 4096 - kty: 'RSA' - rotationPolicy: { - attributes: { - expiryTime: 'P${string(keyExpirationInDays)}D' - } - lifetimeActions: [ - { - action: { - type: 'Notify' - } - trigger: { - timeBeforeExpiry: 'P10D' - } - } - { - action: { - type: 'Rotate' - } - trigger: { - timeAfterCreate: 'P${string(keyExpirationInDays - 7)}D' - } - } - ] - } - } -} - -resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = { - name: replace(namingConvention.storageAccountName, serviceToken, 'scale') - location: location - tags: contains(tags, 'Microsoft.Storage/storageAccounts') ? tags['Microsoft.Storage/storageAccounts'] : {} - sku: { - name: 'Standard_LRS' - } - kind: 'StorageV2' - identity: { - type: 'UserAssigned' - userAssignedIdentities: { - '${userAssignedIdentity.id}': {} - } - } - properties: { - accessTier: 'Hot' - allowBlobPublicAccess: false - allowCrossTenantReplication: false - allowedCopyScope: 'PrivateLink' - allowSharedKeyAccess: true - azureFilesIdentityBasedAuthentication: { - directoryServiceOptions: 'None' - } - defaultToOAuthAuthentication: false - dnsEndpointType: 'Standard' - encryption: { - identity: { - userAssignedIdentity: userAssignedIdentity.id - } - requireInfrastructureEncryption: true - keyvaultproperties: { - keyvaulturi: vault.properties.vaultUri - keyname: key_storageAccount.name - } - services: { - file: { - keyType: 'Account' - enabled: true - } - table: { - keyType: 'Account' - enabled: true - } - queue: { - keyType: 'Account' - enabled: true - } - blob: { - keyType: 'Account' - enabled: true - } - } - keySource: 'Microsoft.KeyVault' - } - largeFileSharesState: 'Disabled' - minimumTlsVersion: 'TLS1_2' - networkAcls: { - bypass: 'AzureServices' - virtualNetworkRules: [] - ipRules: [] - defaultAction: 'Deny' - } - publicNetworkAccess: 'Disabled' - supportsHttpsTrafficOnly: true - } -} - -resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2021-09-01' = { - parent: storageAccount - name: 'default' -} - -resource fileServices 'Microsoft.Storage/storageAccounts/fileServices@2022-09-01' = { - parent: storageAccount - name: 'default' - properties: { - protocolSettings: { - smb: { - versions: 'SMB3.1.1;' - authenticationMethods: 'NTLMv2;' - channelEncryption: 'AES-128-GCM;AES-256-GCM;' - } - } - shareDeleteRetentionPolicy: { - enabled: false - } - } -} - -resource share 'Microsoft.Storage/storageAccounts/fileServices/shares@2022-09-01' = { - parent: fileServices - name: fileShareName - properties: { - accessTier: 'TransactionOptimized' - shareQuota: 5120 - enabledProtocols: 'SMB' - } -} - -resource privateEndpoints_storage 'Microsoft.Network/privateEndpoints@2023-04-01' = [ - for resource in storageSubResources: { - name: replace(namingConvention.storageAccountPrivateEndpoint, '${serviceToken}-${resourceAbbreviations.storageAccounts}', '${resource}-${resourceAbbreviations.storageAccounts}-scale') - location: location - tags: contains(tags, 'Microsoft.Network/privateEndpoints') ? tags['Microsoft.Network/privateEndpoints'] : {} - properties: { - customNetworkInterfaceName: replace(namingConvention.storageAccountNetworkInterface, '${serviceToken}-${resourceAbbreviations.storageAccounts}', '${resource}-${resourceAbbreviations.storageAccounts}-scale') - privateLinkServiceConnections: [ - { - name: replace(namingConvention.storageAccountPrivateEndpoint, '${serviceToken}-${resourceAbbreviations.storageAccounts}', '${resource}-${resourceAbbreviations.storageAccounts}-scale') - properties: { - privateLinkServiceId: storageAccount.id - groupIds: [ - resource - ] - } - } - ] - subnet: { - id: subnetResourceId - } - } - } -] - -resource privateDnsZoneGroups_storage 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2021-08-01' = [ - for (resource, i) in storageSubResources: { - parent: privateEndpoints_storage[i] - name: storageAccount.name - properties: { - privateDnsZoneConfigs: [ - { - name: 'ipconfig1' - properties: { - #disable-next-line use-resource-id-functions - privateDnsZoneId: storagePrivateDnsZoneResourceIds[i] - } - } - ] - } - } -] - -resource diagnosticSetting_storage_blob 'Microsoft.Insights/diagnosticsettings@2017-05-01-preview' = - if (!empty(logAnalyticsWorkspaceResourceId)) { - scope: blobService - name: replace(namingConvention.storageAccountDiagnosticSetting, '${serviceToken}-${resourceAbbreviations.storageAccounts}', 'blob-${resourceAbbreviations.storageAccounts}-scale') - properties: { - logs: [ - { - category: 'StorageWrite' - enabled: true - } - ] - metrics: [ - { - category: 'Transaction' - enabled: true - } - ] - workspaceId: logAnalyticsWorkspaceResourceId - } - } - -resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { - name: replace(namingConvention.applicationInsightsName, serviceToken, 'scale') - location: location - tags: contains(tags, 'Microsoft.Insights/components') ? tags['Microsoft.Insights/components'] : {} - properties: { - Application_Type: 'web' - } - kind: 'web' -} - -module privateLinkScope '../management/privateLinkScope.bicep' = { - name: 'PrivateLinkScope_${timestamp}' - scope: resourceGroup(privateLinkScopeSubscriptionId, privateLinkScopeResourceGroupName) - params: { - applicationInsightsName: applicationInsights.name - applicationInsightsResourceId: applicationInsights.id - privateLinkScopeResourceId: privateLinkScopeResourceId - } -} - -resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = { - name: replace(namingConvention.appServicePlanName, serviceToken, 'scale') - location: location - tags: contains(tags, 'Microsoft.Web/serverfarms') ? tags['Microsoft.Web/serverfarms'] : {} - sku: { - tier: 'ElasticPremium' - name: 'EP1' - } - kind: 'functionapp' - properties: { - targetWorkerSizeId: 3 - targetWorkerCount: 1 - maximumElasticWorkerCount: 20 - zoneRedundant: false - } - dependsOn: [ - privateEndpoints_storage - privateDnsZoneGroups_storage - ] -} - -resource functionApp 'Microsoft.Web/sites@2023-01-01' = { - name: replace(namingConvention.functionAppName, serviceToken, 'scale') - location: location - tags: contains(tags, 'Microsoft.Web/sites') ? tags['Microsoft.Web/sites'] : {} - kind: 'functionapp' - identity: { - type: 'SystemAssigned' - } - properties: { - clientAffinityEnabled: false - httpsOnly: true - publicNetworkAccess: 'Disabled' - serverFarmId: appServicePlan.id - siteConfig: { - appSettings: [ - { - name: 'FUNCTIONS_EXTENSION_VERSION' - value: '~4' - } - { - name: 'FUNCTIONS_WORKER_RUNTIME' - value: 'powershell' - } - { - name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' - value: applicationInsights.properties.ConnectionString - } - { - name: 'AzureWebJobsStorage' - value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${listKeys(storageAccount.id,'2019-06-01').keys[0].value};EndpointSuffix=${environment().suffixes.storage}' - } - { - name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' - value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};AccountKey=${listKeys(storageAccount.id,'2019-06-01').keys[0].value};EndpointSuffix=${environment().suffixes.storage}' - } - { - name: 'WEBSITE_CONTENTSHARE' - value: fileShareName - } - { - name: 'BeginPeakTime' - value: beginPeakTime - } - { - name: 'EndPeakTime' - value: endPeakTime - } - { - name: 'EnvironmentName' - value: environment().name - } - { - name: 'HostPoolName' - value: hostPoolName - } - { - name: 'HostPoolResourceGroupName' - value: hostPoolResourceGroupName - } - { - name: 'LimitSecondsToForceLogOffUser' - value: limitSecondsToForceLogOffUser - } - { - name: 'LogOffMessageBody' - value: 'This session is about to be logged off. Please save your work.' - } - { - name: 'LogOffMessageTitle' - value: 'Session Log Off' - } - { - name: 'MaintenanceTagName' - value: 'Maintenance' - } - { - name: 'MinimumNumberOfRDSH' - value: minimumNumberOfRdsh - } - { - name: 'ResourceManagerUrl' - // This workaround is needed because the environment().resourceManager value is missing the trailing slash for some Azure environments - value: endsWith(environment().resourceManager, '/') ? environment().resourceManager : '${environment().resourceManager}/' - } - { - name: 'SessionThresholdPerCPU' - value: sessionThresholdPerCPU - } - { - name: 'SubscriptionId' - value: subscription().subscriptionId - } - { - name: 'TenantId' - value: subscription().tenantId - } - { - name: 'TimeDifference' - value: timeDifference - } - ] - cors: { - allowedOrigins: [ - environment().portal - ] - } - ftpsState: 'FtpsOnly' - netFrameworkVersion: 'v6.0' - powerShellVersion: '7.2' - use32BitWorkerProcess: false - } - virtualNetworkSubnetId: delegatedSubnetResourceId - vnetContentShareEnabled: true - vnetRouteAllEnabled: true - } -} - -resource privateEndpoint_functionApp 'Microsoft.Network/privateEndpoints@2023-04-01' = { - name: replace(namingConvention.functionAppPrivateEndpoint, serviceToken, 'scale') - location: location - properties: { - customNetworkInterfaceName: replace(namingConvention.functionAppNetworkInterface, serviceToken, 'scale') - privateLinkServiceConnections: [ - { - name: replace(namingConvention.functionAppPrivateEndpoint, serviceToken, 'scale') - properties: { - privateLinkServiceId: functionApp.id - groupIds: [ - 'sites' - ] - } - } - ] - subnet: { - id: subnetResourceId - } - } -} - -resource privateDnsZoneGroup_functionApp 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2021-08-01' = { - parent: privateEndpoint_functionApp - name: 'default' - properties: { - privateDnsZoneConfigs: [ - { - name: 'ipconfig1' - properties: { - #disable-next-line use-resource-id-functions - privateDnsZoneId: '${privateDnsZoneResourceIdPrefix}${filter(privateDnsZones, name => contains(name, functionAppKeyword))[0]}' - } - } - ] - } -} - -resource function 'Microsoft.Web/sites/functions@2020-12-01' = { - parent: functionApp - name: 'avd-scaling-tool' - properties: { - config: { - disabled: false - bindings: [ - { - name: 'Timer' - type: 'timerTrigger' - direction: 'in' - schedule: '0 */15 * * * *' - } - ] - } - files: { - 'requirements.psd1': loadTextContent('../../artifacts/scaling-tool/requirements.psd1') - 'run.ps1': loadTextContent('../../artifacts/scaling-tool/run.ps1') - '../profile.ps1': loadTextContent('../../artifacts/scaling-tool/profile.ps1') - } - } - dependsOn: [ - privateEndpoint_functionApp - privateDnsZoneGroup_functionApp - ] -} - -// Gives the function app the "Desktop Virtualization Power On Off Contributor" role on the resource groups containing the hosts and host pool -module roleAssignments_ResourceGroups '../common/roleAssignment.bicep' = [ - for i in range(0, length(roleAssignments)): { - name: 'RoleAssignment_${roleAssignments[i]}_${timestamp}' - scope: resourceGroup(roleAssignments[i]) - params: { - principalId: functionApp.identity.principalId - principalType: 'ServicePrincipal' - roleDefinitionId: '40c5ff49-9181-41f8-ae61-143b0e78555e' // Desktop Virtualization Power On Off Contributor - } - } -] diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/management/schedules.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/management/schedules.bicep deleted file mode 100644 index f4e6f5325..000000000 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/management/schedules.bicep +++ /dev/null @@ -1,37 +0,0 @@ -param automationAccountName string -param fslogixContainerType string -param storageAccountName string -param time string = utcNow() -param timeZone string - -resource automationAccount 'Microsoft.Automation/automationAccounts@2022-08-08' existing = { - name: automationAccountName -} - -resource schedules_ProfileContainers 'Microsoft.Automation/automationAccounts/schedules@2022-08-08' = [for i in range(0, 4): { - parent: automationAccount - name: '${storageAccountName}_ProfileContainers_${(i + 1) * 15}min' - properties: { - advancedSchedule: {} - description: null - expiryTime: null - frequency: 'Hour' - interval: 1 - startTime: dateTimeAdd(time, 'PT${(i + 1) * 15}M') - timeZone: timeZone - } -}] - -resource schedules_OfficeContainers 'Microsoft.Automation/automationAccounts/schedules@2022-08-08' = [for i in range(0, 4): if (contains(fslogixContainerType, 'Office')) { - parent: automationAccount - name: '${storageAccountName}_OfficeContainers_${(i + 1) * 15}min' - properties: { - advancedSchedule: {} - description: null - expiryTime: null - frequency: 'Hour' - interval: 1 - startTime: dateTimeAdd(time, 'PT${(i + 1) * 15}M') - timeZone: timeZone - } -}] diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/management/virtualMachine.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/management/virtualMachine.bicep index c2f293828..3f6c64318 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/management/virtualMachine.bicep +++ b/src/bicep/add-ons/azure-virtual-desktop/modules/management/virtualMachine.bicep @@ -1,3 +1,4 @@ +param deploymentUserAssignedIdentityPrincipalId string param deploymentUserAssignedIdentityResourceId string param diskEncryptionSetResourceId string param diskName string @@ -6,32 +7,27 @@ param diskSku string param domainJoinPassword string param domainJoinUserPrincipalName string param domainName string -param hostPoolName string +param hostPoolResourceId string param location string param mlzTags object param networkInterfaceName string param organizationalUnitPath string -param resourceGroupControlPlane string -param subnet string +param subnetResourceId string param tags object param timestamp string = utcNow('yyyyMMddhhmmss') -param virtualNetwork string -param virtualNetworkResourceGroup string param virtualMachineName string @secure() param virtualMachinePassword string param virtualMachineUsername string -var tagsVirtualMachines = union({ - 'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}' -}, contains(tags, 'Microsoft.Compute/virtualMachines') ? tags['Microsoft.Compute/virtualMachines'] : {}, mlzTags) +var tagsVirtualMachines = union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Compute/virtualMachines'] ?? {}, mlzTags) resource networkInterface 'Microsoft.Network/networkInterfaces@2020-05-01' = { name: networkInterfaceName location: location tags: union({ - 'cm-resource-parent': '${subscription().id}}/resourceGroups/${resourceGroupControlPlane}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}' - }, contains(tags, 'Microsoft.Network/networkInterfaces') ? tags['Microsoft.Network/networkInterfaces'] : {}, mlzTags) + 'cm-resource-parent': hostPoolResourceId + }, tags[?'Microsoft.Network/networkInterfaces'] ?? {}, mlzTags) properties: { ipConfigurations: [ { @@ -39,7 +35,7 @@ resource networkInterface 'Microsoft.Network/networkInterfaces@2020-05-01' = { properties: { privateIPAllocationMethod: 'Dynamic' subnet: { - id: resourceId(virtualNetworkResourceGroup, 'Microsoft.Network/virtualNetworks/subnets', virtualNetwork, subnet) + id: subnetResourceId } primary: true privateIPAddressVersion: 'IPv4' @@ -200,5 +196,15 @@ resource extension_JsonADDomainExtension 'Microsoft.Compute/virtualMachines/exte } } +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(deploymentUserAssignedIdentityPrincipalId, 'a959dbd1-f747-45e3-8ba6-dd80f235f97c', virtualMachine.id) + scope: virtualMachine + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', 'a959dbd1-f747-45e3-8ba6-dd80f235f97c') // Desktop Virtualization Virtual Machine Contributor (Purpose: remove the management virtual machine) + principalId: deploymentUserAssignedIdentityPrincipalId + principalType: 'ServicePrincipal' + } +} + output name string = virtualMachine.name output resourceId string = virtualMachine.id diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/resourceGroup.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/resourceGroup.bicep deleted file mode 100644 index e689bc4f0..000000000 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/resourceGroup.bicep +++ /dev/null @@ -1,11 +0,0 @@ -targetScope = 'subscription' - -param location string -param resourceGroupName string -param tags object - -resource resourceGroup 'Microsoft.Resources/resourceGroups@2020-10-01' = { - name: resourceGroupName - location: location - tags: contains(tags, 'Microsoft.Resources/resourceGroups') ? tags['Microsoft.Resources/resourceGroups'] : {} -} diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/sessionHosts/sessionHosts.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/sessionHosts/sessionHosts.bicep index 9345521f5..1161fe40b 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/sessionHosts/sessionHosts.bicep +++ b/src/bicep/add-ons/azure-virtual-desktop/modules/sessionHosts/sessionHosts.bicep @@ -5,10 +5,15 @@ param availability string param availabilitySetsCount int param availabilitySetsIndex int param availabilityZones array +param avdConfigurationZipFileName string param dataCollectionRuleResourceId string param deployFslogix bool param deploymentNameSuffix string param deploymentUserAssignedIdentityClientId string +param deploymentUserAssignedIdentityPrincipalId string +param diskAccessPolicyDefinitionId string +param diskAccessPolicyDisplayName string +param diskAccessResourceId string param diskEncryptionSetResourceId string param diskSku string param divisionRemainderValue int @@ -19,10 +24,11 @@ param domainName string param drainMode bool param enableAcceleratedNetworking bool param enableAvdInsights bool +param enableRecoveryServices bool param environmentAbbreviation string param fslogixContainerType string -param functionAppName string param hostPoolName string +param hostPoolResourceId string param hostPoolType string param identifier string param imageOffer string @@ -30,20 +36,21 @@ param imagePublisher string param imageSku string param imageVersionResourceId string param location string +param logAnalyticsWorkspaceResourceId string param managementVirtualMachineName string param maxResourcesPerTemplateDeployment int param mlzTags object param namingConvention object param netAppFileShares array param organizationalUnitPath string -param pooledHostPool bool -param enableRecoveryServices bool -param enableScalingTool bool +param profile string param recoveryServicesVaultName string -param resourceGroupControlPlane string -param resourceGroupHosts string param resourceGroupManagement string -param roleDefinitions object +param resourceGroupName string +param scalingWeekdaysOffPeakStartTime string +param scalingWeekdaysPeakStartTime string +param scalingWeekendsOffPeakStartTime string +param scalingWeekendsPeakStartTime string param securityPrincipalObjectIds array param serviceToken string param sessionHostBatchCount int @@ -55,40 +62,58 @@ param storageService string param storageSuffix string param subnetResourceId string param tags object +param timeZone string @secure() param virtualMachinePassword string param virtualMachineSize string param virtualMachineUsername string var availabilitySetNamePrefix = namingConvention.availabilitySet -var tagsAvailabilitySets = union({'cm-resource-parent': '${subscription().id}/resourceGroups/${resourceGroupManagement}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}'}, contains(tags, 'Microsoft.Compute/availabilitySets') ? tags['Microsoft.Compute/availabilitySets'] : {}, mlzTags) -var tagsNetworkInterfaces = union({'cm-resource-parent': '${subscription().id}/resourceGroups/${resourceGroupManagement}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}'}, contains(tags, 'Microsoft.Network/networkInterfaces') ? tags['Microsoft.Network/networkInterfaces'] : {}, mlzTags) -var tagsRecoveryServicesVault = union({'cm-resource-parent': '${subscription().id}/resourceGroups/${resourceGroupManagement}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}'}, contains(tags, 'Microsoft.recoveryServices/vaults') ? tags['Microsoft.recoveryServices/vaults'] : {}, mlzTags) -var tagsVirtualMachines = union({'cm-resource-parent': '${subscription().id}/resourceGroups/${resourceGroupManagement}/providers/Microsoft.DesktopVirtualization/hostpools/${hostPoolName}'}, contains(tags, 'Microsoft.Compute/virtualMachines') ? tags['Microsoft.Compute/virtualMachines'] : {}, mlzTags) +var tagsVirtualMachines = union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Compute/virtualMachines'] ?? {}, mlzTags) var uniqueToken = uniqueString(identifier, environmentAbbreviation, subscription().subscriptionId) var virtualMachineNamePrefix = replace(namingConvention.virtualMachine, serviceToken, '') -module availabilitySets 'availabilitySets.bicep' = if (pooledHostPool && availability == 'AvailabilitySets') { - name: 'deploy-avail-${deploymentNameSuffix}' - scope: resourceGroup(resourceGroupHosts) +resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = { + name: resourceGroupName + location: location + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Resources/resourceGroups'] ?? {}, mlzTags) +} + +// Sets an Azure policy to disable public network access to managed disks +module policyAssignment '../management/policyAssignment.bicep' = { + name: 'assign-policy-diskAccess-${deploymentNameSuffix}' + scope: rg + params: { + diskAccessResourceId: diskAccessResourceId + location: location + policyDefinitionId: diskAccessPolicyDefinitionId + policyDisplayName: diskAccessPolicyDisplayName + policyName: diskAccessPolicyDisplayName + } +} + +module availabilitySets 'availabilitySets.bicep' = if (hostPoolType == 'Pooled' && availability == 'AvailabilitySets') { + name: 'deploy-avSets-${deploymentNameSuffix}' + scope: rg params: { availabilitySetNamePrefix: availabilitySetNamePrefix availabilitySetsCount: availabilitySetsCount availabilitySetsIndex: availabilitySetsIndex location: location - tagsAvailabilitySets: tagsAvailabilitySets + tagsAvailabilitySets: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Compute/availabilitySets'] ?? {}, mlzTags) } } -// Role Assignment for Virtual Machine Login User -// This module deploys the role assignments to login to Azure AD joined session hosts -module roleAssignments '../common/roleAssignments/resourceGroup.bicep' = [for i in range(0, length(securityPrincipalObjectIds)): if (!contains(activeDirectorySolution, 'DomainServices')) { - name: 'deploy-role-assignments-${i}-${deploymentNameSuffix}' - scope: resourceGroup(resourceGroupHosts) +// Role Assignment for Entra Joined Virtual Machines +// Purpose: assigns the Virtual Machine Login User role on the hosts resource group +// to enable the login to Entra joined virtual machines +module roleAssignments '../common/roleAssignments/resourceGroup.bicep' = [for i in range(0, length(securityPrincipalObjectIds)): if (contains(activeDirectorySolution, 'EntraId')) { + name: 'assign-role-${i}-${deploymentNameSuffix}' + scope: rg params: { principalId: securityPrincipalObjectIds[i] principalType: 'Group' - roleDefinitionId: roleDefinitions.VirtualMachineUserLogin + roleDefinitionId: 'fb879df8-f326-4884-b1cf-06f3ad86be52' } }] @@ -102,15 +127,97 @@ resource image 'Microsoft.Compute/galleries/images@2023-07-03' existing = if (em name: split(imageVersionResourceId, '/')[10] } +// Disable Autoscale if adding new session hosts to an existing host pool +module disableAutoscale '../common/runCommand.bicep' = { + name: 'deploy-disableAutoscale-${deploymentNameSuffix}' + scope: resourceGroup(resourceGroupManagement) + params: { + location: location + name: 'Disable-Autoscale' + parameters: [ + { + name: 'HostPoolResourceId' + value: hostPoolResourceId + } + { + name: 'ResourceGroupName' + value: resourceGroupManagement + } + { + name: 'ResourceManagerUri' + value: environment().resourceManager + } + { + name: 'ScalingPlanName' + value: namingConvention.scalingPlan + } + { + name: 'SubscriptionId' + value: subscription().subscriptionId + } + { + name: 'UserAssignedidentityClientId' + value: deploymentUserAssignedIdentityClientId + } + ] + script: loadTextContent('../../artifacts/Disable-Autoscale.ps1') + tags: tagsVirtualMachines + virtualMachineName: managementVirtualMachineName + } +} + +// Set MarketPlace Terms for ESRI's ArcGIS Pro image +module setMarketplaceTerms '../common/runCommand.bicep' = if (profile == 'ArcGISPro') { + name: 'set-marketplaceTerms-${deploymentNameSuffix}' + scope: resourceGroup(resourceGroupManagement) + params: { + location: location + name: 'Set-AzureMarketplaceTerms' + parameters: [ + { + name: 'ImageOffer' + value: imageOffer + } + { + name: 'ImagePublisher' + value: imagePublisher + } + { + name: 'ImageSku' + value: imageSku + } + { + name: 'ResourceManagerUri' + value: environment().resourceManager + } + { + name: 'SubscriptionId' + value: subscription().subscriptionId + } + { + name: 'UserAssignedidentityClientId' + value: deploymentUserAssignedIdentityClientId + } + ] + script: loadTextContent('../../artifacts/Set-AzureMarketplaceTerms.ps1') + tags: tagsVirtualMachines + virtualMachineName: managementVirtualMachineName + } + dependsOn: [ + disableAutoscale + ] +} + @batchSize(1) module virtualMachines 'virtualMachines.bicep' = [for i in range(1, sessionHostBatchCount): { name: 'deploy-vms-${i - 1}-${deploymentNameSuffix}' - scope: resourceGroup(resourceGroupHosts) + scope: rg params: { activeDirectorySolution: activeDirectorySolution availability: availability availabilitySetNamePrefix: availabilitySetNamePrefix availabilityZones: availabilityZones + avdConfigurationZipFileName: avdConfigurationZipFileName batchCount: i dataCollectionRuleAssociationName: namingConvention.dataCollectionRuleAssociation dataCollectionRuleResourceId: dataCollectionRuleResourceId @@ -128,7 +235,6 @@ module virtualMachines 'virtualMachines.bicep' = [for i in range(1, sessionHostB enableDrainMode: drainMode fslogixContainerType: fslogixContainerType hostPoolName: hostPoolName - hostPoolType: hostPoolType imageVersionResourceId: imageVersionResourceId imageOffer: empty(imageVersionResourceId) ? imageOffer : image.properties.purchasePlan.product imagePublisher: empty(imageVersionResourceId) ? imagePublisher: image.properties.purchasePlan.publisher @@ -138,7 +244,7 @@ module virtualMachines 'virtualMachines.bicep' = [for i in range(1, sessionHostB netAppFileShares: netAppFileShares networkInterfaceNamePrefix: namingConvention.virtualMachineNetworkInterface organizationalUnitPath: organizationalUnitPath - resourceGroupControlPlane: resourceGroupControlPlane + profile: profile resourceGroupManagement: resourceGroupManagement serviceToken: serviceToken sessionHostCount: i == sessionHostBatchCount && divisionRemainderValue > 0 ? divisionRemainderValue : maxResourcesPerTemplateDeployment @@ -149,7 +255,7 @@ module virtualMachines 'virtualMachines.bicep' = [for i in range(1, sessionHostB storageService: storageService storageSuffix: storageSuffix subnetResourceId: subnetResourceId - tagsNetworkInterfaces: tagsNetworkInterfaces + tagsNetworkInterfaces: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.Network/networkInterfaces'] ?? {}, mlzTags) tagsVirtualMachines: tagsVirtualMachines uniqueToken: uniqueToken virtualMachineNamePrefix: virtualMachineNamePrefix @@ -159,11 +265,13 @@ module virtualMachines 'virtualMachines.bicep' = [for i in range(1, sessionHostB } dependsOn: [ availabilitySets + disableAutoscale + setMarketplaceTerms ] }] -module recoveryServices 'recoveryServices.bicep' = if (enableRecoveryServices && contains(hostPoolType, 'Personal')) { - name: 'deploy-recovery-services-${deploymentNameSuffix}' +module recoveryServices 'recoveryServices.bicep' = if (enableRecoveryServices && hostPoolType == 'Personal') { + name: 'deploy-recoveryServices-${deploymentNameSuffix}' scope: resourceGroup(resourceGroupManagement) params: { deployFslogix: deployFslogix @@ -172,11 +280,11 @@ module recoveryServices 'recoveryServices.bicep' = if (enableRecoveryServices && location: location maxResourcesPerTemplateDeployment: maxResourcesPerTemplateDeployment recoveryServicesVaultName: recoveryServicesVaultName - resourceGroupHosts: resourceGroupHosts + resourceGroupHosts: rg.name resourceGroupManagement: resourceGroupManagement sessionHostBatchCount: sessionHostBatchCount sessionHostIndex: sessionHostIndex - tagsRecoveryServicesVault: tagsRecoveryServicesVault + tagsRecoveryServicesVault: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.recoveryServices/vaults'] ?? {}, mlzTags) virtualMachineNamePrefix: virtualMachineNamePrefix } dependsOn: [ @@ -184,17 +292,27 @@ module recoveryServices 'recoveryServices.bicep' = if (enableRecoveryServices && ] } -module scalingTool '../common/function.bicep' = if (enableScalingTool && pooledHostPool) { - name: 'deploy-scaling-tool-${deploymentNameSuffix}' +module scalingPlan '../management/scalingPlan.bicep' = { + name: 'deploy-scalingPlan-${deploymentNameSuffix}' scope: resourceGroup(resourceGroupManagement) params: { - files: { - 'requirements.psd1': loadTextContent('../../artifacts/scaling-tool/requirements.psd1') - 'run.ps1': loadTextContent('../../artifacts/scaling-tool/run.ps1') - '../profile.ps1': loadTextContent('../../artifacts/scaling-tool/profile.ps1') - } - functionAppName: functionAppName - functionName: 'avd-scaling-tool' - schedule: '0 */15 * * * *' + deploymentUserAssignedIdentityPrincipalId: deploymentUserAssignedIdentityPrincipalId + enableAvdInsights: enableAvdInsights + hostPoolResourceId: hostPoolResourceId + hostPoolType: hostPoolType + location: location + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId + scalingPlanDiagnosticSettingName: namingConvention.scalingPlanDiagnosticSetting + scalingPlanName: namingConvention.scalingPlan + tags: union({'cm-resource-parent': hostPoolResourceId}, tags[?'Microsoft.DesktopVirtualization/scalingPlans'] ?? {}, mlzTags) + timeZone: timeZone + weekdaysOffPeakStartTime: scalingWeekdaysOffPeakStartTime + weekdaysPeakStartTime: scalingWeekdaysPeakStartTime + weekendsOffPeakStartTime: scalingWeekendsOffPeakStartTime + weekendsPeakStartTime: scalingWeekendsPeakStartTime } + dependsOn: [ + recoveryServices + virtualMachines + ] } diff --git a/src/bicep/add-ons/azure-virtual-desktop/modules/sessionHosts/virtualMachines.bicep b/src/bicep/add-ons/azure-virtual-desktop/modules/sessionHosts/virtualMachines.bicep index 4e7947f93..5584da0a7 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/modules/sessionHosts/virtualMachines.bicep +++ b/src/bicep/add-ons/azure-virtual-desktop/modules/sessionHosts/virtualMachines.bicep @@ -2,6 +2,7 @@ param activeDirectorySolution string param availability string param availabilitySetNamePrefix string param availabilityZones array +param avdConfigurationZipFileName string param batchCount int param dataCollectionRuleAssociationName string param dataCollectionRuleResourceId string @@ -20,7 +21,6 @@ param enableAvdInsights bool param enableDrainMode bool param fslogixContainerType string param hostPoolName string -param hostPoolType string param imageOffer string param imagePublisher string param imageSku string @@ -30,7 +30,7 @@ param managementVirtualMachineName string param netAppFileShares array param networkInterfaceNamePrefix string param organizationalUnitPath string -param resourceGroupControlPlane string +param profile string param resourceGroupManagement string param serviceToken string param sessionHostCount int @@ -72,7 +72,7 @@ var imageReference = empty(imageVersionResourceId) ? { } : { id: imageVersionResourceId } -var intune = contains(activeDirectorySolution, 'intuneEnrollment') +var intune = contains(activeDirectorySolution, 'IntuneEnrollment') var nvidiaVmSize = contains(nvidiaVmSizes, virtualMachineSize) var nvidiaVmSizes = [ 'Standard_NV6' @@ -92,13 +92,12 @@ var nvidiaVmSizes = [ 'Standard_NV36adms_A10_v5' 'Standard_NV72ads_A10_v5' ] -var pooledHostPool = (split(hostPoolType, ' ')[0] == 'Pooled') var sessionHostNamePrefix = replace(virtualMachineNamePrefix, serviceToken, '') -var storageAccountToken = take('${storageAccountPrefix}??${uniqueToken}', 24) +var storageAccountToken = '${storageAccountPrefix}??' // The token is used for AntiVirus exclusions. The '??' represents the two digits at the end of each storage account name. resource hostPool 'Microsoft.DesktopVirtualization/hostPools@2023-09-05' existing = { name: hostPoolName - scope: resourceGroup(subscription().subscriptionId, resourceGroupControlPlane) + scope: resourceGroup(subscription().subscriptionId, resourceGroupManagement) } resource networkInterface 'Microsoft.Network/networkInterfaces@2020-05-01' = [for i in range(0, sessionHostCount): { @@ -128,6 +127,14 @@ resource virtualMachine 'Microsoft.Compute/virtualMachines@2021-03-01' = [for i name: '${sessionHostNamePrefix}${padLeft((i + sessionHostIndex), 4, '0')}' location: location tags: tagsVirtualMachines + identity: { + type: 'SystemAssigned' // Required for Entra join + } + plan: profile == 'ArcGISPro' ? { + name: imageSku + publisher: imagePublisher + product: imageOffer + } : null zones: availability == 'AvailabilityZones' ? [ availabilityZones[i % length(availabilityZones)] ] : null @@ -352,7 +359,7 @@ resource installAvdAgents 'Microsoft.Compute/virtualMachines/extensions@2021-03- typeHandlerVersion: '2.73' autoUpgradeMinorVersion: true settings: { - modulesUrl: 'https://wvdportalstorageblob.blob.${environment().suffixes.storage}/galleryartifacts/Configuration_1.0.02721.349.zip' + modulesUrl: 'https://wvdportalstorageblob.blob.${environment().suffixes.storage}/galleryartifacts/${avdConfigurationZipFileName}' configurationFunction: 'Configuration.ps1\\AddSessionHost' properties: { hostPoolName: hostPoolName @@ -360,7 +367,7 @@ resource installAvdAgents 'Microsoft.Compute/virtualMachines/extensions@2021-03- UserName: 'PLACEHOLDER_DO_NOT_USE' Password: 'PrivateSettingsRef:RegistrationInfoToken' } - aadJoin: !contains(activeDirectorySolution, 'DomainServices') + aadJoin: contains(activeDirectorySolution, 'EntraId') UseAgentDownloadEndpoint: false mdmId: intune ? '0000000a-0000-0000-c000-000000000000' : '' } @@ -378,7 +385,7 @@ resource installAvdAgents 'Microsoft.Compute/virtualMachines/extensions@2021-03- ] // Enables drain mode on the session hosts so users cannot login to the hosts immediately after the deployment -module drainMode '../common/runCommand.bicep' = if (enableDrainMode && pooledHostPool) { +module drainMode '../common/runCommand.bicep' = if (enableDrainMode) { name: 'deploy-drain-mode-${batchCount}-${deploymentNameSuffix}' scope: resourceGroup(resourceGroupManagement) params: { @@ -395,7 +402,7 @@ module drainMode '../common/runCommand.bicep' = if (enableDrainMode && pooledHos } { name: 'HostPoolResourceGroupName' - value: resourceGroupControlPlane + value: resourceGroupManagement } { name: 'ResourceManagerUri' @@ -462,7 +469,7 @@ resource extension_JsonADDomainExtension 'Microsoft.Compute/virtualMachines/exte ] }] -resource extension_AADLoginForWindows 'Microsoft.Compute/virtualMachines/extensions@2021-03-01' = [for i in range(0, sessionHostCount): if (!contains(activeDirectorySolution, 'DomainServices')) { +resource extension_AADLoginForWindows 'Microsoft.Compute/virtualMachines/extensions@2021-03-01' = [for i in range(0, sessionHostCount): if (contains(activeDirectorySolution, 'EntraId')) { parent: virtualMachine[i] name: 'AADLoginForWindows' location: location diff --git a/src/bicep/add-ons/azure-virtual-desktop/parameters.json b/src/bicep/add-ons/azure-virtual-desktop/parameters.json deleted file mode 100644 index d5fe535eb..000000000 --- a/src/bicep/add-ons/azure-virtual-desktop/parameters.json +++ /dev/null @@ -1,222 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "activeDirectorySolution": { - "value": "ActiveDirectoryDomainServices" - }, - "artifactsContainerName": { - "value": "artifacts" - }, - "artifactsStorageAccountResourceId": { - "value": "" - }, - "automationAccountPrivateDnsZoneResourceId": { - "value": "" - }, - "availability": { - "value": "AvailabilityZones" - }, - "avdAgentMsiName": { - "value": "Microsoft.RDInfra.RDAgent.Installer-x64-1.0.7539.8300.msi" - }, - "avdAgentBootLoaderMsiName": { - "value": "Microsoft.RDInfra.RDAgentBootLoader.Installer-x64 (5).msi" - }, - "avdObjectId": { - "value": "cdcfb416-e2fe-41e2-be12-33813c1cd427" - }, - "avdPrivateDnsZoneResourceId": { - "value": "/subscriptions/3764b123-4849-4395-8e6e-ca6d68d8d4b4/resourceGroups/rg-net-d-eu/providers/Microsoft.Network/privateDnsZones/privatelink.wvd.microsoft.com" - }, - "azureFilesPrivateDnsZoneResourceId": { - "value": "/subscriptions/3764b123-4849-4395-8e6e-ca6d68d8d4b4/resourceGroups/rg-net-d-eu/providers/Microsoft.Network/privateDnsZones/privatelink.file.core.windows.net" - }, - "azurePowerShellModuleMsiName": { - "value": "Az-Cmdlets-10.2.0.37547-x64.msi" - }, - "customRdpProperty": { - "value": "audiocapturemode:i:1;camerastoredirect:s:*;use multimon:i:0;drivestoredirect:s:;" - }, - "disableBgpRoutePropagation": { - "value": false - }, - "diskEncryption": { - "value": true - }, - "diskSku": { - "value": "Premium_LRS" - }, - "domainJoinPassword": { - "reference": { - "keyVault": { - "id": "/subscriptions/3764b123-4849-4395-8e6e-ca6d68d8d4b4/resourceGroups/rg-core-d-eu/providers/Microsoft.KeyVault/vaults/kv-core-d-eu" - }, - "secretName": "DomainJoinPassword" - } - }, - "domainJoinUserPrincipalName": { - "reference": { - "keyVault": { - "id": "/subscriptions/3764b123-4849-4395-8e6e-ca6d68d8d4b4/resourceGroups/rg-core-d-eu/providers/Microsoft.KeyVault/vaults/kv-core-d-eu" - }, - "secretName": "DomainJoinUsername" - } - }, - "domainName": { - "value": "jasonmasten.com" - }, - "drainMode": { - "value": true - }, - "environment": { - "value": "d" - }, - "fslogixShareSizeInGB": { - "value": 100 - }, - "fslogixSolution": { - "value": "ProfileContainer" - }, - "fslogixStorage": { - "value": "AzureFiles Premium" - }, - "globalWorkspacePrivateDnsZoneResourceId": { - "value": "/subscriptions/3764b123-4849-4395-8e6e-ca6d68d8d4b4/resourceGroups/rg-net-d-eu/providers/Microsoft.Network/privateDnsZones/privatelink-global.wvd.microsoft.com" - }, - "hostPoolPublicNetworkAccess": { - "value": "Enabled" - }, - "hostPoolType": { - "value": "Pooled DepthFirst" - }, - "hubAzureFirewallResourceId": { - "value": "" - }, - "hubVirtualNetworkResourceId": { - "value": "" - }, - "identifier": { - "value": "jnm" - }, - "imageDefinitionResourceId": { - "value": "" - }, - "imageOffer": { - "value": "office-365" - }, - "imagePublisher": { - "value": "MicrosoftWindowsDesktop" - }, - "imageSku": { - "value": "win11-22h2-avd-m365" - }, - "logAnalyticsWorkspaceRetention": { - "value": 30 - }, - "logAnalyticsWorkspaceSku": { - "value": "PerGB2018" - }, - "maxSessionLimit": { - "value": 8 - }, - "monitoring": { - "value": true - }, - "organizationalUnitPath": { - "value": "OU=AVD,DC=jasonmasten,DC=com" - }, - "recoveryServices": { - "value": false - }, - "scalingBeginPeakTime": { - "value": "7:00" - }, - "scalingEndPeakTime": { - "value": "18:00" - }, - "scalingLimitSecondsToForceLogOffUser": { - "value": "0" - }, - "scalingMinimumNumberOfRdsh": { - "value": "0" - }, - "scalingSessionThresholdPerCPU": { - "value": "1" - }, - "scalingTool": { - "value": true - }, - "securityLogAnalyticsWorkspaceResourceId": { - "value": "" - }, - "securityPrincipalObjectIds": { - "value": [ - "06c4afa5-b6dc-4719-8034-947887108e29" - ] - }, - "securityPrincipals": { - "value": [] - }, - "sessionDesktopFriendlyName": { - "value": "" - }, - "sessionHostCount": { - "value": 1 - }, - "sessionHostIndex": { - "value": 0 - }, - "stampIndex": { - "value": 0 - }, - "storageCount": { - "value": 1 - }, - "storageIndex": { - "value": 0 - }, - "subnetAddressPrefix": { - "value": "10.0.8.0/24" - }, - "subnetResourceId": { - "value": "/subscriptions/3764b123-4849-4395-8e6e-ca6d68d8d4b4/resourceGroups/rg-net-d-eu/providers/Microsoft.Network/virtualNetworks/vnet-net-d-eu/subnets/Clients" - }, - "tags": { - "value": {} - }, - "validationEnvironment": { - "value": false - }, - "virtualMachinePassword": { - "reference": { - "keyVault": { - "id": "/subscriptions/3764b123-4849-4395-8e6e-ca6d68d8d4b4/resourceGroups/rg-core-d-eu/providers/Microsoft.KeyVault/vaults/kv-core-d-eu" - }, - "secretName": "VmPassword" - } - }, - "virtualMachineSize": { - "value": "Standard_D4ads_v5" - }, - "virtualMachineUsername": { - "reference": { - "keyVault": { - "id": "/subscriptions/3764b123-4849-4395-8e6e-ca6d68d8d4b4/resourceGroups/rg-core-d-eu/providers/Microsoft.KeyVault/vaults/kv-core-d-eu" - }, - "secretName": "VmUsername" - } - }, - "virtualNetworkAddressPrefix": { - "value": [ - "10.0.8.0/24" - ] - }, - "workspaceFriendlyName": { - "value": "Jason Masten" - }, - "workspacePublicNetworkAccess": { - "value": "Enabled" - } - } -} \ No newline at end of file diff --git a/src/bicep/add-ons/azure-virtual-desktop/solution.bicep b/src/bicep/add-ons/azure-virtual-desktop/solution.bicep index 5a3120d54..d01785a2d 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/solution.bicep +++ b/src/bicep/add-ons/azure-virtual-desktop/solution.bicep @@ -20,6 +20,9 @@ param availability string = 'AvailabilityZones' @description('The availability zones allowed for the AVD session hosts deployment location.') param availabilityZones array +@description('The file name for the ZIP file containing the AVD agents and DSC configuration.') +param avdConfigurationZipFileName string = 'Configuration_1.0.02790.438.zip' + @description('The object ID for the Azure Virtual Desktop enterprise application in Microsoft Entra ID. The object ID can found by selecting Microsoft Applications using the Application type filter in the Enterprise Applications blade of Microsoft Entra ID.') param avdObjectId string @@ -74,7 +77,7 @@ param emailSecurityContact string @description('Determine whether to enable accelerated networking on the AVD session hosts. https://learn.microsoft.com/en-us/azure/virtual-network/accelerated-networking-overview') param enableAcceleratedNetworking bool -@description('Deploys the required resources to monitor the function app for the Scaling Tool and Auto Increase Premium File Share solutions.') +@description('Deploys the required resources to monitor the function app for the Auto Increase Premium File Share solutions.') param enableApplicationInsights bool = true @description('Deploys the required monitoring resources to enable AVD Insights.') @@ -114,12 +117,12 @@ param fslogixContainerType string = 'ProfileContainer' 'AzureNetAppFiles Standard' // ANF with the Standard SKU, 320,000 IOPS 'AzureFiles Premium' // Azure Files Premium with a Private Endpoint, 100,000 IOPS 'AzureFiles Standard' // Azure Files Standard with the Large File Share option and a Private Endpoint, 20,000 IOPS - 'None' + 'None' // Local Profiles ]) @description('Enable an Fslogix storage option to manage user profiles for the AVD session hosts. The selected service & SKU should provide sufficient IOPS for all of your users. https://docs.microsoft.com/en-us/azure/architecture/example-scenario/wvd/windows-virtual-desktop-fslogix#performance-requirements') param fslogixStorageService string = 'AzureFiles Standard' -@description('The subnet address prefix for the delegated subnet for the Azure Function App. This subnet is required for the Scaling Tool and the Auto Increase Premium File Share Quotas tool.') +@description('The subnet address prefix for the delegated subnet for the Azure Function App. This subnet is required for the Auto Increase Premium File Share Quotas tool.') param functionAppSubnetAddressPrefix string = '' @allowed([ @@ -132,13 +135,11 @@ param functionAppSubnetAddressPrefix string = '' param hostPoolPublicNetworkAccess string @allowed([ - 'Pooled DepthFirst' - 'Pooled BreadthFirst' - 'Personal Automatic' - 'Personal Direct' + 'Pooled' + 'Personal' ]) -@description('These options specify the host pool type and depending on the type provides the load balancing options and assignment types.') -param hostPoolType string = 'Pooled DepthFirst' +@description('The type of AVD host pool.') +param hostPoolType string = 'Pooled' @description('The resource ID for the Azure Firewall in the HUB subscription') param hubAzureFirewallResourceId string @@ -204,27 +205,30 @@ param profile string = 'Generic' @description('Enable backups to an Azure Recovery Services vault. For a pooled host pool this will enable backups on the Azure file share. For a personal host pool this will enable backups on the AVD sessions hosts.') param recoveryServices bool = false -@description('The time when session hosts will scale up and continue to stay on to support peak demand; Format 24 hours e.g. 9:00 for 9am') -param scalingBeginPeakTime string = '9:00' - -@description('The time when session hosts will scale down and stay off to support low demand; Format 24 hours e.g. 17:00 for 5pm') -param scalingEndPeakTime string = '17:00' +@description('Off peak start time for weekdays in HH:mm format.') +param scalingWeekdaysOffPeakStartTime string = '17:00' -@description('The number of seconds to wait before automatically signing out users. If set to 0 any session host that has user sessions will be left untouched') -param scalingLimitSecondsToForceLogOffUser string = '0' +@description('Off peak start time for weekends in HH:mm format.') +param scalingWeekdaysPeakStartTime string = '09:00' -@description('The minimum number of session host VMs to keep running during off-peak hours. The scaling tool will not work if all virtual machines are turned off and the Start VM On Connect solution is not enabled.') -param scalingMinimumNumberOfRdsh string = '0' +@description('Peak start time for weekdays in HH:mm format.') +param scalingWeekendsOffPeakStartTime string = '17:00' -@description('The maximum number of sessions per CPU that will be used as a threshold to determine when new session host VMs need to be started during peak hours') -param scalingSessionThresholdPerCPU string = '1' - -@description('Deploys the required resources for the Scaling Tool. https://docs.microsoft.com/en-us/azure/virtual-desktop/scaling-automation-logic-apps') -param scalingTool bool = false +@description('Peak start time for weekends in HH:mm format.') +param scalingWeekendsPeakStartTime string = '09:00' @description('The array of Security Principals with their object IDs and display names to assign to the AVD Application Group and FSLogix Storage.') param securityPrincipals array +/* Example of a security principal +[ + { + displayName: 'AVD' + objectId: '00000000-0000-0000-0000-000000000000' + } +] +*/ + @maxValue(5000) @minValue(0) @description('The number of session hosts to deploy in the host pool. Ensure you have the approved quota to deploy the desired count.') @@ -316,9 +320,9 @@ var availabilitySetsCount = length(range(beginAvSetRange, (endAvSetRange - begin // OTHER LOGIC & COMPUTED VALUES var customImageId = empty(imageVersionResourceId) ? 'null' : '"${imageVersionResourceId}"' -var deployFslogix = fslogixStorageService == 'None' || !contains(activeDirectorySolution, 'DomainServices') - ? false - : true +var deployFslogix = contains(fslogixStorageService, 'Azure') && contains(activeDirectorySolution, 'DomainServices') + ? true + : false var fileShareNames = { CloudCacheProfileContainer: [ 'profile-containers' @@ -337,26 +341,7 @@ var fileShareNames = { } var fileShares = fileShareNames[fslogixContainerType] var netbios = split(domainName, '.')[0] -var pooledHostPool = split(hostPoolType, ' ')[0] == 'Pooled' ? true : false var privateDnsZoneResourceIdPrefix = '/subscriptions/${split(hubVirtualNetworkResourceId, '/')[2]}/resourceGroups/${split(hubVirtualNetworkResourceId, '/')[4]}/providers/Microsoft.Network/privateDnsZones/' -var resourceGroupServices = union( - [ - 'controlPlane' - 'hosts' - 'management' - ], - deployFslogix - ? [ - 'storage' - ] - : [] -) -var roleDefinitions = { - DesktopVirtualizationPowerOnContributor: '489581de-a3bd-480d-9518-53dea7416b33' - DesktopVirtualizationUser: '1d18fff3-a72a-46b5-b4a9-0b38a3cd7e63' - Reader: 'acdd72a7-3385-48ef-bd42-f606fba81ae7' - VirtualMachineUserLogin: 'fb879df8-f326-4884-b1cf-06f3ad86be52' -} var storageSku = fslogixStorageService == 'None' ? 'None' : split(fslogixStorageService, ' ')[1] var storageService = split(fslogixStorageService, ' ')[0] var storageSuffix = environment().suffixes.storage @@ -379,7 +364,7 @@ var subnets = { } ] : [] - functionApp: scalingTool || fslogixStorageService == 'AzureFiles Premium' + functionApp: fslogixStorageService == 'AzureFiles Premium' ? [ { name: 'FunctionAppOutbound' @@ -391,19 +376,19 @@ var subnets = { : [] } -// Gets the hub virtual network for its location and tags +// Gets the MLZ hub virtual network for its location and tags resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-11-01' existing = { name: split(hubVirtualNetworkResourceId, '/')[8] scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) } -// Gets the application group references if the feed workspace already exists +// Gets the application group references if the AVD feed workspace already exists resource workspace 'Microsoft.DesktopVirtualization/workspaces@2023-09-05' existing = if (!empty(existingFeedWorkspaceResourceId)) { scope: resourceGroup(split(existingFeedWorkspaceResourceId, '/')[2], split(existingFeedWorkspaceResourceId, '/')[4]) name: split(existingFeedWorkspaceResourceId, '/')[8] } -// This module deploys telemetry for ArcGIS Pro deployments +// Optionally deploys telemetry for ArcGIS Pro deployments #disable-next-line no-deployments-resources resource partnerTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (enableTelemetry && profile == 'ArcGISPro') { name: 'pid-4e82be1d-7fcb-4913-a90c-aa84d7ea3a1c' @@ -418,22 +403,9 @@ resource partnerTelemetry 'Microsoft.Resources/deployments@2021-04-01' = if (ena } } -// This deployment is used to get the naming convention and tokens for the resource groups and resources. -module naming_controlPlane '../../modules/naming-convention.bicep' = { - name: 'get-naming-cp-${deploymentNameSuffix}' - params: { - environmentAbbreviation: environmentAbbreviation - location: virtualNetwork.location - networkName: 'avd' - networkShortName: 'avd' - resourcePrefix: identifier - stampIndex: string(stampIndex) - } -} - -// This deployment is used to get the naming convention and tokens for the resource groups and resources. -module naming_hub '../../modules/naming-convention.bicep' = { - name: 'get-naming-hub-${deploymentNameSuffix}' +// Gets the naming convention and tokens for the resource groups and resources +module naming_management '../../modules/naming-convention.bicep' = { + name: 'get-naming-mgmt-${deploymentNameSuffix}' params: { environmentAbbreviation: environmentAbbreviation location: virtualNetwork.location @@ -471,29 +443,18 @@ module tier3_hosts '../tier3/solution.bicep' = { } } -// Resource Groups -module rgs '../../modules/resource-group.bicep' = [ - for service in resourceGroupServices: { - name: 'deploy-rg-${service}-${deploymentNameSuffix}' - params: { - location: service == 'controlPlane' ? virtualNetwork.location : locationVirtualMachines - mlzTags: tier3_hosts.outputs.mlzTags - name: service == 'controlPlane' - ? replace(naming_controlPlane.outputs.names.resourceGroup, naming_controlPlane.outputs.tokens.service, service) - : replace(tier3_hosts.outputs.namingConvention.resourceGroup, tier3_hosts.outputs.tokens.service, service) - tags: tags - } - } -] - -// Management Services: AVD Insights, File Share Scaling, Scaling Tool +// Management: host pool, app group, AVD Insights, File Share Scaling module management 'modules/management/management.bicep' = { name: 'deploy-management-${deploymentNameSuffix}' params: { activeDirectorySolution: activeDirectorySolution avdObjectId: avdObjectId + avdPrivateDnsZoneResourceId: '${privateDnsZoneResourceIdPrefix}${filter(tier3_hosts.outputs.privateDnsZones, name => startsWith(name, 'privatelink.wvd'))[0]}' + customImageId: customImageId + customRdpProperty: customRdpProperty deployFslogix: deployFslogix deploymentNameSuffix: deploymentNameSuffix + desktopFriendlyName: empty(desktopFriendlyName) ? string(stampIndex) : desktopFriendlyName diskEncryptionSetResourceId: tier3_hosts.outputs.diskEncryptionSetResourceId diskSku: diskSku domainJoinPassword: domainJoinPassword @@ -503,10 +464,17 @@ module management 'modules/management/management.bicep' = { enableAvdInsights: enableAvdInsights environmentAbbreviation: environmentAbbreviation fslogixStorageService: fslogixStorageService + hostPoolPublicNetworkAccess: hostPoolPublicNetworkAccess hostPoolType: hostPoolType + imageOffer: imageOffer + imagePublisher: imagePublisher + imageSku: imageSku + imageVersionResourceId: imageVersionResourceId + locationControlPlane: virtualNetwork.location locationVirtualMachines: locationVirtualMachines logAnalyticsWorkspaceRetention: logAnalyticsWorkspaceRetention logAnalyticsWorkspaceSku: logAnalyticsWorkspaceSku + maxSessionLimit: usersPerCore * virtualMachineVirtualCpuCount mlzTags: tier3_hosts.outputs.mlzTags namingConvention: tier3_hosts.outputs.namingConvention organizationalUnitPath: organizationalUnitPath @@ -516,71 +484,25 @@ module management 'modules/management/management.bicep' = { recoveryServices: recoveryServices recoveryServicesGeo: tier3_hosts.outputs.locationProperties.recoveryServicesGeo resourceAbbreviations: tier3_hosts.outputs.resourceAbbreviations - resourceGroupControlPlane: rgs[0].outputs.name - resourceGroupHosts: rgs[1].outputs.name - resourceGroupManagement: rgs[2].outputs.name - resourceGroupStorage: deployFslogix ? replace(tier3_hosts.outputs.namingConvention.resourceGroup, tier3_hosts.outputs.tokens.service, 'storage') : '' - roleDefinitions: roleDefinitions - scalingBeginPeakTime: scalingBeginPeakTime - scalingEndPeakTime: scalingEndPeakTime - scalingLimitSecondsToForceLogOffUser: scalingLimitSecondsToForceLogOffUser - scalingMinimumNumberOfRdsh: scalingMinimumNumberOfRdsh - scalingSessionThresholdPerCPU: scalingSessionThresholdPerCPU - scalingTool: scalingTool + resourceGroupName: replace(naming_management.outputs.names.resourceGroup, naming_management.outputs.tokens.service, 'management') + resourceGroupProfiles: replace(tier3_hosts.outputs.namingConvention.resourceGroup, tier3_hosts.outputs.tokens.service, 'profiles') + securityPrincipalObjectIds: map(securityPrincipals, item => item.objectId) serviceToken: tier3_hosts.outputs.tokens.service + sessionHostNamePrefix: replace( + tier3_hosts.outputs.namingConvention.virtualMachine, + tier3_hosts.outputs.tokens.service, + '' + ) storageService: storageService subnetResourceId: tier3_hosts.outputs.subnets[0].id subnets: tier3_hosts.outputs.subnets tags: tags - timeDifference: tier3_hosts.outputs.locationProperties.timeDifference timeZone: tier3_hosts.outputs.locationProperties.timeZone - virtualMachinePassword: virtualMachinePassword - virtualMachineUsername: virtualMachineUsername - } -} - -// AVD Control Plane -// This module deploys the host pool and desktop application group -module controlPlane 'modules/controlPlane/controlPlane.bicep' = { - name: 'deploy-control-plane-${deploymentNameSuffix}' - params: { - activeDirectorySolution: activeDirectorySolution - avdPrivateDnsZoneResourceId: '${privateDnsZoneResourceIdPrefix}${filter(tier3_hosts.outputs.privateDnsZones, name => startsWith(name, 'privatelink.wvd'))[0]}' - customImageId: customImageId - customRdpProperty: customRdpProperty - deploymentNameSuffix: deploymentNameSuffix - deploymentUserAssignedIdentityClientId: management.outputs.deploymentUserAssignedIdentityClientId - desktopFriendlyName: empty(desktopFriendlyName) ? string(stampIndex) : desktopFriendlyName - diskSku: diskSku - domainName: domainName - enableAvdInsights: enableAvdInsights - hostPoolPublicNetworkAccess: hostPoolPublicNetworkAccess - hostPoolType: hostPoolType - imageOffer: imageOffer - imagePublisher: imagePublisher - imageSku: imageSku - imageVersionResourceId: imageVersionResourceId - locationControlPlane: virtualNetwork.location - locationVirtualMachines: locationVirtualMachines - logAnalyticsWorkspaceResourceId: enableAvdInsights ? management.outputs.logAnalyticsWorkspaceResourceId : '' - managementVirtualMachineName: management.outputs.virtualMachineName - maxSessionLimit: usersPerCore * virtualMachineVirtualCpuCount - mlzTags: tier3_hosts.outputs.mlzTags - namingConvention: naming_controlPlane.outputs.names - resourceGroupControlPlane: rgs[0].outputs.name - resourceGroupManagement: rgs[2].outputs.name - roleDefinitions: roleDefinitions - securityPrincipalObjectIds: map(securityPrincipals, item => item.objectId) - serviceToken: naming_controlPlane.outputs.tokens.service - sessionHostNamePrefix: replace(tier3_hosts.outputs.namingConvention.virtualMachine, tier3_hosts.outputs.tokens.service, '') - subnetResourceId: tier3_hosts.outputs.subnets[1].id - tags: tags validationEnvironment: validationEnvironment + virtualMachinePassword: virtualMachinePassword virtualMachineSize: virtualMachineSize + virtualMachineUsername: virtualMachineUsername } - dependsOn: [ - rgs - ] } // AVD Workspaces @@ -589,40 +511,64 @@ module workspaces 'modules/sharedServices/sharedServices.bicep' = { name: 'deploy-workspaces-${deploymentNameSuffix}' scope: subscription(split(sharedServicesSubnetResourceId, '/')[2]) params: { - applicationGroupResourceId: controlPlane.outputs.applicationGroupResourceId + applicationGroupResourceId: management.outputs.applicationGroupResourceId avdPrivateDnsZoneResourceId: '${privateDnsZoneResourceIdPrefix}${filter(tier3_hosts.outputs.privateDnsZones, name => startsWith(name, 'privatelink.wvd'))[0]}' deploymentNameSuffix: deploymentNameSuffix deploymentUserAssignedIdentityClientId: management.outputs.deploymentUserAssignedIdentityClientId deploymentUserAssignedIdentityPrincipalId: management.outputs.deploymentUserAssignedIdentityPrincipalId enableAvdInsights: enableAvdInsights - existingApplicationGroupReferences: empty(existingFeedWorkspaceResourceId) ? [] : workspace.properties.applicationGroupReferences + existingApplicationGroupReferences: empty(existingFeedWorkspaceResourceId) + ? [] + : workspace.properties.applicationGroupReferences existingFeedWorkspaceResourceId: existingFeedWorkspaceResourceId existingWorkspace: !empty(existingFeedWorkspaceResourceId) - hostPoolName: controlPlane.outputs.hostPoolName + hostPoolName: management.outputs.hostPoolName locationControlPlane: virtualNetwork.location locationVirtualMachines: locationVirtualMachines logAnalyticsWorkspaceResourceId: management.outputs.logAnalyticsWorkspaceResourceId managementVirtualMachineName: management.outputs.virtualMachineName mlzTags: tier3_hosts.outputs.mlzTags - resourceGroupManagement: rgs[2].outputs.name + resourceGroupManagement: management.outputs.resourceGroupName sharedServicesSubnetResourceId: sharedServicesSubnetResourceId tags: tags - workspaceFeedDiagnoticSettingName: replace(replace(naming_hub.outputs.names.workspaceFeedDiagnosticSetting,naming_hub.outputs.tokens.service,'feed'),'-${stampIndex}','') - workspaceFeedName: replace(replace(naming_controlPlane.outputs.names.workspaceFeed,naming_controlPlane.outputs.tokens.service,'feed'),'-${stampIndex}','') - workspaceFeedNetworkInterfaceName: replace(replace(naming_hub.outputs.names.workspaceFeedNetworkInterface,naming_hub.outputs.tokens.service,'feed'),'-${stampIndex}','') - workspaceFeedPrivateEndpointName: replace(replace(naming_hub.outputs.names.workspaceFeedPrivateEndpoint,naming_hub.outputs.tokens.service,'feed'),'-${stampIndex}','') - workspaceFeedResourceGroupName: replace(replace(naming_controlPlane.outputs.names.resourceGroup, naming_controlPlane.outputs.tokens.service, 'feedWorkspace'), '-${stampIndex}', '') - workspaceFriendlyName: empty(workspaceFriendlyName) ? replace(replace(naming_controlPlane.outputs.names.workspaceFeed,'-${naming_controlPlane.outputs.tokens.service}',''), '-${stampIndex}', '') : '${workspaceFriendlyName} (${virtualNetwork.location})' - workspaceGlobalName: replace(replace(replace(naming_controlPlane.outputs.names.workspaceGlobal,naming_controlPlane.outputs.tokens.service,'global'),'-${stampIndex}',''),identifier,virtualNetwork.tags.resourcePrefix) - workspaceGlobalNetworkInterfaceName: replace(replace(replace(naming_hub.outputs.names.workspaceGlobalNetworkInterface,naming_hub.outputs.tokens.service,'global'),'-${stampIndex}',''),identifier,virtualNetwork.tags.resourcePrefix) + workspaceFeedDiagnoticSettingName: naming_management.outputs.names.workspaceFeedDiagnosticSetting + workspaceFeedName: naming_management.outputs.names.workspaceFeed + workspaceFeedNetworkInterfaceName: naming_management.outputs.names.workspaceFeedNetworkInterface + workspaceFeedPrivateEndpointName: naming_management.outputs.names.workspaceFeedPrivateEndpoint + workspaceFeedResourceGroupName: replace( + replace( + naming_management.outputs.names.resourceGroup, + naming_management.outputs.tokens.service, + 'feedWorkspace' + ), + '-${stampIndex}', + '' + ) + workspaceFriendlyName: empty(workspaceFriendlyName) + ? replace(naming_management.outputs.names.workspaceFeed, '-${naming_management.outputs.tokens.service}', '') + : '${workspaceFriendlyName} (${virtualNetwork.location})' + workspaceGlobalName: replace(naming_management.outputs.names.workspaceGlobal, identifier, virtualNetwork.tags.resourcePrefix) + workspaceGlobalNetworkInterfaceName: replace(naming_management.outputs.names.workspaceGlobalNetworkInterface, identifier, virtualNetwork.tags.resourcePrefix) workspaceGlobalPrivateDnsZoneResourceId: '${privateDnsZoneResourceIdPrefix}${filter(tier3_hosts.outputs.privateDnsZones, name => startsWith(name, 'privatelink-global.wvd'))[0]}' - workspaceGlobalPrivateEndpointName: replace(replace(replace(naming_hub.outputs.names.workspaceGlobalPrivateEndpoint,naming_hub.outputs.tokens.service,'global'),'-${stampIndex}',''),identifier,virtualNetwork.tags.resourcePrefix) - workspaceGlobalResourceGroupName: replace(replace(replace(naming_controlPlane.outputs.names.resourceGroup,naming_controlPlane.outputs.tokens.service,'globalWorkspace'),'-${stampIndex}',''),identifier,virtualNetwork.tags.resourcePrefix) + workspaceGlobalPrivateEndpointName: replace(naming_management.outputs.names.workspaceGlobalPrivateEndpoint, identifier, virtualNetwork.tags.resourcePrefix) + workspaceGlobalResourceGroupName: replace( + replace( + replace( + naming_management.outputs.names.resourceGroup, + naming_management.outputs.tokens.service, + 'globalWorkspace' + ), + '-${stampIndex}', + '' + ), + identifier, + virtualNetwork.tags.resourcePrefix + ) workspacePublicNetworkAccess: workspacePublicNetworkAccess } } -module fslogix 'modules/fslogix/fslogix.bicep' = { +module fslogix 'modules/fslogix/fslogix.bicep' = if (deployFslogix) { name: 'deploy-fslogix-${deploymentNameSuffix}' params: { activeDirectorySolution: activeDirectorySolution @@ -630,6 +576,7 @@ module fslogix 'modules/fslogix/fslogix.bicep' = { azureFilesPrivateDnsZoneResourceId: '${privateDnsZoneResourceIdPrefix}${filter(tier3_hosts.outputs.privateDnsZones, name => contains(name, 'file'))[0]}' deploymentNameSuffix: deploymentNameSuffix deploymentUserAssignedIdentityClientId: management.outputs.deploymentUserAssignedIdentityClientId + deploymentUserAssignedIdentityPrincipalId: management.outputs.deploymentUserAssignedIdentityPrincipalId dnsServers: string(tier3_hosts.outputs.dnsServers) domainJoinPassword: domainJoinPassword domainJoinUserPrincipalName: domainJoinUserPrincipalName @@ -640,8 +587,8 @@ module fslogix 'modules/fslogix/fslogix.bicep' = { fslogixContainerType: fslogixContainerType fslogixShareSizeInGB: fslogixShareSizeInGB fslogixStorageService: fslogixStorageService - functionAppName: management.outputs.functionAppName - hostPoolType: hostPoolType + functionAppPrincipalId: management.outputs.functionAppPrincipalId + hostPoolResourceId: management.outputs.hostPoolResourceId keyVaultUri: tier3_hosts.outputs.keyVaultUri location: locationVirtualMachines managementVirtualMachineName: management.outputs.virtualMachineName @@ -650,10 +597,9 @@ module fslogix 'modules/fslogix/fslogix.bicep' = { netbios: netbios organizationalUnitPath: organizationalUnitPath recoveryServices: recoveryServices - resourceGroupControlPlane: rgs[0].outputs.name - resourceGroupManagement: rgs[2].outputs.name - resourceGroupStorage: deployFslogix ? rgs[3].outputs.name : '' - securityPrincipalNames: map(securityPrincipals, item => item.name) + resourceGroupManagement: management.outputs.resourceGroupName + resourceGroupName: replace(tier3_hosts.outputs.namingConvention.resourceGroup, tier3_hosts.outputs.tokens.service, 'profiles') + securityPrincipalNames: map(securityPrincipals, item => item.displayName) securityPrincipalObjectIds: map(securityPrincipals, item => item.objectId) serviceToken: tier3_hosts.outputs.tokens.service smbServerLocation: tier3_hosts.outputs.locationProperties.timeZone @@ -666,10 +612,6 @@ module fslogix 'modules/fslogix/fslogix.bicep' = { subnets: tier3_hosts.outputs.subnets tags: tags } - dependsOn: [ - controlPlane - rgs - ] } module sessionHosts 'modules/sessionHosts/sessionHosts.bicep' = { @@ -680,10 +622,15 @@ module sessionHosts 'modules/sessionHosts/sessionHosts.bicep' = { availabilitySetsCount: availabilitySetsCount availabilitySetsIndex: beginAvSetRange availabilityZones: availabilityZones + avdConfigurationZipFileName: avdConfigurationZipFileName dataCollectionRuleResourceId: management.outputs.dataCollectionRuleResourceId deployFslogix: deployFslogix deploymentNameSuffix: deploymentNameSuffix deploymentUserAssignedIdentityClientId: management.outputs.deploymentUserAssignedIdentityClientId + deploymentUserAssignedIdentityPrincipalId: management.outputs.deploymentUserAssignedIdentityPrincipalId + diskAccessPolicyDefinitionId: management.outputs.diskAccessPolicyDefinitionId + diskAccessPolicyDisplayName: management.outputs.diskAccessPolicyDisplayName + diskAccessResourceId: management.outputs.diskAccessResourceId diskEncryptionSetResourceId: tier3_hosts.outputs.diskEncryptionSetResourceId diskSku: diskSku divisionRemainderValue: divisionRemainderValue @@ -694,11 +641,10 @@ module sessionHosts 'modules/sessionHosts/sessionHosts.bicep' = { enableAcceleratedNetworking: enableAcceleratedNetworking enableAvdInsights: enableAvdInsights enableRecoveryServices: recoveryServices - enableScalingTool: scalingTool environmentAbbreviation: environmentAbbreviation fslogixContainerType: fslogixContainerType - functionAppName: management.outputs.functionAppName - hostPoolName: controlPlane.outputs.hostPoolName + hostPoolName: management.outputs.hostPoolName + hostPoolResourceId: management.outputs.hostPoolResourceId hostPoolType: hostPoolType identifier: identifier imageOffer: imageOffer @@ -706,40 +652,41 @@ module sessionHosts 'modules/sessionHosts/sessionHosts.bicep' = { imageSku: imageSku imageVersionResourceId: imageVersionResourceId location: locationVirtualMachines + logAnalyticsWorkspaceResourceId: management.outputs.logAnalyticsWorkspaceResourceId managementVirtualMachineName: management.outputs.virtualMachineName maxResourcesPerTemplateDeployment: maxResourcesPerTemplateDeployment mlzTags: tier3_hosts.outputs.mlzTags namingConvention: tier3_hosts.outputs.namingConvention - netAppFileShares: deployFslogix - ? fslogix.outputs.netAppShares - : [ - 'None' - ] + netAppFileShares: deployFslogix ? fslogix.outputs.netAppShares : [ + 'None' + ] organizationalUnitPath: organizationalUnitPath - pooledHostPool: pooledHostPool + profile: profile recoveryServicesVaultName: management.outputs.recoveryServicesVaultName - resourceGroupControlPlane: rgs[0].outputs.name - resourceGroupHosts: rgs[1].outputs.name - resourceGroupManagement: rgs[2].outputs.name - roleDefinitions: roleDefinitions + resourceGroupManagement: management.outputs.resourceGroupName + resourceGroupName: replace(tier3_hosts.outputs.namingConvention.resourceGroup, tier3_hosts.outputs.tokens.service, 'hosts') + scalingWeekdaysOffPeakStartTime: scalingWeekdaysOffPeakStartTime + scalingWeekdaysPeakStartTime: scalingWeekdaysPeakStartTime + scalingWeekendsOffPeakStartTime: scalingWeekendsOffPeakStartTime + scalingWeekendsPeakStartTime: scalingWeekendsPeakStartTime securityPrincipalObjectIds: map(securityPrincipals, item => item.objectId) serviceToken: tier3_hosts.outputs.tokens.service sessionHostBatchCount: sessionHostBatchCount sessionHostIndex: sessionHostIndex - storageAccountNamePrefix: fslogix.outputs.storageAccountNamePrefix + storageAccountNamePrefix: deployFslogix ? fslogix.outputs.storageAccountNamePrefix : '' storageCount: storageCount storageIndex: storageIndex storageService: storageService storageSuffix: storageSuffix subnetResourceId: tier3_hosts.outputs.subnets[0].id tags: tags + timeZone: tier3_hosts.outputs.locationProperties.timeZone virtualMachinePassword: virtualMachinePassword virtualMachineSize: virtualMachineSize virtualMachineUsername: virtualMachineUsername } dependsOn: [ fslogix - rgs ] } @@ -748,7 +695,7 @@ module cleanUp 'modules/cleanUp/cleanUp.bicep' = { params: { deploymentNameSuffix: deploymentNameSuffix location: locationVirtualMachines - resourceGroupManagement: rgs[2].outputs.name + resourceGroupManagement: management.outputs.resourceGroupName userAssignedIdentityClientId: management.outputs.deploymentUserAssignedIdentityClientId virtualMachineResourceId: management.outputs.virtualMachineResourceId } diff --git a/src/bicep/add-ons/azure-virtual-desktop/solution.json b/src/bicep/add-ons/azure-virtual-desktop/solution.json index 85881aa3a..d9109271c 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/solution.json +++ b/src/bicep/add-ons/azure-virtual-desktop/solution.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "9820015838062663350" + "version": "0.31.92.45157", + "templateHash": "8358069094561879230" } }, "parameters": { @@ -39,6 +39,13 @@ "description": "The availability zones allowed for the AVD session hosts deployment location." } }, + "avdConfigurationZipFileName": { + "type": "string", + "defaultValue": "Configuration_1.0.02790.438.zip", + "metadata": { + "description": "The file name for the ZIP file containing the AVD agents and DSC configuration." + } + }, "avdObjectId": { "type": "string", "metadata": { @@ -153,7 +160,7 @@ "type": "bool", "defaultValue": true, "metadata": { - "description": "Deploys the required resources to monitor the function app for the Scaling Tool and Auto Increase Premium File Share solutions." + "description": "Deploys the required resources to monitor the function app for the Auto Increase Premium File Share solutions." } }, "enableAvdInsights": { @@ -234,7 +241,7 @@ "type": "string", "defaultValue": "", "metadata": { - "description": "The subnet address prefix for the delegated subnet for the Azure Function App. This subnet is required for the Scaling Tool and the Auto Increase Premium File Share Quotas tool." + "description": "The subnet address prefix for the delegated subnet for the Azure Function App. This subnet is required for the Auto Increase Premium File Share Quotas tool." } }, "hostPoolPublicNetworkAccess": { @@ -251,15 +258,13 @@ }, "hostPoolType": { "type": "string", - "defaultValue": "Pooled DepthFirst", + "defaultValue": "Pooled", "allowedValues": [ - "Pooled DepthFirst", - "Pooled BreadthFirst", - "Personal Automatic", - "Personal Direct" + "Pooled", + "Personal" ], "metadata": { - "description": "These options specify the host pool type and depending on the type provides the load balancing options and assignment types." + "description": "The type of AVD host pool." } }, "hubAzureFirewallResourceId": { @@ -386,46 +391,32 @@ "description": "Enable backups to an Azure Recovery Services vault. For a pooled host pool this will enable backups on the Azure file share. For a personal host pool this will enable backups on the AVD sessions hosts." } }, - "scalingBeginPeakTime": { - "type": "string", - "defaultValue": "9:00", - "metadata": { - "description": "The time when session hosts will scale up and continue to stay on to support peak demand; Format 24 hours e.g. 9:00 for 9am" - } - }, - "scalingEndPeakTime": { + "scalingWeekdaysOffPeakStartTime": { "type": "string", "defaultValue": "17:00", "metadata": { - "description": "The time when session hosts will scale down and stay off to support low demand; Format 24 hours e.g. 17:00 for 5pm" + "description": "Off peak start time for weekdays in HH:mm format." } }, - "scalingLimitSecondsToForceLogOffUser": { + "scalingWeekdaysPeakStartTime": { "type": "string", - "defaultValue": "0", + "defaultValue": "09:00", "metadata": { - "description": "The number of seconds to wait before automatically signing out users. If set to 0 any session host that has user sessions will be left untouched" + "description": "Off peak start time for weekends in HH:mm format." } }, - "scalingMinimumNumberOfRdsh": { + "scalingWeekendsOffPeakStartTime": { "type": "string", - "defaultValue": "0", + "defaultValue": "17:00", "metadata": { - "description": "The minimum number of session host VMs to keep running during off-peak hours. The scaling tool will not work if all virtual machines are turned off and the Start VM On Connect solution is not enabled." + "description": "Peak start time for weekdays in HH:mm format." } }, - "scalingSessionThresholdPerCPU": { + "scalingWeekendsPeakStartTime": { "type": "string", - "defaultValue": "1", - "metadata": { - "description": "The maximum number of sessions per CPU that will be used as a threshold to determine when new session host VMs need to be started during peak hours" - } - }, - "scalingTool": { - "type": "bool", - "defaultValue": false, + "defaultValue": "09:00", "metadata": { - "description": "Deploys the required resources for the Scaling Tool. https://docs.microsoft.com/en-us/azure/virtual-desktop/scaling-automation-logic-apps" + "description": "Peak start time for weekends in HH:mm format." } }, "securityPrincipals": { @@ -582,7 +573,7 @@ "endAvSetRange": "[div(add(parameters('sessionHostCount'), parameters('sessionHostIndex')), variables('maxAvSetMembers'))]", "availabilitySetsCount": "[length(range(variables('beginAvSetRange'), add(sub(variables('endAvSetRange'), variables('beginAvSetRange')), 1)))]", "customImageId": "[if(empty(parameters('imageVersionResourceId')), 'null', format('\"{0}\"', parameters('imageVersionResourceId')))]", - "deployFslogix": "[if(or(equals(parameters('fslogixStorageService'), 'None'), not(contains(parameters('activeDirectorySolution'), 'DomainServices'))), false(), true())]", + "deployFslogix": "[if(and(contains(parameters('fslogixStorageService'), 'Azure'), contains(parameters('activeDirectorySolution'), 'DomainServices')), true(), false())]", "fileShareNames": { "CloudCacheProfileContainer": [ "profile-containers" @@ -601,15 +592,7 @@ }, "fileShares": "[variables('fileShareNames')[parameters('fslogixContainerType')]]", "netbios": "[split(parameters('domainName'), '.')[0]]", - "pooledHostPool": "[if(equals(split(parameters('hostPoolType'), ' ')[0], 'Pooled'), true(), false())]", "privateDnsZoneResourceIdPrefix": "[format('/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Network/privateDnsZones/', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4])]", - "resourceGroupServices": "[union(createArray('controlPlane', 'hosts', 'management'), if(variables('deployFslogix'), createArray('storage'), createArray()))]", - "roleDefinitions": { - "DesktopVirtualizationPowerOnContributor": "489581de-a3bd-480d-9518-53dea7416b33", - "DesktopVirtualizationUser": "1d18fff3-a72a-46b5-b4a9-0b38a3cd7e63", - "Reader": "acdd72a7-3385-48ef-bd42-f606fba81ae7", - "VirtualMachineUserLogin": "fb879df8-f326-4884-b1cf-06f3ad86be52" - }, "storageSku": "[if(equals(parameters('fslogixStorageService'), 'None'), 'None', split(parameters('fslogixStorageService'), ' ')[1])]", "storageService": "[split(parameters('fslogixStorageService'), ' ')[0]]", "storageSuffix": "[environment().suffixes.storage]", @@ -623,7 +606,7 @@ } ], "azureNetAppFiles": "[if(and(contains(parameters('fslogixStorageService'), 'AzureNetAppFiles'), not(empty(parameters('azureNetAppFilesSubnetAddressPrefix')))), createArray(createObject('name', 'AzureNetAppFiles', 'properties', createObject('addressPrefix', parameters('azureNetAppFilesSubnetAddressPrefix')))), createArray())]", - "functionApp": "[if(or(parameters('scalingTool'), equals(parameters('fslogixStorageService'), 'AzureFiles Premium')), createArray(createObject('name', 'FunctionAppOutbound', 'properties', createObject('addressPrefix', parameters('functionAppSubnetAddressPrefix')))), createArray())]" + "functionApp": "[if(equals(parameters('fslogixStorageService'), 'AzureFiles Premium'), createArray(createObject('name', 'FunctionAppOutbound', 'properties', createObject('addressPrefix', parameters('functionAppSubnetAddressPrefix')))), createArray())]" } }, "resources": [ @@ -645,7 +628,7 @@ { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('get-naming-cp-{0}', parameters('deploymentNameSuffix'))]", + "name": "[format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))]", "location": "[deployment().location]", "properties": { "expressionEvaluationOptions": { @@ -678,8 +661,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7616623366375139473" + "version": "0.31.92.45157", + "templateHash": "11125044402666498605" } }, "parameters": { @@ -705,6 +688,7 @@ "tokens": { "type": "object", "defaultValue": { + "purpose": "purpose_token", "resource": "resource_token", "service": "service_token" } @@ -1130,6 +1114,7 @@ "remoteApplicationGroups": "vdag", "resourceGroups": "rg", "routeTables": "rt", + "scalingPlans": "vdscaling", "storageAccounts": "st", "subnets": "snet", "userAssignedIdentities": "id", @@ -1140,11 +1125,11 @@ "locations": "[variables('$fxv#0')[environment().name]]", "locationAbbreviation": "[variables('locations')[parameters('location')].abbreviation]", "resourceAbbreviations": "[variables('$fxv#1')]", - "namingConvention": "[format('{0}-{1}{2}-{3}-{4}-{5}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), parameters('environmentAbbreviation'), variables('locationAbbreviation'))]", - "namingConvention_Service": "[format('{0}-{1}{2}-{3}-{4}-{5}-{6}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('tokens').service, parameters('networkName'), parameters('environmentAbbreviation'), variables('locationAbbreviation'))]", + "namingConvention": "[format('{0}-{1}{2}-{3}-{4}-{5}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), variables('locationAbbreviation'), parameters('environmentAbbreviation'))]", + "namingConvention_Service": "[format('{0}-{1}{2}-{3}-{4}-{5}-{6}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), parameters('tokens').service, variables('locationAbbreviation'), parameters('environmentAbbreviation'))]", "names": { "actionGroup": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').actionGroups)]", - "applicationGroup": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').applicationGroups)]", + "applicationGroup": "[replace(variables('namingConvention'), parameters('tokens').resource, format('{0}-desktop', variables('resourceAbbreviations').applicationGroups))]", "applicationInsights": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').applicationInsights)]", "appServicePlan": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').appServicePlans)]", "automationAccount": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').automationAccounts)]", @@ -1198,10 +1183,18 @@ "recoveryServicesVaultPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, variables('resourceAbbreviations').recoveryServicesVaults)]", "resourceGroup": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').resourceGroups)]", "routeTable": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').routeTables)]", + "scalingPlan": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').scalingPlans)]", + "scalingPlanDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').scalingPlans)]", "storageAccount": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').storageAccounts), parameters('networkName'), parameters('networkShortName'))]", "storageAccountDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", - "storageAccountNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", - "storageAccountPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", + "storageAccountBlobNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-blob', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountFileNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-file', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountQueueNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-queue', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountTableNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-table', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountBlobPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-blob', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountFilePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-file', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountQueuePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-queue', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountTablePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-table', variables('resourceAbbreviations').storageAccounts))]", "subnet": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').subnets)]", "userAssignedIdentity": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').userAssignedIdentities)]", "virtualMachine": "[replace(replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').virtualMachines), parameters('environmentAbbreviation'), first(parameters('environmentAbbreviation'))), parameters('networkName'), ''), '-', '')]", @@ -1209,14 +1202,14 @@ "virtualMachineNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').virtualMachines))]", "virtualNetwork": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').virtualNetworks)]", "virtualNetworkDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').virtualNetworks)]", - "workspaceFeed": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').workspaces), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobal": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').workspaces), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]" + "workspaceFeed": "[replace(replace(variables('namingConvention'), parameters('tokens').resource, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceFeedDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceFeedNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceFeedPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobal": "[replace(replace(variables('namingConvention'), parameters('tokens').resource, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobalDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobalNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobalPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]" } }, "resources": [], @@ -1244,7 +1237,7 @@ { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('get-naming-hub-{0}', parameters('deploymentNameSuffix'))]", + "name": "[format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))]", "location": "[deployment().location]", "properties": { "expressionEvaluationOptions": { @@ -1252,23 +1245,68 @@ }, "mode": "Incremental", "parameters": { + "additionalSubnets": { + "value": "[union(variables('subnets').avdControlPlane, variables('subnets').azureNetAppFiles, variables('subnets').functionApp)]" + }, + "deployActivityLogDiagnosticSetting": { + "value": "[parameters('deployActivityLogDiagnosticSetting')]" + }, + "deployDefender": { + "value": "[parameters('deployDefender')]" + }, + "deploymentNameSuffix": { + "value": "[parameters('deploymentNameSuffix')]" + }, + "deployNetworkWatcher": { + "value": "[parameters('deployNetworkWatcher')]" + }, + "deployPolicy": { + "value": "[parameters('deployPolicy')]" + }, + "emailSecurityContact": { + "value": "[parameters('emailSecurityContact')]" + }, "environmentAbbreviation": { "value": "[parameters('environmentAbbreviation')]" }, - "location": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Network/virtualNetworks', split(parameters('hubVirtualNetworkResourceId'), '/')[8]), '2023-11-01', 'full').location]" - }, - "networkName": { - "value": "avd" + "firewallResourceId": { + "value": "[parameters('hubAzureFirewallResourceId')]" }, - "networkShortName": { - "value": "avd" + "hubVirtualNetworkResourceId": { + "value": "[parameters('hubVirtualNetworkResourceId')]" }, - "resourcePrefix": { + "identifier": { "value": "[parameters('identifier')]" }, + "location": { + "value": "[parameters('locationVirtualMachines')]" + }, + "logAnalyticsWorkspaceResourceId": { + "value": "[parameters('operationsLogAnalyticsWorkspaceResourceId')]" + }, + "policy": { + "value": "[parameters('policy')]" + }, "stampIndex": { "value": "[string(parameters('stampIndex'))]" + }, + "subnetName": { + "value": "AvdSessionHosts" + }, + "subnetAddressPrefix": { + "value": "[parameters('subnetAddressPrefixes')[0]]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "virtualNetworkAddressPrefix": { + "value": "[parameters('virtualNetworkAddressPrefixes')[0]]" + }, + "workloadName": { + "value": "avd" + }, + "workloadShortName": { + "value": "avd" } }, "template": { @@ -1277,829 +1315,185 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7616623366375139473" + "version": "0.31.92.45157", + "templateHash": "14266827690996546891" } }, "parameters": { + "additionalSubnets": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "An array of additional subnets to support the tier3 workload." + } + }, + "deployActivityLogDiagnosticSetting": { + "type": "bool", + "metadata": { + "description": "Choose whether to deploy a diagnostic setting for the Activity Log." + } + }, + "deployDefender": { + "type": "bool", + "metadata": { + "description": "Choose whether to deploy Defender for Cloud." + } + }, + "deploymentNameSuffix": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "The suffix to append to the deployment name. It defaults to the current UTC date and time." + } + }, + "deployNetworkWatcher": { + "type": "bool", + "metadata": { + "description": "Choose whether to deploy Network Watcher for the deployment location." + } + }, + "deployPolicy": { + "type": "bool", + "metadata": { + "description": "Choose whether to deploy a policy assignment." + } + }, + "emailSecurityContact": { + "type": "string", + "metadata": { + "description": "The email address to use for Defender for Cloud notifications." + } + }, "environmentAbbreviation": { - "type": "string" + "type": "string", + "defaultValue": "dev", + "allowedValues": [ + "dev", + "prod", + "test" + ], + "metadata": { + "description": "The abbreviation for the environment." + } + }, + "firewallResourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Azure Firewall in the HUB." + } + }, + "hubVirtualNetworkResourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the HUB Virtual Network." + } + }, + "identifier": { + "type": "string", + "maxLength": 3, + "metadata": { + "description": "The identifier for the resource names. This value should represent the workload, project, or business unit." + } + }, + "keyVaultDiagnosticsLogs": { + "type": "array", + "defaultValue": [ + { + "category": "AuditEvent", + "enabled": true + }, + { + "category": "AzurePolicyEvaluationDetails", + "enabled": true + } + ], + "metadata": { + "description": "An array of Key Vault Diagnostic Logs categories to collect. See \"https://learn.microsoft.com/en-us/azure/key-vault/general/logging?tabs=Vault\" for valid values." + } }, "location": { - "type": "string" + "type": "string", + "defaultValue": "[deployment().location]", + "metadata": { + "description": "The location for the deployment. It defaults to the location of the deployment." + } }, - "networkName": { - "type": "string" + "logAnalyticsWorkspaceResourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Log Analytics Workspace to use for log storage." + } }, - "networkShortName": { - "type": "string" + "logStorageSkuName": { + "type": "string", + "defaultValue": "Standard_GRS", + "metadata": { + "description": "The Storage Account SKU to use for log storage. It defaults to \"Standard_GRS\". See https://docs.microsoft.com/en-us/rest/api/storagerp/srp_sku_types for valid settings." + } }, - "resourcePrefix": { - "type": "string" + "networkSecurityGroupDiagnosticsLogs": { + "type": "array", + "defaultValue": [ + { + "category": "NetworkSecurityGroupEvent", + "enabled": true + }, + { + "category": "NetworkSecurityGroupRuleCounter", + "enabled": true + } + ], + "metadata": { + "description": "An array of Network Security Group diagnostic logs to apply to the workload Virtual Network. See https://docs.microsoft.com/en-us/azure/virtual-network/virtual-network-nsg-manage-log#log-categories for valid settings." + } + }, + "networkSecurityGroupDiagnosticsMetrics": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "The metrics to monitor for the Network Security Group." + } + }, + "networkSecurityGroupRules": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "The rules to apply to the Network Security Group." + } + }, + "policy": { + "type": "string", + "defaultValue": "NISTRev4", + "metadata": { + "description": "The policy to assign to the workload." + } }, "stampIndex": { "type": "string", - "defaultValue": "" + "defaultValue": "", + "metadata": { + "description": "The stamp index allows for multiple AVD stamps with the same business unit or project to support different use cases." + } }, - "tokens": { + "subnetAddressPrefix": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The address prefix for the workload subnet." + } + }, + "subnetName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The custom name for the workload subnet if the naming convention is not desired. Subnets are child resources and do not require a unique name between virtual networks, only within the same virtual network." + } + }, + "tags": { "type": "object", - "defaultValue": { - "resource": "resource_token", - "service": "service_token" - } - } - }, - "variables": { - "$fxv#0": { - "AzureChina": { - "chinaeast": { - "abbreviation": "cne", - "recoveryServicesGeo": "sha", - "timeDifference": "+8:00", - "timeZone": "China Standard Time" - }, - "chinaeast2": { - "abbreviation": "cne2", - "recoveryServicesGeo": "sha2", - "timeDifference": "+8:00", - "timeZone": "China Standard Time" - }, - "chinanorth": { - "abbreviation": "cnn", - "recoveryServicesGeo": "bjb", - "timeDifference": "+8:00", - "timeZone": "China Standard Time" - }, - "chinanorth2": { - "abbreviation": "cnn2", - "recoveryServicesGeo": "bjb2", - "timeDifference": "+8:00", - "timeZone": "China Standard Time" - } - }, - "AzureCloud": { - "australiacentral": { - "abbreviation": "auc", - "recoveryServicesGeo": "acl", - "timeDifference": "+10:00", - "timeZone": "AUS Eastern Standard Time" - }, - "australiacentral2": { - "abbreviation": "auc2", - "recoveryServicesGeo": "acl2", - "timeDifference": "+10:00", - "timeZone": "AUS Eastern Standard Time" - }, - "australiaeast": { - "abbreviation": "aue", - "recoveryServicesGeo": "ae", - "timeDifference": "+10:00", - "timeZone": "AUS Eastern Standard Time" - }, - "australiasoutheast": { - "abbreviation": "ause", - "recoveryServicesGeo": "ase", - "timeDifference": "+10:00", - "timeZone": "AUS Eastern Standard Time" - }, - "brazilsouth": { - "abbreviation": "brs", - "recoveryServicesGeo": "brs", - "timeDifference": "-3:00", - "timeZone": "E. South America Standard Time" - }, - "brazilsoutheast": { - "abbreviation": "brse", - "recoveryServicesGeo": "bse", - "timeDifference": "-3:00", - "timeZone": "E. South America Standard Time" - }, - "canadacentral": { - "abbreviation": "cac", - "recoveryServicesGeo": "cnc", - "timeDifference": "-5:00", - "timeZone": "Eastern Standard Time" - }, - "canadaeast": { - "abbreviation": "cae", - "recoveryServicesGeo": "cne", - "timeDifference": "-5:00", - "timeZone": "Eastern Standard Time" - }, - "centralindia": { - "abbreviation": "inc", - "recoveryServicesGeo": "inc", - "timeDifference": "+5:30", - "timeZone": "India Standard Time" - }, - "centralus": { - "abbreviation": "usc", - "recoveryServicesGeo": "cus", - "timeDifference": "-6:00", - "timeZone": "Central Standard Time" - }, - "eastasia": { - "abbreviation": "ase", - "recoveryServicesGeo": "ea", - "timeDifference": "+8:00", - "timeZone": "China Standard Time" - }, - "eastus": { - "abbreviation": "use", - "recoveryServicesGeo": "eus", - "timeDifference": "-5:00", - "timeZone": "Eastern Standard Time" - }, - "eastus2": { - "abbreviation": "use2", - "recoveryServicesGeo": "eus2", - "timeDifference": "-5:00", - "timeZone": "Eastern Standard Time" - }, - "francecentral": { - "abbreviation": "frc", - "recoveryServicesGeo": "frc", - "timeDifference": "+1:00", - "timeZone": "Central Europe Standard Time" - }, - "francesouth": { - "abbreviation": "frs", - "recoveryServicesGeo": "frs", - "timeDifference": "+1:00", - "timeZone": "Central Europe Standard Time" - }, - "germanynorth": { - "abbreviation": "den", - "recoveryServicesGeo": "gn", - "timeDifference": "+1:00", - "timeZone": "Central Europe Standard Time" - }, - "germanywestcentral": { - "abbreviation": "dewc", - "recoveryServicesGeo": "gwc", - "timeDifference": "+1:00", - "timeZone": "Central Europe Standard Time" - }, - "israelcentral": { - "abbreviation": "ilc", - "recoveryServicesGeo": "ilc", - "timeDifference": "+2:00", - "timeZone": "Israel Standard Time" - }, - "italynorth": { - "abbreviation": "itn", - "recoveryServicesGeo": "itn", - "timeDifference": "+1:00", - "timeZone": "Central Europe Standard Time" - }, - "japaneast": { - "abbreviation": "jpe", - "recoveryServicesGeo": "jpe", - "timeDifference": "+9:00", - "timeZone": "Tokyo Standard Time" - }, - "japanwest": { - "abbreviation": "jpw", - "recoveryServicesGeo": "jpw", - "timeDifference": "+9:00", - "timeZone": "Tokyo Standard Time" - }, - "jioindiacentral": { - "abbreviation": "injc", - "recoveryServicesGeo": "jic", - "timeDifference": "+5:30", - "timeZone": "India Standard Time" - }, - "jioindiawest": { - "abbreviation": "injw", - "recoveryServicesGeo": "jiw", - "timeDifference": "+5:30", - "timeZone": "India Standard Time" - }, - "koreacentral": { - "abbreviation": "krc", - "recoveryServicesGeo": "krc", - "timeDifference": "+9:00", - "timeZone": "Korea Standard Time" - }, - "koreasouth": { - "abbreviation": "krs", - "recoveryServicesGeo": "krs", - "timeDifference": "+9:00", - "timeZone": "Korea Standard Time" - }, - "northcentralus": { - "abbreviation": "usnc", - "recoveryServicesGeo": "ncus", - "timeDifference": "-6:00", - "timeZone": "Central Standard Time" - }, - "northeurope": { - "abbreviation": "eun", - "recoveryServicesGeo": "ne", - "timeDifference": "0:00", - "timeZone": "GMT Standard Time" - }, - "norwayeast": { - "abbreviation": "noe", - "recoveryServicesGeo": "nwe", - "timeDifference": "+1:00", - "timeZone": "Central Europe Standard Time" - }, - "norwaywest": { - "abbreviation": "now", - "recoveryServicesGeo": "nww", - "timeDifference": "+1:00", - "timeZone": "Central Europe Standard Time" - }, - "polandcentral": { - "abbreviation": "plc", - "recoveryServicesGeo": "plc", - "timeDifference": "+1:00", - "timeZone": "Central Europe Standard Time" - }, - "qatarcentral": { - "abbreviation": "qac", - "recoveryServicesGeo": "qac", - "timeDifference": "+3:00", - "timeZone": "Arabian Standard Time" - }, - "southafricanorth": { - "abbreviation": "zan", - "recoveryServicesGeo": "san", - "timeDifference": "+2:00", - "timeZone": "South Africa Standard Time" - }, - "southafricawest": { - "abbreviation": "zaw", - "recoveryServicesGeo": "saw", - "timeDifference": "+2:00", - "timeZone": "South Africa Standard Time" - }, - "southcentralus": { - "abbreviation": "ussc", - "recoveryServicesGeo": "scus", - "timeDifference": "-6:00", - "timeZone": "Central Standard Time" - }, - "southeastasia": { - "abbreviation": "asse", - "recoveryServicesGeo": "sea", - "timeDifference": "+8:00", - "timeZone": "Singapore Standard Time" - }, - "southindia": { - "abbreviation": "ins", - "recoveryServicesGeo": "ins", - "timeDifference": "+5:30", - "timeZone": "India Standard Time" - }, - "swedencentral": { - "abbreviation": "sec", - "recoveryServicesGeo": "sdc", - "timeDifference": "+1:00", - "timeZone": "Central Europe Standard Time" - }, - "switzerlandnorth": { - "abbreviation": "chn", - "recoveryServicesGeo": "szn", - "timeDifference": "+1:00", - "timeZone": "Central Europe Standard Time" - }, - "switzerlandwest": { - "abbreviation": "chw", - "recoveryServicesGeo": "szw", - "timeDifference": "+1:00", - "timeZone": "Central Europe Standard Time" - }, - "uaecentral": { - "abbreviation": "aec", - "recoveryServicesGeo": "uac", - "timeDifference": "+3:00", - "timeZone": "Arabian Standard Time" - }, - "uaenorth": { - "abbreviation": "aen", - "recoveryServicesGeo": "uan", - "timeDifference": "+3:00", - "timeZone": "Arabian Standard Time" - }, - "uksouth": { - "abbreviation": "uks", - "recoveryServicesGeo": "uks", - "timeDifference": "0:00", - "timeZone": "GMT Standard Time" - }, - "ukwest": { - "abbreviation": "ukw", - "recoveryServicesGeo": "ukw", - "timeDifference": "0:00", - "timeZone": "GMT Standard Time" - }, - "westcentralus": { - "abbreviation": "uswc", - "recoveryServicesGeo": "wcus", - "timeDifference": "-7:00", - "timeZone": "Mountain Standard Time" - }, - "westeurope": { - "abbreviation": "euw", - "recoveryServicesGeo": "we", - "timeDifference": "+1:00", - "timeZone": "Central Europe Standard Time" - }, - "westindia": { - "abbreviation": "inw", - "recoveryServicesGeo": "inw", - "timeDifference": "+5:30", - "timeZone": "India Standard Time" - }, - "westus": { - "abbreviation": "usw", - "recoveryServicesGeo": "wus", - "timeDifference": "-8:00", - "timeZone": "Pacific Standard Time" - }, - "westus2": { - "abbreviation": "usw2", - "recoveryServicesGeo": "wus2", - "timeDifference": "-8:00", - "timeZone": "Pacific Standard Time" - }, - "westus3": { - "abbreviation": "usw3", - "recoveryServicesGeo": "wus3", - "timeDifference": "-7:00", - "timeZone": "Mountain Standard Time" - } - }, - "AzureUSGovernment": { - "usdodcentral": { - "abbreviation": "dodc", - "recoveryServicesGeo": "udc", - "timeDifference": "-6:00", - "timeZone": "Central Standard Time" - }, - "usdodeast": { - "abbreviation": "dode", - "recoveryServicesGeo": "ude", - "timeDifference": "-5:00", - "timeZone": "Eastern Standard Time" - }, - "usgovarizona": { - "abbreviation": "az", - "recoveryServicesGeo": "uga", - "timeDifference": "-7:00", - "timeZone": "Mountain Standard Time" - }, - "usgovtexas": { - "abbreviation": "tx", - "recoveryServicesGeo": "ugt", - "timeDifference": "-6:00", - "timeZone": "Central Standard Time" - }, - "usgovvirginia": { - "abbreviation": "va", - "recoveryServicesGeo": "ugv", - "timeDifference": "-5:00", - "timeZone": "Eastern Standard Time" - } - }, - "USNat": { - "usnateast": { - "abbreviation": "east", - "recoveryServicesGeo": "exe", - "timeDifference": "-5:00", - "timeZone": "Eastern Standard Time" - }, - "usnatwest": { - "abbreviation": "west", - "recoveryServicesGeo": "exw", - "timeDifference": "-8:00", - "timeZone": "Pacific Standard Time" - } - }, - "USSec": { - "usseceast": { - "abbreviation": "east", - "recoveryServicesGeo": "rxe", - "timeDifference": "-5:00", - "timeZone": "Eastern Standard Time" - }, - "ussecwest": { - "abbreviation": "west", - "recoveryServicesGeo": "rxw", - "timeDifference": "-8:00", - "timeZone": "Pacific Standard Time" - } - } - }, - "$fxv#1": { - "actionGroups": "ag", - "applicationGroups": "vdag", - "applicationInsights": "appi", - "appServicePlans": "asp", - "automationAccounts": "aa", - "availabilitySets": "avail", - "azureFirewalls": "afw", - "bastionHosts": "bas", - "computeGallieries": "cg", - "dataCollectionEndpoints": "dce", - "dataCollectionRuleAssociations": "dcra", - "dataCollectionRules": "dcr", - "diagnosticSettings": "diag", - "diskAccesses": "da", - "diskEncryptionSets": "des", - "disks": "disk", - "firewallPolicies": "afwp", - "functionApps": "func", - "hostPools": "vdpool", - "ipConfigurations": "ipconf", - "keyVaults": "kv", - "logAnalyticsWorkspaces": "log", - "netAppAccounts": "naa", - "netAppCapacityPools": "nacp", - "networkInterfaces": "nic", - "networkSecurityGroups": "nsg", - "networkWatchers": "nw", - "privateEndpoints": "pe", - "privateLinkScopes": "pls", - "publicIPAddresses": "pip", - "recoveryServicesVaults": "rsv", - "remoteApplicationGroups": "vdag", - "resourceGroups": "rg", - "routeTables": "rt", - "storageAccounts": "st", - "subnets": "snet", - "userAssignedIdentities": "id", - "virtualMachines": "vm", - "virtualNetworks": "vnet", - "workspaces": "vdws" - }, - "locations": "[variables('$fxv#0')[environment().name]]", - "locationAbbreviation": "[variables('locations')[parameters('location')].abbreviation]", - "resourceAbbreviations": "[variables('$fxv#1')]", - "namingConvention": "[format('{0}-{1}{2}-{3}-{4}-{5}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), parameters('environmentAbbreviation'), variables('locationAbbreviation'))]", - "namingConvention_Service": "[format('{0}-{1}{2}-{3}-{4}-{5}-{6}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('tokens').service, parameters('networkName'), parameters('environmentAbbreviation'), variables('locationAbbreviation'))]", - "names": { - "actionGroup": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').actionGroups)]", - "applicationGroup": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').applicationGroups)]", - "applicationInsights": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').applicationInsights)]", - "appServicePlan": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').appServicePlans)]", - "automationAccount": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').automationAccounts)]", - "automationAccountDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').automationAccounts)]", - "automationAccountNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, variables('resourceAbbreviations').automationAccounts)]", - "automationAccountPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, variables('resourceAbbreviations').automationAccounts)]", - "availabilitySet": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').availabilitySets)]", - "azureFirewall": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').azureFirewalls)]", - "azureFirewallClientPublicIPAddress": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').publicIPAddresses), parameters('tokens').service, format('client-{0}', variables('resourceAbbreviations').azureFirewalls))]", - "azureFirewallClientPublicIPAddressDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-client-{1}', variables('resourceAbbreviations').publicIPAddresses, variables('resourceAbbreviations').azureFirewalls))]", - "azureFirewallDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').azureFirewalls)]", - "azureFirewallManagementPublicIPAddress": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').publicIPAddresses), parameters('tokens').service, format('mgmt-{0}', variables('resourceAbbreviations').azureFirewalls))]", - "azureFirewallManagementPublicIPAddressDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-mgmt-{1}', variables('resourceAbbreviations').publicIPAddresses, variables('resourceAbbreviations').azureFirewalls))]", - "azureFirewallPolicy": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').firewallPolicies)]", - "bastionHost": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').bastionHosts)]", - "bastionHostNetworkSecurityGroup": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkSecurityGroups), parameters('tokens').service, variables('resourceAbbreviations').bastionHosts)]", - "bastionHostDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').bastionHosts)]", - "bastionHostPublicIPAddress": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').publicIPAddresses), parameters('tokens').service, variables('resourceAbbreviations').bastionHosts)]", - "bastionHostPublicIPAddressDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', variables('resourceAbbreviations').publicIPAddresses, variables('resourceAbbreviations').bastionHosts))]", - "computeGallery": "[replace(replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').computeGallieries), '-', '_')]", - "dataCollectionEndpoint": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').dataCollectionEndpoints)]", - "dataCollectionRuleAssociation": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').dataCollectionRuleAssociations)]", - "dataCollectionRule": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').dataCollectionRules)]", - "diskAccess": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').diskAccesses)]", - "diskAccessNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, variables('resourceAbbreviations').diskAccesses)]", - "diskAccessPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, variables('resourceAbbreviations').diskAccesses)]", - "diskEncryptionSet": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').diskEncryptionSets)]", - "functionApp": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').functionApps)]", - "functionAppNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', variables('resourceAbbreviations').functionApps, parameters('tokens').service))]", - "functionAppPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', variables('resourceAbbreviations').functionApps, parameters('tokens').service))]", - "hostPool": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').hostPools)]", - "hostPoolDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').hostPools)]", - "hostPoolNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, variables('resourceAbbreviations').hostPools)]", - "hostPoolPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, variables('resourceAbbreviations').hostPools)]", - "keyVault": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').keyVaults), '-', ''), parameters('networkName'), parameters('networkShortName'))]", - "keyVaultDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', variables('resourceAbbreviations').keyVaults, parameters('tokens').service))]", - "keyVaultNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', variables('resourceAbbreviations').keyVaults, parameters('tokens').service))]", - "keyVaultPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', variables('resourceAbbreviations').keyVaults, parameters('tokens').service))]", - "logAnalyticsWorkspace": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').logAnalyticsWorkspaces)]", - "logAnalyticsWorkspaceDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').logAnalyticsWorkspaces)]", - "netAppAccountCapacityPool": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').netAppCapacityPools)]", - "netAppAccount": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').netAppAccounts)]", - "networkSecurityGroup": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').networkSecurityGroups)]", - "networkSecurityGroupDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').networkSecurityGroups)]", - "networkWatcher": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').networkWatchers)]", - "privateLinkScope": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').privateLinkScopes)]", - "privateLinkScopeNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, variables('resourceAbbreviations').privateLinkScopes)]", - "privateLinkScopePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, variables('resourceAbbreviations').privateLinkScopes)]", - "recoveryServicesVault": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').recoveryServicesVaults)]", - "recoveryServicesVaultNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, variables('resourceAbbreviations').recoveryServicesVaults)]", - "recoveryServicesVaultPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, variables('resourceAbbreviations').recoveryServicesVaults)]", - "resourceGroup": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').resourceGroups)]", - "routeTable": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').routeTables)]", - "storageAccount": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').storageAccounts), parameters('networkName'), parameters('networkShortName'))]", - "storageAccountDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", - "storageAccountNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", - "storageAccountPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", - "subnet": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').subnets)]", - "userAssignedIdentity": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').userAssignedIdentities)]", - "virtualMachine": "[replace(replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').virtualMachines), parameters('environmentAbbreviation'), first(parameters('environmentAbbreviation'))), parameters('networkName'), ''), '-', '')]", - "virtualMachineDisk": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').disks), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').virtualMachines))]", - "virtualMachineNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').virtualMachines))]", - "virtualNetwork": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').virtualNetworks)]", - "virtualNetworkDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').virtualNetworks)]", - "workspaceFeed": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').workspaces), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobal": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').workspaces), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]" - } - }, - "resources": [], - "outputs": { - "locations": { - "type": "object", - "value": "[variables('locations')]" - }, - "names": { - "type": "object", - "value": "[variables('names')]" - }, - "resourceAbbreviations": { - "type": "object", - "value": "[variables('resourceAbbreviations')]" - }, - "tokens": { - "type": "object", - "value": "[parameters('tokens')]" - } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))]", - "location": "[deployment().location]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "additionalSubnets": { - "value": "[union(variables('subnets').avdControlPlane, variables('subnets').azureNetAppFiles, variables('subnets').functionApp)]" - }, - "deployActivityLogDiagnosticSetting": { - "value": "[parameters('deployActivityLogDiagnosticSetting')]" - }, - "deployDefender": { - "value": "[parameters('deployDefender')]" - }, - "deploymentNameSuffix": { - "value": "[parameters('deploymentNameSuffix')]" - }, - "deployNetworkWatcher": { - "value": "[parameters('deployNetworkWatcher')]" - }, - "deployPolicy": { - "value": "[parameters('deployPolicy')]" - }, - "emailSecurityContact": { - "value": "[parameters('emailSecurityContact')]" - }, - "environmentAbbreviation": { - "value": "[parameters('environmentAbbreviation')]" - }, - "firewallResourceId": { - "value": "[parameters('hubAzureFirewallResourceId')]" - }, - "hubVirtualNetworkResourceId": { - "value": "[parameters('hubVirtualNetworkResourceId')]" - }, - "identifier": { - "value": "[parameters('identifier')]" - }, - "location": { - "value": "[parameters('locationVirtualMachines')]" - }, - "logAnalyticsWorkspaceResourceId": { - "value": "[parameters('operationsLogAnalyticsWorkspaceResourceId')]" - }, - "policy": { - "value": "[parameters('policy')]" - }, - "stampIndex": { - "value": "[string(parameters('stampIndex'))]" - }, - "subnetName": { - "value": "AvdSessionHosts" - }, - "subnetAddressPrefix": { - "value": "[parameters('subnetAddressPrefixes')[0]]" - }, - "tags": { - "value": "[parameters('tags')]" - }, - "virtualNetworkAddressPrefix": { - "value": "[parameters('virtualNetworkAddressPrefixes')[0]]" - }, - "workloadName": { - "value": "avd" - }, - "workloadShortName": { - "value": "avd" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "5332647717093931418" - } - }, - "parameters": { - "additionalSubnets": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "An array of additional subnets to support the tier3 workload." - } - }, - "deployActivityLogDiagnosticSetting": { - "type": "bool", - "metadata": { - "description": "Choose whether to deploy a diagnostic setting for the Activity Log." - } - }, - "deployDefender": { - "type": "bool", - "metadata": { - "description": "Choose whether to deploy Defender for Cloud." - } - }, - "deploymentNameSuffix": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "The suffix to append to the deployment name. It defaults to the current UTC date and time." - } - }, - "deployNetworkWatcher": { - "type": "bool", - "metadata": { - "description": "Choose whether to deploy Network Watcher for the deployment location." - } - }, - "deployPolicy": { - "type": "bool", - "metadata": { - "description": "Choose whether to deploy a policy assignment." - } - }, - "emailSecurityContact": { - "type": "string", - "metadata": { - "description": "The email address to use for Defender for Cloud notifications." - } - }, - "environmentAbbreviation": { - "type": "string", - "defaultValue": "dev", - "allowedValues": [ - "dev", - "prod", - "test" - ], - "metadata": { - "description": "The abbreviation for the environment." - } - }, - "firewallResourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the Azure Firewall in the HUB." - } - }, - "hubVirtualNetworkResourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the HUB Virtual Network." - } - }, - "identifier": { - "type": "string", - "maxLength": 3, - "metadata": { - "description": "The identifier for the resource names. This value should represent the workload, project, or business unit." - } - }, - "keyVaultDiagnosticsLogs": { - "type": "array", - "defaultValue": [ - { - "category": "AuditEvent", - "enabled": true - }, - { - "category": "AzurePolicyEvaluationDetails", - "enabled": true - } - ], - "metadata": { - "description": "An array of Key Vault Diagnostic Logs categories to collect. See \"https://learn.microsoft.com/en-us/azure/key-vault/general/logging?tabs=Vault\" for valid values." - } - }, - "location": { - "type": "string", - "defaultValue": "[deployment().location]", - "metadata": { - "description": "The location for the deployment. It defaults to the location of the deployment." - } - }, - "logAnalyticsWorkspaceResourceId": { - "type": "string", - "metadata": { - "description": "The resource ID of the Log Analytics Workspace to use for log storage." - } - }, - "logStorageSkuName": { - "type": "string", - "defaultValue": "Standard_GRS", - "metadata": { - "description": "The Storage Account SKU to use for log storage. It defaults to \"Standard_GRS\". See https://docs.microsoft.com/en-us/rest/api/storagerp/srp_sku_types for valid settings." - } - }, - "networkSecurityGroupDiagnosticsLogs": { - "type": "array", - "defaultValue": [ - { - "category": "NetworkSecurityGroupEvent", - "enabled": true - }, - { - "category": "NetworkSecurityGroupRuleCounter", - "enabled": true - } - ], - "metadata": { - "description": "An array of Network Security Group diagnostic logs to apply to the workload Virtual Network. See https://docs.microsoft.com/en-us/azure/virtual-network/virtual-network-nsg-manage-log#log-categories for valid settings." - } - }, - "networkSecurityGroupDiagnosticsMetrics": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "The metrics to monitor for the Network Security Group." - } - }, - "networkSecurityGroupRules": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "The rules to apply to the Network Security Group." - } - }, - "policy": { - "type": "string", - "defaultValue": "NISTRev4", - "metadata": { - "description": "The policy to assign to the workload." - } - }, - "stampIndex": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The stamp index allows for multiple AVD stamps with the same business unit or project to support different use cases." - } - }, - "subnetAddressPrefix": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The address prefix for the workload subnet." - } - }, - "subnetName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The custom name for the workload subnet if the naming convention is not desired. Subnets are child resources and do not require a unique name between virtual networks, only within the same virtual network." - } - }, - "tags": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "The tags to apply to the resources." + "defaultValue": {}, + "metadata": { + "description": "The tags to apply to the resources." } }, "virtualNetworkAddressPrefix": { @@ -2198,8 +1592,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "11424932506966599764" + "version": "0.31.92.45157", + "templateHash": "15810721730485220824" } }, "parameters": { @@ -2278,8 +1672,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7616623366375139473" + "version": "0.31.92.45157", + "templateHash": "11125044402666498605" } }, "parameters": { @@ -2305,6 +1699,7 @@ "tokens": { "type": "object", "defaultValue": { + "purpose": "purpose_token", "resource": "resource_token", "service": "service_token" } @@ -2730,6 +2125,7 @@ "remoteApplicationGroups": "vdag", "resourceGroups": "rg", "routeTables": "rt", + "scalingPlans": "vdscaling", "storageAccounts": "st", "subnets": "snet", "userAssignedIdentities": "id", @@ -2740,11 +2136,11 @@ "locations": "[variables('$fxv#0')[environment().name]]", "locationAbbreviation": "[variables('locations')[parameters('location')].abbreviation]", "resourceAbbreviations": "[variables('$fxv#1')]", - "namingConvention": "[format('{0}-{1}{2}-{3}-{4}-{5}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), parameters('environmentAbbreviation'), variables('locationAbbreviation'))]", - "namingConvention_Service": "[format('{0}-{1}{2}-{3}-{4}-{5}-{6}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('tokens').service, parameters('networkName'), parameters('environmentAbbreviation'), variables('locationAbbreviation'))]", + "namingConvention": "[format('{0}-{1}{2}-{3}-{4}-{5}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), variables('locationAbbreviation'), parameters('environmentAbbreviation'))]", + "namingConvention_Service": "[format('{0}-{1}{2}-{3}-{4}-{5}-{6}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), parameters('tokens').service, variables('locationAbbreviation'), parameters('environmentAbbreviation'))]", "names": { "actionGroup": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').actionGroups)]", - "applicationGroup": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').applicationGroups)]", + "applicationGroup": "[replace(variables('namingConvention'), parameters('tokens').resource, format('{0}-desktop', variables('resourceAbbreviations').applicationGroups))]", "applicationInsights": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').applicationInsights)]", "appServicePlan": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').appServicePlans)]", "automationAccount": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').automationAccounts)]", @@ -2798,10 +2194,18 @@ "recoveryServicesVaultPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, variables('resourceAbbreviations').recoveryServicesVaults)]", "resourceGroup": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').resourceGroups)]", "routeTable": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').routeTables)]", + "scalingPlan": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').scalingPlans)]", + "scalingPlanDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').scalingPlans)]", "storageAccount": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').storageAccounts), parameters('networkName'), parameters('networkShortName'))]", "storageAccountDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", - "storageAccountNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", - "storageAccountPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", + "storageAccountBlobNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-blob', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountFileNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-file', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountQueueNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-queue', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountTableNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-table', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountBlobPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-blob', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountFilePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-file', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountQueuePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-queue', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountTablePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-table', variables('resourceAbbreviations').storageAccounts))]", "subnet": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').subnets)]", "userAssignedIdentity": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').userAssignedIdentities)]", "virtualMachine": "[replace(replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').virtualMachines), parameters('environmentAbbreviation'), first(parameters('environmentAbbreviation'))), parameters('networkName'), ''), '-', '')]", @@ -2809,14 +2213,14 @@ "virtualMachineNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').virtualMachines))]", "virtualNetwork": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').virtualNetworks)]", "virtualNetworkDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').virtualNetworks)]", - "workspaceFeed": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').workspaces), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobal": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').workspaces), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]" + "workspaceFeed": "[replace(replace(variables('namingConvention'), parameters('tokens').resource, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceFeedDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceFeedNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceFeedPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobal": "[replace(replace(variables('namingConvention'), parameters('tokens').resource, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobalDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobalNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobalPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]" } }, "resources": [], @@ -2862,8 +2266,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "4151572700986885014" + "version": "0.31.92.45157", + "templateHash": "17619606927129129347" } }, "parameters": { @@ -3002,8 +2406,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10109660736443081073" + "version": "0.31.92.45157", + "templateHash": "9371138343009436350" } }, "parameters": { @@ -3131,8 +2535,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2001402118470594055" + "version": "0.31.92.45157", + "templateHash": "12095518102496352105" } }, "parameters": { @@ -3267,8 +2671,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3535186238457701125" + "version": "0.31.92.45157", + "templateHash": "11818136489056939588" } }, "parameters": { @@ -3385,8 +2789,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "6722359274420487391" + "version": "0.31.92.45157", + "templateHash": "3681943409502537301" } }, "parameters": { @@ -3468,8 +2872,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "12652296496577802490" + "version": "0.31.92.45157", + "templateHash": "18425321023142226965" } }, "parameters": { @@ -3571,8 +2975,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17592952825859536181" + "version": "0.31.92.45157", + "templateHash": "15002544166394392504" } }, "parameters": { @@ -3648,8 +3052,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7007835755326231171" + "version": "0.31.92.45157", + "templateHash": "16817025486402215719" } }, "parameters": { @@ -3789,8 +3193,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3832300165614083813" + "version": "0.31.92.45157", + "templateHash": "10267893616110384815" } }, "parameters": { @@ -3842,8 +3246,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2015499370398293300" + "version": "0.31.92.45157", + "templateHash": "5574526676512163672" } }, "parameters": { @@ -3916,8 +3320,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "9502319494004310539" + "version": "0.31.92.45157", + "templateHash": "11553909803736438916" } }, "parameters": { @@ -3969,8 +3373,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2015499370398293300" + "version": "0.31.92.45157", + "templateHash": "5574526676512163672" } }, "parameters": { @@ -4069,8 +3473,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10999644342250296299" + "version": "0.31.92.45157", + "templateHash": "4304275711041823961" } }, "parameters": { @@ -4127,8 +3531,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2757774880390840506" + "version": "0.31.92.45157", + "templateHash": "16077950968688123011" } }, "parameters": { @@ -4230,8 +3634,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "11152278840466293351" + "version": "0.31.92.45157", + "templateHash": "12787329163785242553" } }, "parameters": { @@ -4319,8 +3723,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "1874197755006172394" + "version": "0.31.92.45157", + "templateHash": "11761568940379970751" } }, "parameters": { @@ -4577,8 +3981,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17613382135787640077" + "version": "0.31.92.45157", + "templateHash": "4207798980384159491" } }, "parameters": { @@ -4657,8 +4061,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8372265102618018066" + "version": "0.31.92.45157", + "templateHash": "1966035938992047983" } }, "parameters": { @@ -4752,8 +4156,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "6675708379514380442" + "version": "0.31.92.45157", + "templateHash": "7930493629995578222" } }, "parameters": { @@ -4858,6 +4262,9 @@ "blobsPrivateDnsZoneResourceId": { "value": "[resourceId(variables('hubSubscriptionId'), variables('hubResourceGroupName'), 'Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" }, + "filesPrivateDnsZoneResourceId": { + "value": "[resourceId(variables('hubSubscriptionId'), variables('hubResourceGroupName'), 'Microsoft.Network/privateDnsZones', format('privatelink.file.{0}', environment().suffixes.storage))]" + }, "keyVaultUri": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-cmk-{0}-{1}', parameters('workloadShortName'), parameters('deploymentNameSuffix'))), '2022-09-01').outputs.keyVaultUri.value]" }, @@ -4873,6 +4280,9 @@ "network": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-logic-{0}-{1}', parameters('workloadShortName'), parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tiers.value[0]]" }, + "queuesPrivateDnsZoneResourceId": { + "value": "[resourceId(variables('hubSubscriptionId'), variables('hubResourceGroupName'), 'Microsoft.Network/privateDnsZones', format('privatelink.queue.{0}', environment().suffixes.storage))]" + }, "resourceGroupName": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', parameters('workloadShortName'), parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" }, @@ -4904,14 +4314,17 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17995428208905585384" + "version": "0.31.92.45157", + "templateHash": "17358288344184718166" } }, "parameters": { "blobsPrivateDnsZoneResourceId": { "type": "string" }, + "filesPrivateDnsZoneResourceId": { + "type": "string" + }, "keyVaultUri": { "type": "string" }, @@ -4927,6 +4340,9 @@ "network": { "type": "object" }, + "queuesPrivateDnsZoneResourceId": { + "type": "string" + }, "resourceGroupName": { "type": "string" }, @@ -4968,6 +4384,9 @@ "blobsPrivateDnsZoneResourceId": { "value": "[parameters('blobsPrivateDnsZoneResourceId')]" }, + "filesPrivateDnsZoneResourceId": { + "value": "[parameters('filesPrivateDnsZoneResourceId')]" + }, "keyVaultUri": { "value": "[parameters('keyVaultUri')]" }, @@ -4977,6 +4396,9 @@ "mlzTags": { "value": "[parameters('mlzTags')]" }, + "queuesPrivateDnsZoneResourceId": { + "value": "[parameters('queuesPrivateDnsZoneResourceId')]" + }, "serviceToken": { "value": "[parameters('serviceToken')]" }, @@ -5008,14 +4430,17 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10231061264498799420" + "version": "0.31.92.45157", + "templateHash": "1625826941635729014" } }, "parameters": { "blobsPrivateDnsZoneResourceId": { "type": "string" }, + "filesPrivateDnsZoneResourceId": { + "type": "string" + }, "keyVaultUri": { "type": "string" }, @@ -5025,6 +4450,9 @@ "mlzTags": { "type": "object" }, + "queuesPrivateDnsZoneResourceId": { + "type": "string" + }, "serviceToken": { "type": "string" }, @@ -5051,9 +4479,27 @@ } }, "variables": { - "zones": [ - "[parameters('blobsPrivateDnsZoneResourceId')]", - "[parameters('tablesPrivateDnsZoneResourceId')]" + "subResources": [ + { + "id": "[parameters('blobsPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountBlobNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountBlobPrivateEndpoint]" + }, + { + "id": "[parameters('filesPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountFileNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountFilePrivateEndpoint]" + }, + { + "id": "[parameters('queuesPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountQueueNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountQueuePrivateEndpoint]" + }, + { + "id": "[parameters('tablesPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountTableNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountTablePrivateEndpoint]" + } ] }, "resources": [ @@ -5124,22 +4570,22 @@ { "copy": { "name": "privateEndpoints", - "count": "[length(variables('zones'))]" + "count": "[length(variables('subResources'))]" }, "type": "Microsoft.Network/privateEndpoints", "apiVersion": "2023-04-01", - "name": "[replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]))]", + "name": "[variables('subResources')[copyIndex()].pe]", "location": "[parameters('location')]", "tags": "[union(if(contains(parameters('tags'), 'Microsoft.Network/privateEndpoints'), parameters('tags')['Microsoft.Network/privateEndpoints'], createObject()), parameters('mlzTags'))]", "properties": { - "customNetworkInterfaceName": "[replace(parameters('tier').namingConvention.storageAccountNetworkInterface, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]))]", + "customNetworkInterfaceName": "[variables('subResources')[copyIndex()].nic]", "privateLinkServiceConnections": [ { - "name": "[replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]))]", + "name": "[variables('subResources')[copyIndex()].pe]", "properties": { "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]", "groupIds": [ - "[split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]]" + "[split(split(variables('subResources')[copyIndex()].id, '/')[8], '.')[1]]" ] } } @@ -5155,23 +4601,23 @@ { "copy": { "name": "privateDnsZoneGroups", - "count": "[length(variables('zones'))]" + "count": "[length(variables('subResources'))]" }, "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", "apiVersion": "2021-08-01", - "name": "[format('{0}/{1}', replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1])), uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]", + "name": "[format('{0}/{1}', variables('subResources')[copyIndex()].pe, uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]", "properties": { "privateDnsZoneConfigs": [ { "name": "ipconfig1", "properties": { - "privateDnsZoneId": "[variables('zones')[copyIndex()]]" + "privateDnsZoneId": "[variables('subResources')[copyIndex()].id]" } } ] }, "dependsOn": [ - "[resourceId('Microsoft.Network/privateEndpoints', replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1])))]", + "[resourceId('Microsoft.Network/privateEndpoints', variables('subResources')[copyIndex()].pe)]", "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]" ] } @@ -5265,8 +4711,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8480968961555779332" + "version": "0.31.92.45157", + "templateHash": "4345251511078445463" } }, "parameters": { @@ -5340,8 +4786,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8389358797157027271" + "version": "0.31.92.45157", + "templateHash": "4687229436121899773" } }, "parameters": { @@ -5430,8 +4876,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3320497816733398371" + "version": "0.31.92.45157", + "templateHash": "1721966359516622278" } }, "parameters": { @@ -5504,8 +4950,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "9851134383266019486" + "version": "0.31.92.45157", + "templateHash": "2073766618455932098" } }, "parameters": { @@ -5582,8 +5028,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7358671180047253284" + "version": "0.31.92.45157", + "templateHash": "9546260853018527046" } }, "parameters": { @@ -5673,8 +5119,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7046499602359448652" + "version": "0.31.92.45157", + "templateHash": "15014526386353172066" } }, "parameters": { @@ -5730,8 +5176,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "16614224552992880134" + "version": "0.31.92.45157", + "templateHash": "8545903679924437739" } }, "parameters": { @@ -5906,8 +5352,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8372265102618018066" + "version": "0.31.92.45157", + "templateHash": "1966035938992047983" } }, "parameters": { @@ -5991,8 +5437,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3588347827815061814" + "version": "0.31.92.45157", + "templateHash": "17047820191891552534" } }, "parameters": { @@ -6287,89 +5733,6 @@ } } }, - { - "copy": { - "name": "rgs", - "count": "[length(variables('resourceGroupServices'))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[copyIndex()], parameters('deploymentNameSuffix'))]", - "location": "[deployment().location]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "location": "[if(equals(variables('resourceGroupServices')[copyIndex()], 'controlPlane'), createObject('value', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Network/virtualNetworks', split(parameters('hubVirtualNetworkResourceId'), '/')[8]), '2023-11-01', 'full').location), createObject('value', parameters('locationVirtualMachines')))]", - "mlzTags": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.mlzTags.value]" - }, - "name": "[if(equals(variables('resourceGroupServices')[copyIndex()], 'controlPlane'), createObject('value', replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.resourceGroup, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, variables('resourceGroupServices')[copyIndex()])), createObject('value', replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.namingConvention.value.resourceGroup, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, variables('resourceGroupServices')[copyIndex()])))]", - "tags": { - "value": "[parameters('tags')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10109660736443081073" - } - }, - "parameters": { - "mlzTags": { - "type": "object" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object", - "defaultValue": {} - } - }, - "resources": [ - { - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2019-05-01", - "name": "[parameters('name')]", - "location": "[parameters('location')]", - "tags": "[union(if(contains(parameters('tags'), 'Microsoft.Resources/resourceGroups'), parameters('tags')['Microsoft.Resources/resourceGroups'], createObject()), parameters('mlzTags'))]" - } - ], - "outputs": { - "id": { - "type": "string", - "value": "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('name'))]" - }, - "name": { - "type": "string", - "value": "[parameters('name')]" - }, - "location": { - "type": "string", - "value": "[reference(subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('name')), '2019-05-01', 'full').location]" - }, - "tags": { - "type": "object", - "value": "[reference(subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('name')), '2019-05-01', 'full').tags]" - } - } - } - }, - "dependsOn": [ - "[subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix')))]", - "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix')))]" - ] - }, { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", @@ -6387,12 +5750,22 @@ "avdObjectId": { "value": "[parameters('avdObjectId')]" }, + "avdPrivateDnsZoneResourceId": { + "value": "[format('{0}{1}', variables('privateDnsZoneResourceIdPrefix'), filter(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.privateDnsZones.value, lambda('name', startsWith(lambdaVariables('name'), 'privatelink.wvd')))[0])]" + }, + "customImageId": { + "value": "[variables('customImageId')]" + }, + "customRdpProperty": { + "value": "[parameters('customRdpProperty')]" + }, "deployFslogix": { "value": "[variables('deployFslogix')]" }, "deploymentNameSuffix": { "value": "[parameters('deploymentNameSuffix')]" }, + "desktopFriendlyName": "[if(empty(parameters('desktopFriendlyName')), createObject('value', string(parameters('stampIndex'))), createObject('value', parameters('desktopFriendlyName')))]", "diskEncryptionSetResourceId": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.diskEncryptionSetResourceId.value]" }, @@ -6420,9 +5793,27 @@ "fslogixStorageService": { "value": "[parameters('fslogixStorageService')]" }, + "hostPoolPublicNetworkAccess": { + "value": "[parameters('hostPoolPublicNetworkAccess')]" + }, "hostPoolType": { "value": "[parameters('hostPoolType')]" }, + "imageOffer": { + "value": "[parameters('imageOffer')]" + }, + "imagePublisher": { + "value": "[parameters('imagePublisher')]" + }, + "imageSku": { + "value": "[parameters('imageSku')]" + }, + "imageVersionResourceId": { + "value": "[parameters('imageVersionResourceId')]" + }, + "locationControlPlane": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Network/virtualNetworks', split(parameters('hubVirtualNetworkResourceId'), '/')[8]), '2023-11-01', 'full').location]" + }, "locationVirtualMachines": { "value": "[parameters('locationVirtualMachines')]" }, @@ -6432,6 +5823,9 @@ "logAnalyticsWorkspaceSku": { "value": "[parameters('logAnalyticsWorkspaceSku')]" }, + "maxSessionLimit": { + "value": "[mul(parameters('usersPerCore'), parameters('virtualMachineVirtualCpuCount'))]" + }, "mlzTags": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.mlzTags.value]" }, @@ -6459,40 +5853,21 @@ "resourceAbbreviations": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceAbbreviations.value]" }, - "resourceGroupControlPlane": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[0], parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" + "resourceGroupName": { + "value": "[replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.resourceGroup, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'management')]" }, - "resourceGroupHosts": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[1], parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" + "resourceGroupProfiles": { + "value": "[replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.namingConvention.value.resourceGroup, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'profiles')]" }, - "resourceGroupManagement": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[2], parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" - }, - "resourceGroupStorage": "[if(variables('deployFslogix'), createObject('value', replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.namingConvention.value.resourceGroup, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'storage')), createObject('value', ''))]", - "roleDefinitions": { - "value": "[variables('roleDefinitions')]" - }, - "scalingBeginPeakTime": { - "value": "[parameters('scalingBeginPeakTime')]" - }, - "scalingEndPeakTime": { - "value": "[parameters('scalingEndPeakTime')]" - }, - "scalingLimitSecondsToForceLogOffUser": { - "value": "[parameters('scalingLimitSecondsToForceLogOffUser')]" - }, - "scalingMinimumNumberOfRdsh": { - "value": "[parameters('scalingMinimumNumberOfRdsh')]" - }, - "scalingSessionThresholdPerCPU": { - "value": "[parameters('scalingSessionThresholdPerCPU')]" - }, - "scalingTool": { - "value": "[parameters('scalingTool')]" + "securityPrincipalObjectIds": { + "value": "[map(parameters('securityPrincipals'), lambda('item', lambdaVariables('item').objectId))]" }, "serviceToken": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service]" }, + "sessionHostNamePrefix": { + "value": "[replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.namingConvention.value.virtualMachine, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, '')]" + }, "storageService": { "value": "[variables('storageService')]" }, @@ -6505,15 +5880,18 @@ "tags": { "value": "[parameters('tags')]" }, - "timeDifference": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.locationProperties.value.timeDifference]" - }, "timeZone": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.locationProperties.value.timeZone]" }, + "validationEnvironment": { + "value": "[parameters('validationEnvironment')]" + }, "virtualMachinePassword": { "value": "[parameters('virtualMachinePassword')]" }, + "virtualMachineSize": { + "value": "[parameters('virtualMachineSize')]" + }, "virtualMachineUsername": { "value": "[parameters('virtualMachineUsername')]" } @@ -6524,8 +5902,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "5604402442599402109" + "version": "0.31.92.45157", + "templateHash": "10495259955667653232" } }, "parameters": { @@ -6535,12 +5913,24 @@ "avdObjectId": { "type": "string" }, + "avdPrivateDnsZoneResourceId": { + "type": "string" + }, + "customImageId": { + "type": "string" + }, + "customRdpProperty": { + "type": "string" + }, "deployFslogix": { "type": "bool" }, "deploymentNameSuffix": { "type": "string" }, + "desktopFriendlyName": { + "type": "string" + }, "diskEncryptionSetResourceId": { "type": "string" }, @@ -6568,9 +5958,27 @@ "fslogixStorageService": { "type": "string" }, + "hostPoolPublicNetworkAccess": { + "type": "string" + }, "hostPoolType": { "type": "string" }, + "imageOffer": { + "type": "string" + }, + "imagePublisher": { + "type": "string" + }, + "imageSku": { + "type": "string" + }, + "imageVersionResourceId": { + "type": "string" + }, + "locationControlPlane": { + "type": "string" + }, "locationVirtualMachines": { "type": "string" }, @@ -6580,6 +5988,9 @@ "logAnalyticsWorkspaceSku": { "type": "string" }, + "maxSessionLimit": { + "type": "int" + }, "mlzTags": { "type": "object" }, @@ -6607,40 +6018,19 @@ "resourceAbbreviations": { "type": "object" }, - "resourceGroupControlPlane": { - "type": "string" - }, - "resourceGroupHosts": { - "type": "string" - }, - "resourceGroupManagement": { - "type": "string" - }, - "resourceGroupStorage": { - "type": "string" - }, - "roleDefinitions": { - "type": "object" - }, - "scalingBeginPeakTime": { + "resourceGroupName": { "type": "string" }, - "scalingEndPeakTime": { + "resourceGroupProfiles": { "type": "string" }, - "scalingLimitSecondsToForceLogOffUser": { - "type": "string" - }, - "scalingMinimumNumberOfRdsh": { - "type": "string" + "securityPrincipalObjectIds": { + "type": "array" }, - "scalingSessionThresholdPerCPU": { + "serviceToken": { "type": "string" }, - "scalingTool": { - "type": "bool" - }, - "serviceToken": { + "sessionHostNamePrefix": { "type": "string" }, "storageService": { @@ -6655,64 +6045,89 @@ "tags": { "type": "object" }, - "timeDifference": { - "type": "string" - }, "timeZone": { "type": "string" }, + "validationEnvironment": { + "type": "bool" + }, "virtualMachinePassword": { "type": "securestring" }, + "virtualMachineSize": { + "type": "string" + }, "virtualMachineUsername": { "type": "string" } }, "variables": { + "galleryImageOffer": "[if(empty(parameters('imageVersionResourceId')), format('\"{0}\"', parameters('imageOffer')), 'null')]", + "galleryImagePublisher": "[if(empty(parameters('imageVersionResourceId')), format('\"{0}\"', parameters('imagePublisher')), 'null')]", + "galleryImageSku": "[if(empty(parameters('imageVersionResourceId')), format('\"{0}\"', parameters('imageSku')), 'null')]", + "galleryItemId": "[if(empty(parameters('imageVersionResourceId')), format('\"{0}.{1}{2}\"', parameters('imagePublisher'), parameters('imageOffer'), parameters('imageSku')), 'null')]", "hostPoolName": "[parameters('namingConvention').hostPool]", - "roleAssignments": "[union(createArray(createObject('roleDefinitionId', '86240b0e-9422-4c43-887b-b61143f32ba8', 'resourceGroup', parameters('resourceGroupControlPlane'), 'subscription', subscription().subscriptionId), createObject('roleDefinitionId', '2ad6aaab-ead9-4eaa-8ac5-da422f562408', 'resourceGroup', parameters('resourceGroupControlPlane'), 'subscription', subscription().subscriptionId), createObject('roleDefinitionId', 'a959dbd1-f747-45e3-8ba6-dd80f235f97c', 'resourceGroup', parameters('resourceGroupManagement'), 'subscription', subscription().subscriptionId)), if(parameters('deployFslogix'), createArray(createObject('roleDefinitionId', '17d1049b-9a84-46fb-8f53-869881c3d3ab', 'resourceGroup', parameters('resourceGroupStorage'), 'subscription', subscription().subscriptionId)), createArray()))]", - "userAssignedIdentityNamePrefix": "[parameters('namingConvention').userAssignedIdentity]", - "virtualNetworkName": "[split(parameters('subnetResourceId'), '/')[8]]", - "virtualNetworkResourceGroupName": "[split(parameters('subnetResourceId'), '/')[4]]" + "imageType": "[if(empty(parameters('imageVersionResourceId')), '\"Gallery\"', '\"CustomImage\"')]", + "userAssignedIdentityNamePrefix": "[parameters('namingConvention').userAssignedIdentity]" }, "resources": [ + { + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2023-07-01", + "name": "[parameters('resourceGroupName')]", + "location": "[parameters('locationControlPlane')]", + "tags": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupName'), variables('hostPoolName'))), coalesce(tryGet(parameters('tags'), 'Microsoft.Resources/resourceGroups'), createObject()), parameters('mlzTags'))]" + }, { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "name": "[guid(parameters('avdObjectId'), parameters('roleDefinitions').DesktopVirtualizationPowerOnContributor, subscription().id)]", + "name": "[guid(parameters('avdObjectId'), '40c5ff49-9181-41f8-ae61-143b0e78555e', subscription().id)]", "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitions').DesktopVirtualizationPowerOnContributor)]", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '40c5ff49-9181-41f8-ae61-143b0e78555e')]", "principalId": "[parameters('avdObjectId')]" } }, { + "condition": "[or(parameters('enableApplicationInsights'), parameters('enableAvdInsights'))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('deploy-disk-access-{0}', parameters('deploymentNameSuffix'))]", - "resourceGroup": "[parameters('resourceGroupManagement')]", + "name": "[format('deploy-monitoring-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[parameters('resourceGroupName')]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "hostPoolName": { - "value": "[variables('hostPoolName')]" + "deploymentNameSuffix": { + "value": "[parameters('deploymentNameSuffix')]" + }, + "enableAvdInsights": { + "value": "[parameters('enableAvdInsights')]" + }, + "hostPoolResourceId": { + "value": "[format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupName'), variables('hostPoolName'))]" }, "location": { "value": "[parameters('locationVirtualMachines')]" }, + "logAnalyticsWorkspaceRetention": { + "value": "[parameters('logAnalyticsWorkspaceRetention')]" + }, + "logAnalyticsWorkspaceSku": { + "value": "[parameters('logAnalyticsWorkspaceSku')]" + }, "mlzTags": { "value": "[parameters('mlzTags')]" }, "namingConvention": { "value": "[parameters('namingConvention')]" }, - "resourceGroupControlPlane": { - "value": "[parameters('resourceGroupControlPlane')]" + "privateLinkScopeResourceId": { + "value": "[parameters('privateLinkScopeResourceId')]" }, - "subnetResourceId": { - "value": "[parameters('subnetResourceId')]" + "serviceToken": { + "value": "[parameters('serviceToken')]" }, "tags": { "value": "[parameters('tags')]" @@ -6724,27 +6139,43 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "15818789439914368510" + "version": "0.31.92.45157", + "templateHash": "14902820815719387395" } }, "parameters": { - "hostPoolName": { + "deploymentNameSuffix": { + "type": "string" + }, + "enableAvdInsights": { + "type": "bool" + }, + "hostPoolResourceId": { "type": "string" }, "location": { "type": "string" }, + "logAnalyticsWorkspaceRetention": { + "type": "int" + }, + "logAnalyticsWorkspaceSku": { + "type": "string" + }, "mlzTags": { "type": "object" }, "namingConvention": { "type": "object" }, - "resourceGroupControlPlane": { + "privateLinkScopeResourceId": { "type": "string" }, - "subnetResourceId": { + "service": { + "type": "string", + "defaultValue": "mgmt" + }, + "serviceToken": { "type": "string" }, "tags": { @@ -6753,150 +6184,227 @@ }, "resources": [ { - "type": "Microsoft.Compute/diskAccesses", - "apiVersion": "2021-04-01", - "name": "[parameters('namingConvention').diskAccess]", + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2021-06-01", + "name": "[replace(parameters('namingConvention').logAnalyticsWorkspace, parameters('serviceToken'), parameters('service'))]", "location": "[parameters('location')]", - "tags": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.Compute/diskAccesses'), parameters('tags')['Microsoft.Compute/diskAccesses'], createObject()), parameters('mlzTags'))]", - "properties": {} + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.OperationalInsights/workspaces'), createObject()), parameters('mlzTags'))]", + "properties": { + "sku": { + "name": "[parameters('logAnalyticsWorkspaceSku')]" + }, + "retentionInDays": "[parameters('logAnalyticsWorkspaceRetention')]", + "workspaceCapping": { + "dailyQuotaGb": -1 + }, + "publicNetworkAccessForIngestion": "Disabled", + "publicNetworkAccessForQuery": "Enabled" + } }, { - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-04-01", - "name": "[parameters('namingConvention').diskAccessPrivateEndpoint]", + "condition": "[parameters('enableAvdInsights')]", + "type": "Microsoft.Insights/dataCollectionRules", + "apiVersion": "2022-06-01", + "name": "[format('microsoft-avdi-{0}', replace(parameters('namingConvention').dataCollectionRule, parameters('serviceToken'), parameters('service')))]", "location": "[parameters('location')]", - "tags": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.Network/privateEndpoints'), parameters('tags')['Microsoft.Network/privateEndpoints'], createObject()), parameters('mlzTags'))]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Insights/dataCollectionRules'), createObject()), parameters('mlzTags'))]", + "kind": "Windows", "properties": { - "customNetworkInterfaceName": "[parameters('namingConvention').diskAccessNetworkInterface]", - "privateLinkServiceConnections": [ - { - "name": "[parameters('namingConvention').diskAccessPrivateEndpoint]", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Compute/diskAccesses', parameters('namingConvention').diskAccess)]", - "groupIds": [ - "disks" - ] + "dataSources": { + "performanceCounters": [ + { + "streams": [ + "Microsoft-Perf" + ], + "samplingFrequencyInSeconds": 30, + "counterSpecifiers": [ + "\\LogicalDisk(C:)\\Avg. Disk Queue Length", + "\\LogicalDisk(C:)\\Current Disk Queue Length", + "\\Memory\\Available Mbytes", + "\\Memory\\Page Faults/sec", + "\\Memory\\Pages/sec", + "\\Memory\\% Committed Bytes In Use", + "\\PhysicalDisk(*)\\Avg. Disk Queue Length", + "\\PhysicalDisk(*)\\Avg. Disk sec/Read", + "\\PhysicalDisk(*)\\Avg. Disk sec/Transfer", + "\\PhysicalDisk(*)\\Avg. Disk sec/Write", + "\\Processor Information(_Total)\\% Processor Time", + "\\User Input Delay per Process(*)\\Max Input Delay", + "\\User Input Delay per Session(*)\\Max Input Delay", + "\\RemoteFX Network(*)\\Current TCP RTT", + "\\RemoteFX Network(*)\\Current UDP Bandwidth" + ], + "name": "perfCounterDataSource10" + }, + { + "streams": [ + "Microsoft-Perf" + ], + "samplingFrequencyInSeconds": 60, + "counterSpecifiers": [ + "\\LogicalDisk(C:)\\% Free Space", + "\\LogicalDisk(C:)\\Avg. Disk sec/Transfer", + "\\Terminal Services(*)\\Active Sessions", + "\\Terminal Services(*)\\Inactive Sessions", + "\\Terminal Services(*)\\Total Sessions" + ], + "name": "perfCounterDataSource30" + } + ], + "windowsEventLogs": [ + { + "streams": [ + "Microsoft-Event" + ], + "xPathQueries": [ + "Microsoft-Windows-TerminalServices-RemoteConnectionManager/Admin!*[System[(Level=2 or Level=3 or Level=4 or Level=0)]]", + "Microsoft-Windows-TerminalServices-LocalSessionManager/Operational!*[System[(Level=2 or Level=3 or Level=4 or Level=0)]]", + "System!*", + "Microsoft-FSLogix-Apps/Operational!*[System[(Level=2 or Level=3 or Level=4 or Level=0)]]", + "Application!*[System[(Level=2 or Level=3)]]", + "Microsoft-FSLogix-Apps/Admin!*[System[(Level=2 or Level=3 or Level=4 or Level=0)]]" + ], + "name": "eventLogsDataSource" + } + ] + }, + "destinations": { + "logAnalytics": [ + { + "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', replace(parameters('namingConvention').logAnalyticsWorkspace, parameters('serviceToken'), parameters('service')))]", + "name": "la-workspace" } + ] + }, + "dataFlows": [ + { + "streams": [ + "Microsoft-Perf", + "Microsoft-Event" + ], + "destinations": [ + "la-workspace" + ] } - ], - "subnet": { - "id": "[parameters('subnetResourceId')]" - } + ] }, "dependsOn": [ - "[resourceId('Microsoft.Compute/diskAccesses', parameters('namingConvention').diskAccess)]" + "[resourceId('Microsoft.OperationalInsights/workspaces', replace(parameters('namingConvention').logAnalyticsWorkspace, parameters('serviceToken'), parameters('service')))]" ] - } - ], - "outputs": { - "resourceId": { - "type": "string", - "value": "[resourceId('Microsoft.Compute/diskAccesses', parameters('namingConvention').diskAccess)]" - } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('deploy-policy-disks-{0}', parameters('deploymentNameSuffix'))]", - "location": "[deployment().location]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "diskAccessResourceId": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-disk-access-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" - }, - "location": { - "value": "[parameters('locationVirtualMachines')]" - }, - "resourceGroupName": { - "value": "[parameters('resourceGroupHosts')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "11917989921453943443" - } - }, - "parameters": { - "diskAccessResourceId": { - "type": "string", - "defaultValue": "" }, - "location": { - "type": "string" + { + "condition": "[parameters('enableAvdInsights')]", + "type": "Microsoft.Insights/dataCollectionEndpoints", + "apiVersion": "2021-04-01", + "name": "[replace(parameters('namingConvention').dataCollectionEndpoint, parameters('serviceToken'), parameters('service'))]", + "location": "[parameters('location')]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Insights/dataCollectionEndpoints'), createObject()), parameters('mlzTags'))]", + "kind": "Windows", + "properties": { + "networkAcls": { + "publicNetworkAccess": "Disabled" + } + } }, - "resourceGroupName": { - "type": "string" - } - }, - "variables": { - "parameters": "[if(not(empty(parameters('diskAccessResourceId'))), createObject('diskAccessId', createObject('type', 'String', 'metadata', createObject('displayName', 'Disk Access Resource Id', 'description', 'The resource Id of the Disk Access to associate to the managed disks.'))), createObject())]", - "operations": "[if(not(empty(parameters('diskAccessResourceId'))), createArray(createObject('operation', 'addOrReplace', 'field', 'Microsoft.Compute/disks/networkAccessPolicy', 'value', 'AllowPrivate'), createObject('operation', 'addOrReplace', 'field', 'Microsoft.Compute/disks/publicNetworkAccess', 'value', 'Disabled'), createObject('operation', 'addOrReplace', 'field', 'Microsoft.Compute/disks/diskAccessId', 'value', '[parameters(''diskAccessId'')]')), createArray(createObject('operation', 'addOrReplace', 'field', 'Microsoft.Compute/disks/networkAccessPolicy', 'value', 'DenyAll'), createObject('operation', 'addOrReplace', 'field', 'Microsoft.Compute/disks/publicNetworkAccess', 'value', 'Disabled')))]" - }, - "resources": [ { - "type": "Microsoft.Authorization/policyDefinitions", - "apiVersion": "2021-06-01", - "name": "DiskNetworkAccess", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('deploy-private-link-scope-law-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('privateLinkScopeResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('privateLinkScopeResourceId'), '/')[4]]", "properties": { - "description": "Disable network access to managed disks.", - "displayName": "Disable Disk Access", - "mode": "All", - "parameters": "[variables('parameters')]", - "policyRule": { - "if": { - "field": "type", - "equals": "Microsoft.Compute/disks" + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "logAnalyticsWorkspaceResourceId": { + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', replace(parameters('namingConvention').logAnalyticsWorkspace, parameters('serviceToken'), parameters('service')))]" + }, + "privateLinkScopeResourceId": { + "value": "[parameters('privateLinkScopeResourceId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.92.45157", + "templateHash": "6666075809401495349" + } + }, + "parameters": { + "applicationInsightsResourceId": { + "type": "string", + "defaultValue": "" + }, + "dataCollectionEndpointResourceId": { + "type": "string", + "defaultValue": "" + }, + "logAnalyticsWorkspaceResourceId": { + "type": "string", + "defaultValue": "" + }, + "privateLinkScopeResourceId": { + "type": "string" + } }, - "then": { - "effect": "modify", - "details": { - "roleDefinitionIds": [ - "/providers/Microsoft.Authorization/roleDefinitions/60fc6e62-5479-42d4-8bf4-67625fcc2840" - ], - "operations": "[variables('operations')]" + "resources": [ + { + "condition": "[not(empty(parameters('applicationInsightsResourceId')))]", + "type": "Microsoft.Insights/privateLinkScopes/scopedResources", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('applicationInsightsResourceId')), 'applicationInsights', split(parameters('applicationInsightsResourceId'), '/')[8]))]", + "properties": { + "linkedResourceId": "[parameters('applicationInsightsResourceId')]" + } + }, + { + "condition": "[not(empty(parameters('dataCollectionEndpointResourceId')))]", + "type": "Microsoft.Insights/privateLinkScopes/scopedResources", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('dataCollectionEndpointResourceId')), 'dataCollectionEndpoint', split(parameters('dataCollectionEndpointResourceId'), '/')[8]))]", + "properties": { + "linkedResourceId": "[parameters('dataCollectionEndpointResourceId')]" + } + }, + { + "condition": "[not(empty(parameters('logAnalyticsWorkspaceResourceId')))]", + "type": "Microsoft.Insights/privateLinkScopes/scopedResources", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('logAnalyticsWorkspaceResourceId')), 'logAnalyticsWorkspace', split(parameters('logAnalyticsWorkspaceResourceId'), '/')[8]))]", + "properties": { + "linkedResourceId": "[parameters('logAnalyticsWorkspaceResourceId')]" + } } - } - }, - "policyType": "Custom" - } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.OperationalInsights/workspaces', replace(parameters('namingConvention').logAnalyticsWorkspace, parameters('serviceToken'), parameters('service')))]" + ] }, { + "condition": "[parameters('enableAvdInsights')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "assign-policy-disk-network-access", - "resourceGroup": "[parameters('resourceGroupName')]", + "name": "[format('deploy-private-link-scope-dce-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('privateLinkScopeResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('privateLinkScopeResourceId'), '/')[4]]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "diskAccessResourceId": { - "value": "[parameters('diskAccessResourceId')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "policyDefinitionId": { - "value": "[subscriptionResourceId('Microsoft.Authorization/policyDefinitions', 'DiskNetworkAccess')]" - }, - "policyDisplayName": { - "value": "[reference(subscriptionResourceId('Microsoft.Authorization/policyDefinitions', 'DiskNetworkAccess'), '2021-06-01').displayName]" + "dataCollectionEndpointResourceId": { + "value": "[resourceId('Microsoft.Insights/dataCollectionEndpoints', replace(parameters('namingConvention').dataCollectionEndpoint, parameters('serviceToken'), parameters('service')))]" }, - "policyName": { - "value": "[reference(subscriptionResourceId('Microsoft.Authorization/policyDefinitions', 'DiskNetworkAccess'), '2021-06-01').displayName]" + "privateLinkScopeResourceId": { + "value": "[parameters('privateLinkScopeResourceId')]" } }, "template": { @@ -6905,259 +6413,174 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17841511129945171695" + "version": "0.31.92.45157", + "templateHash": "6666075809401495349" } }, "parameters": { - "diskAccessResourceId": { - "type": "string" - }, - "location": { - "type": "string" + "applicationInsightsResourceId": { + "type": "string", + "defaultValue": "" }, - "policyDefinitionId": { - "type": "string" + "dataCollectionEndpointResourceId": { + "type": "string", + "defaultValue": "" }, - "policyDisplayName": { - "type": "string" + "logAnalyticsWorkspaceResourceId": { + "type": "string", + "defaultValue": "" }, - "policyName": { + "privateLinkScopeResourceId": { "type": "string" } }, "resources": [ { - "type": "Microsoft.Authorization/policyAssignments", - "apiVersion": "2022-06-01", - "name": "[parameters('policyName')]", - "location": "[parameters('location')]", - "identity": { - "type": "SystemAssigned" - }, + "condition": "[not(empty(parameters('applicationInsightsResourceId')))]", + "type": "Microsoft.Insights/privateLinkScopes/scopedResources", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('applicationInsightsResourceId')), 'applicationInsights', split(parameters('applicationInsightsResourceId'), '/')[8]))]", "properties": { - "displayName": "[parameters('policyDisplayName')]", - "policyDefinitionId": "[parameters('policyDefinitionId')]", - "parameters": "[if(not(empty(parameters('diskAccessResourceId'))), createObject('diskAccessId', createObject('value', parameters('diskAccessResourceId'))), createObject())]" + "linkedResourceId": "[parameters('applicationInsightsResourceId')]" + } + }, + { + "condition": "[not(empty(parameters('dataCollectionEndpointResourceId')))]", + "type": "Microsoft.Insights/privateLinkScopes/scopedResources", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('dataCollectionEndpointResourceId')), 'dataCollectionEndpoint', split(parameters('dataCollectionEndpointResourceId'), '/')[8]))]", + "properties": { + "linkedResourceId": "[parameters('dataCollectionEndpointResourceId')]" + } + }, + { + "condition": "[not(empty(parameters('logAnalyticsWorkspaceResourceId')))]", + "type": "Microsoft.Insights/privateLinkScopes/scopedResources", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('logAnalyticsWorkspaceResourceId')), 'logAnalyticsWorkspace', split(parameters('logAnalyticsWorkspaceResourceId'), '/')[8]))]", + "properties": { + "linkedResourceId": "[parameters('logAnalyticsWorkspaceResourceId')]" } } ] } }, "dependsOn": [ - "[subscriptionResourceId('Microsoft.Authorization/policyDefinitions', 'DiskNetworkAccess')]" + "[resourceId('Microsoft.Insights/dataCollectionEndpoints', replace(parameters('namingConvention').dataCollectionEndpoint, parameters('serviceToken'), parameters('service')))]" ] } - ] - } - }, - "dependsOn": [ - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-disk-access-{0}', parameters('deploymentNameSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix'))]", - "resourceGroup": "[parameters('resourceGroupManagement')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "location": { - "value": "[parameters('locationVirtualMachines')]" - }, - "name": { - "value": "[replace(variables('userAssignedIdentityNamePrefix'), parameters('serviceToken'), 'deployment')]" - }, - "tags": { - "value": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), variables('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.ManagedIdentity/userAssignedIdentities'), parameters('tags')['Microsoft.ManagedIdentity/userAssignedIdentities'], createObject()), parameters('mlzTags'))]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "11961291129443849893" - } - }, - "parameters": { - "location": { - "type": "string" - }, - "name": { - "type": "string" - }, - "tags": { - "type": "object" - } - }, - "resources": [ - { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2018-11-30", - "name": "[parameters('name')]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]" - } ], - "outputs": { - "clientId": { - "type": "string", - "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2018-11-30').clientId]" - }, - "resourceId": { - "type": "string", - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name'))]" - }, - "principalId": { - "type": "string", - "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2018-11-30').principalId]" - } - } - } - } - }, - { - "copy": { - "name": "roleAssignments_deployment", - "count": "[length(range(0, length(variables('roleAssignments'))))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('deploy-role-assignment-{0}-{1}', range(0, length(variables('roleAssignments')))[copyIndex()], parameters('deploymentNameSuffix'))]", - "subscriptionId": "[variables('roleAssignments')[range(0, length(variables('roleAssignments')))[copyIndex()]].subscription]", - "resourceGroup": "[variables('roleAssignments')[range(0, length(variables('roleAssignments')))[copyIndex()]].resourceGroup]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "principalId": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.principalId.value]" - }, - "principalType": { - "value": "ServicePrincipal" - }, - "roleDefinitionId": { - "value": "[variables('roleAssignments')[range(0, length(variables('roleAssignments')))[copyIndex()]].roleDefinitionId]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "15359000832124286075" - } - }, - "parameters": { - "principalId": { - "type": "string" - }, - "principalType": { - "type": "string" - }, - "roleDefinitionId": { - "type": "string" - } - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(parameters('principalId'), parameters('roleDefinitionId'), resourceGroup().id)]", - "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionId'))]", - "principalId": "[parameters('principalId')]", - "principalType": "[parameters('principalType')]" - } + "outputs": { + "logAnalyticsWorkspaceName": { + "type": "string", + "value": "[replace(parameters('namingConvention').logAnalyticsWorkspace, parameters('serviceToken'), parameters('service'))]" + }, + "logAnalyticsWorkspaceResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', replace(parameters('namingConvention').logAnalyticsWorkspace, parameters('serviceToken'), parameters('service')))]" + }, + "dataCollectionRuleResourceId": { + "type": "string", + "value": "[if(parameters('enableAvdInsights'), resourceId('Microsoft.Insights/dataCollectionRules', format('microsoft-avdi-{0}', replace(parameters('namingConvention').dataCollectionRule, parameters('serviceToken'), parameters('service')))), '')]" } - ] + } } }, "dependsOn": [ - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix')))]" + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" ] }, { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('deploy-mgmt-vm-{0}', parameters('deploymentNameSuffix'))]", - "resourceGroup": "[parameters('resourceGroupManagement')]", + "name": "[format('deploy-vdpool-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[parameters('resourceGroupName')]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "deploymentUserAssignedIdentityResourceId": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" + "activeDirectorySolution": { + "value": "[parameters('activeDirectorySolution')]" }, - "diskEncryptionSetResourceId": { - "value": "[parameters('diskEncryptionSetResourceId')]" + "avdPrivateDnsZoneResourceId": { + "value": "[parameters('avdPrivateDnsZoneResourceId')]" }, - "diskName": { - "value": "[replace(parameters('namingConvention').virtualMachineDisk, parameters('serviceToken'), 'mgt')]" + "customImageId": { + "value": "[parameters('customImageId')]" + }, + "customRdpProperty": { + "value": "[parameters('customRdpProperty')]" }, "diskSku": { "value": "[parameters('diskSku')]" }, - "domainJoinPassword": { - "value": "[parameters('domainJoinPassword')]" - }, - "domainJoinUserPrincipalName": { - "value": "[parameters('domainJoinUserPrincipalName')]" - }, "domainName": { "value": "[parameters('domainName')]" }, + "enableAvdInsights": { + "value": "[parameters('enableAvdInsights')]" + }, + "galleryImageOffer": { + "value": "[variables('galleryImageOffer')]" + }, + "galleryImagePublisher": { + "value": "[variables('galleryImagePublisher')]" + }, + "galleryImageSku": { + "value": "[variables('galleryImageSku')]" + }, + "galleryItemId": { + "value": "[variables('galleryItemId')]" + }, + "hostPoolDiagnosticSettingName": { + "value": "[parameters('namingConvention').hostPoolDiagnosticSetting]" + }, "hostPoolName": { "value": "[variables('hostPoolName')]" }, - "location": { - "value": "[parameters('locationVirtualMachines')]" + "hostPoolNetworkInterfaceName": { + "value": "[parameters('namingConvention').hostPoolNetworkInterface]" }, - "mlzTags": { - "value": "[parameters('mlzTags')]" + "hostPoolPrivateEndpointName": { + "value": "[parameters('namingConvention').hostPoolPrivateEndpoint]" }, - "networkInterfaceName": { - "value": "[replace(parameters('namingConvention').virtualMachineNetworkInterface, parameters('serviceToken'), 'mgt')]" + "hostPoolPublicNetworkAccess": { + "value": "[parameters('hostPoolPublicNetworkAccess')]" }, - "organizationalUnitPath": { - "value": "[parameters('organizationalUnitPath')]" + "hostPoolType": { + "value": "[parameters('hostPoolType')]" }, - "resourceGroupControlPlane": { - "value": "[parameters('resourceGroupControlPlane')]" + "imageType": { + "value": "[variables('imageType')]" }, - "subnet": { - "value": "[split(parameters('subnetResourceId'), '/')[10]]" + "location": { + "value": "[parameters('locationControlPlane')]" }, - "tags": { - "value": "[parameters('tags')]" + "logAnalyticsWorkspaceResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-monitoring-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.logAnalyticsWorkspaceResourceId.value]" }, - "virtualMachineName": { - "value": "[replace(parameters('namingConvention').virtualMachine, parameters('serviceToken'), 'mgt')]" + "maxSessionLimit": { + "value": "[parameters('maxSessionLimit')]" }, - "virtualMachinePassword": { - "value": "[parameters('virtualMachinePassword')]" + "mlzTags": { + "value": "[parameters('mlzTags')]" }, - "virtualMachineUsername": { - "value": "[parameters('virtualMachineUsername')]" + "sessionHostNamePrefix": { + "value": "[parameters('sessionHostNamePrefix')]" + }, + "subnetResourceId": { + "value": "[parameters('subnetResourceId')]" + }, + "tags": { + "value": "[parameters('tags')]" }, - "virtualNetwork": { - "value": "[variables('virtualNetworkName')]" + "validationEnvironment": { + "value": "[parameters('validationEnvironment')]" }, - "virtualNetworkResourceGroup": { - "value": "[variables('virtualNetworkResourceGroupName')]" + "virtualMachineSize": { + "value": "[parameters('virtualMachineSize')]" } }, "template": { @@ -7166,327 +6589,231 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "12440906326725869878" + "version": "0.31.92.45157", + "templateHash": "17657921580737492350" } }, "parameters": { - "deploymentUserAssignedIdentityResourceId": { + "activeDirectorySolution": { "type": "string" }, - "diskEncryptionSetResourceId": { + "avdPrivateDnsZoneResourceId": { "type": "string" }, - "diskName": { + "customImageId": { + "type": "string" + }, + "customRdpProperty": { "type": "string" }, "diskSku": { "type": "string" }, - "domainJoinPassword": { - "type": "securestring" + "domainName": { + "type": "string" }, - "domainJoinUserPrincipalName": { + "enableAvdInsights": { + "type": "bool" + }, + "galleryImageOffer": { "type": "string" }, - "domainName": { + "galleryImagePublisher": { "type": "string" }, - "hostPoolName": { + "galleryImageSku": { "type": "string" }, - "location": { + "galleryItemId": { "type": "string" }, - "mlzTags": { - "type": "object" + "hostPoolDiagnosticSettingName": { + "type": "string" }, - "networkInterfaceName": { + "hostPoolName": { "type": "string" }, - "organizationalUnitPath": { + "hostPoolNetworkInterfaceName": { "type": "string" }, - "resourceGroupControlPlane": { + "hostPoolPrivateEndpointName": { "type": "string" }, - "subnet": { + "hostPoolPublicNetworkAccess": { "type": "string" }, - "tags": { - "type": "object" + "hostPoolType": { + "type": "string" }, - "timestamp": { - "type": "string", - "defaultValue": "[utcNow('yyyyMMddhhmmss')]" + "imageType": { + "type": "string" }, - "virtualNetwork": { + "location": { "type": "string" }, - "virtualNetworkResourceGroup": { + "logAnalyticsWorkspaceResourceId": { "type": "string" }, - "virtualMachineName": { + "maxSessionLimit": { + "type": "int" + }, + "mlzTags": { + "type": "object" + }, + "sessionHostNamePrefix": { "type": "string" }, - "virtualMachinePassword": { - "type": "securestring" + "subnetResourceId": { + "type": "string" }, - "virtualMachineUsername": { + "tags": { + "type": "object" + }, + "time": { + "type": "string", + "defaultValue": "[utcNow('u')]" + }, + "validationEnvironment": { + "type": "bool" + }, + "virtualMachineSize": { "type": "string" } }, "variables": { - "tagsVirtualMachines": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.Compute/virtualMachines'), parameters('tags')['Microsoft.Compute/virtualMachines'], createObject()), parameters('mlzTags'))]" + "customRdpProperty_Complete": "[if(contains(parameters('activeDirectorySolution'), 'MicrosoftEntraId'), format('{0}enablerdsaadauth:i:1;', parameters('customRdpProperty')), parameters('customRdpProperty'))]" }, "resources": [ { - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2020-05-01", - "name": "[parameters('networkInterfaceName')]", - "location": "[parameters('location')]", - "tags": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.Network/networkInterfaces'), parameters('tags')['Microsoft.Network/networkInterfaces'], createObject()), parameters('mlzTags'))]", - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig", - "properties": { - "privateIPAllocationMethod": "Dynamic", - "subnet": { - "id": "[resourceId(parameters('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetwork'), parameters('subnet'))]" - }, - "primary": true, - "privateIPAddressVersion": "IPv4" - } - } - ], - "enableAcceleratedNetworking": false, - "enableIPForwarding": false - } - }, - { - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2021-11-01", - "name": "[parameters('virtualMachineName')]", - "location": "[parameters('location')]", - "tags": "[variables('tagsVirtualMachines')]", - "properties": { - "hardwareProfile": { - "vmSize": "Standard_B2s" - }, - "storageProfile": { - "imageReference": { - "publisher": "MicrosoftWindowsServer", - "offer": "WindowsServer", - "sku": "2019-datacenter-core-g2", - "version": "latest" - }, - "osDisk": { - "deleteOption": "Delete", - "osType": "Windows", - "createOption": "FromImage", - "caching": "None", - "managedDisk": { - "diskEncryptionSet": { - "id": "[parameters('diskEncryptionSetResourceId')]" - }, - "storageAccountType": "[parameters('diskSku')]" - }, - "name": "[parameters('diskName')]" - }, - "dataDisks": [] - }, - "osProfile": { - "computerName": "[parameters('virtualMachineName')]", - "adminUsername": "[parameters('virtualMachineUsername')]", - "adminPassword": "[parameters('virtualMachinePassword')]", - "windowsConfiguration": { - "provisionVMAgent": true, - "enableAutomaticUpdates": false - }, - "secrets": [], - "allowExtensionOperations": true - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaceName'))]", - "properties": { - "deleteOption": "Delete" - } - } - ] - }, - "securityProfile": { - "uefiSettings": { - "secureBootEnabled": true, - "vTpmEnabled": true - }, - "securityType": "TrustedLaunch", - "encryptionAtHost": true - }, - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": false - } + "type": "Microsoft.DesktopVirtualization/hostPools", + "apiVersion": "2023-09-05", + "name": "[parameters('hostPoolName')]", + "location": "[parameters('location')]", + "tags": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, resourceGroup().name, parameters('hostPoolName'))), coalesce(tryGet(parameters('tags'), 'Microsoft.DesktopVirtualization/hostPools'), createObject()), parameters('mlzTags'))]", + "properties": { + "customRdpProperty": "[variables('customRdpProperty_Complete')]", + "hostPoolType": "[parameters('hostPoolType')]", + "loadBalancerType": "[if(equals(parameters('hostPoolType'), 'Pooled'), 'DepthFirst', 'Persistent')]", + "maxSessionLimit": "[parameters('maxSessionLimit')]", + "personalDesktopAssignmentType": "[if(equals(parameters('hostPoolType'), 'Personal'), 'Automatic', null())]", + "preferredAppGroupType": "Desktop", + "publicNetworkAccess": "[parameters('hostPoolPublicNetworkAccess')]", + "registrationInfo": { + "expirationTime": "[dateTimeAdd(parameters('time'), 'PT2H')]", + "registrationTokenOperation": "Update" }, - "licenseType": "Windows_Server" - }, - "identity": { - "type": "SystemAssigned, UserAssigned", - "userAssignedIdentities": { - "[format('{0}', parameters('deploymentUserAssignedIdentityResourceId'))]": {} - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaceName'))]" - ] + "startVMOnConnect": true, + "validationEnvironment": "[parameters('validationEnvironment')]", + "vmTemplate": "[format('{{\"domain\":\"{0}\",\"galleryImageOffer\":{1},\"galleryImagePublisher\":{2},\"galleryImageSKU\":{3},\"imageType\":{4},\"customImageId\":{5},\"namePrefix\":\"{6}\",\"osDiskType\":\"{7}\",\"vmSize\":{{\"id\":\"{8}\",\"cores\":null,\"ram\":null,\"rdmaEnabled\": false,\"supportsMemoryPreservingMaintenance\": true}},\"galleryItemId\":{9},\"hibernate\":false,\"diskSizeGB\":0,\"securityType\":\"TrustedLaunch\",\"secureBoot\":true,\"vTPM\":true,\"vmInfrastructureType\":\"Cloud\",\"virtualProcessorCount\":null,\"memoryGB\":null,\"maximumMemoryGB\":null,\"minimumMemoryGB\":null,\"dynamicMemoryConfig\":false}}', parameters('domainName'), parameters('galleryImageOffer'), parameters('galleryImagePublisher'), parameters('galleryImageSku'), parameters('imageType'), parameters('customImageId'), parameters('sessionHostNamePrefix'), parameters('diskSku'), parameters('virtualMachineSize'), parameters('galleryItemId'))]" + } }, { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2021-03-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), 'IaaSAntimalware')]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-04-01", + "name": "[parameters('hostPoolPrivateEndpointName')]", "location": "[parameters('location')]", - "tags": "[variables('tagsVirtualMachines')]", + "tags": "[union(createObject('cm-resource-parent', resourceId('Microsoft.DesktopVirtualization/hostPools', parameters('hostPoolName'))), coalesce(tryGet(parameters('tags'), 'Microsoft.Network/privateEndpoints'), createObject()), parameters('mlzTags'))]", "properties": { - "publisher": "Microsoft.Azure.Security", - "type": "IaaSAntimalware", - "typeHandlerVersion": "1.3", - "autoUpgradeMinorVersion": true, - "enableAutomaticUpgrade": false, - "settings": { - "AntimalwareEnabled": true, - "RealtimeProtectionEnabled": "true", - "ScheduledScanSettings": { - "isEnabled": "true", - "day": "7", - "time": "120", - "scanType": "Quick" - }, - "Exclusions": {} + "customNetworkInterfaceName": "[parameters('hostPoolNetworkInterfaceName')]", + "privateLinkServiceConnections": [ + { + "name": "[parameters('hostPoolPrivateEndpointName')]", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.DesktopVirtualization/hostPools', parameters('hostPoolName'))]", + "groupIds": [ + "connection" + ] + } + } + ], + "subnet": { + "id": "[parameters('subnetResourceId')]" } }, "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachineName'))]" + "[resourceId('Microsoft.DesktopVirtualization/hostPools', parameters('hostPoolName'))]" ] }, { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2021-03-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), 'GuestAttestation')]", - "location": "[parameters('location')]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-05-01", + "name": "[format('{0}/{1}', parameters('hostPoolPrivateEndpointName'), 'default')]", "properties": { - "publisher": "Microsoft.Azure.Security.WindowsAttestation", - "type": "GuestAttestation", - "typeHandlerVersion": "1.0", - "autoUpgradeMinorVersion": true, - "settings": { - "AttestationConfig": { - "MaaSettings": { - "maaEndpoint": "", - "maaTenantName": "GuestAttestation" - }, - "AscSettings": { - "ascReportingEndpoint": "", - "ascReportingFrequency": "" - }, - "useCustomToken": "false", - "disableAlerts": "false" + "privateDnsZoneConfigs": [ + { + "name": "[replace(split(parameters('avdPrivateDnsZoneResourceId'), '/')[8], '.', '-')]", + "properties": { + "privateDnsZoneId": "[parameters('avdPrivateDnsZoneResourceId')]" + } } - } + ] }, "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachineName'))]" + "[resourceId('Microsoft.Network/privateEndpoints', parameters('hostPoolPrivateEndpointName'))]" ] }, { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2019-07-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), 'JsonADDomainExtension')]", - "location": "[parameters('location')]", - "tags": "[variables('tagsVirtualMachines')]", + "condition": "[parameters('enableAvdInsights')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.DesktopVirtualization/hostPools/{0}', parameters('hostPoolName'))]", + "name": "[parameters('hostPoolDiagnosticSettingName')]", "properties": { - "forceUpdateTag": "[parameters('timestamp')]", - "publisher": "Microsoft.Compute", - "type": "JsonADDomainExtension", - "typeHandlerVersion": "1.3", - "autoUpgradeMinorVersion": true, - "settings": { - "Name": "[parameters('domainName')]", - "Options": "3", - "OUPath": "[parameters('organizationalUnitPath')]", - "Restart": "true", - "User": "[parameters('domainJoinUserPrincipalName')]" - }, - "protectedSettings": { - "Password": "[parameters('domainJoinPassword')]" - } + "logs": [ + { + "categoryGroup": "allLogs", + "enabled": true + } + ], + "workspaceId": "[parameters('logAnalyticsWorkspaceResourceId')]" }, "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachineName'))]" + "[resourceId('Microsoft.DesktopVirtualization/hostPools', parameters('hostPoolName'))]" ] } ], "outputs": { "name": { "type": "string", - "value": "[parameters('virtualMachineName')]" + "value": "[parameters('hostPoolName')]" }, "resourceId": { "type": "string", - "value": "[resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachineName'))]" + "value": "[resourceId('Microsoft.DesktopVirtualization/hostPools', parameters('hostPoolName'))]" } } } }, "dependsOn": [ - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix')))]" + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-monitoring-{0}', parameters('deploymentNameSuffix')))]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" ] }, { - "condition": "[or(parameters('enableApplicationInsights'), parameters('enableAvdInsights'))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('deploy-monitoring-{0}', parameters('deploymentNameSuffix'))]", - "resourceGroup": "[parameters('resourceGroupManagement')]", + "name": "[format('deploy-disk-access-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[parameters('resourceGroupName')]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "deploymentNameSuffix": { - "value": "[parameters('deploymentNameSuffix')]" - }, - "enableAvdInsights": { - "value": "[parameters('enableAvdInsights')]" - }, - "hostPoolName": { - "value": "[variables('hostPoolName')]" + "hostPoolResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" }, "location": { "value": "[parameters('locationVirtualMachines')]" }, - "logAnalyticsWorkspaceRetention": { - "value": "[parameters('logAnalyticsWorkspaceRetention')]" - }, - "logAnalyticsWorkspaceSku": { - "value": "[parameters('logAnalyticsWorkspaceSku')]" - }, "mlzTags": { "value": "[parameters('mlzTags')]" }, "namingConvention": { "value": "[parameters('namingConvention')]" }, - "privateLinkScopeResourceId": { - "value": "[parameters('privateLinkScopeResourceId')]" - }, - "resourceGroupControlPlane": { - "value": "[parameters('resourceGroupControlPlane')]" - }, - "serviceToken": { - "value": "[parameters('serviceToken')]" + "subnetResourceId": { + "value": "[parameters('subnetResourceId')]" }, "tags": { "value": "[parameters('tags')]" @@ -7498,275 +6825,180 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "13280657809752810735" + "version": "0.31.92.45157", + "templateHash": "3145769776971011079" } }, "parameters": { - "deploymentNameSuffix": { - "type": "string" - }, - "enableAvdInsights": { - "type": "bool" - }, - "hostPoolName": { + "hostPoolResourceId": { "type": "string" }, "location": { "type": "string" }, - "logAnalyticsWorkspaceRetention": { - "type": "int" - }, - "logAnalyticsWorkspaceSku": { - "type": "string" - }, "mlzTags": { "type": "object" }, "namingConvention": { "type": "object" }, - "privateLinkScopeResourceId": { + "subnetResourceId": { "type": "string" }, - "resourceGroupControlPlane": { - "type": "string" + "tags": { + "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.Compute/diskAccesses", + "apiVersion": "2021-04-01", + "name": "[parameters('namingConvention').diskAccess]", + "location": "[parameters('location')]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Compute/diskAccesses'), createObject()), parameters('mlzTags'))]", + "properties": {} }, - "service": { + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-04-01", + "name": "[parameters('namingConvention').diskAccessPrivateEndpoint]", + "location": "[parameters('location')]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Network/privateEndpoints'), createObject()), parameters('mlzTags'))]", + "properties": { + "customNetworkInterfaceName": "[parameters('namingConvention').diskAccessNetworkInterface]", + "privateLinkServiceConnections": [ + { + "name": "[parameters('namingConvention').diskAccessPrivateEndpoint]", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Compute/diskAccesses', parameters('namingConvention').diskAccess)]", + "groupIds": [ + "disks" + ] + } + } + ], + "subnet": { + "id": "[parameters('subnetResourceId')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/diskAccesses', parameters('namingConvention').diskAccess)]" + ] + } + ], + "outputs": { + "resourceId": { + "type": "string", + "value": "[resourceId('Microsoft.Compute/diskAccesses', parameters('namingConvention').diskAccess)]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix')))]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('deploy-policy-disks-{0}', parameters('deploymentNameSuffix'))]", + "location": "[deployment().location]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "diskAccessResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-disk-access-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" + }, + "location": { + "value": "[parameters('locationControlPlane')]" + }, + "resourceGroupName": { + "value": "[parameters('resourceGroupName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.92.45157", + "templateHash": "6736401548874707618" + } + }, + "parameters": { + "diskAccessResourceId": { "type": "string", - "defaultValue": "mgmt" + "defaultValue": "" }, - "serviceToken": { + "location": { "type": "string" }, - "tags": { - "type": "object" + "resourceGroupName": { + "type": "string" } }, + "variables": { + "parameters": "[if(not(empty(parameters('diskAccessResourceId'))), createObject('diskAccessId', createObject('type', 'String', 'metadata', createObject('displayName', 'Disk Access Resource Id', 'description', 'The resource Id of the Disk Access to associate to the managed disks.'))), createObject())]", + "operations": "[if(not(empty(parameters('diskAccessResourceId'))), createArray(createObject('operation', 'addOrReplace', 'field', 'Microsoft.Compute/disks/networkAccessPolicy', 'value', 'AllowPrivate'), createObject('operation', 'addOrReplace', 'field', 'Microsoft.Compute/disks/publicNetworkAccess', 'value', 'Disabled'), createObject('operation', 'addOrReplace', 'field', 'Microsoft.Compute/disks/diskAccessId', 'value', '[parameters(''diskAccessId'')]')), createArray(createObject('operation', 'addOrReplace', 'field', 'Microsoft.Compute/disks/networkAccessPolicy', 'value', 'DenyAll'), createObject('operation', 'addOrReplace', 'field', 'Microsoft.Compute/disks/publicNetworkAccess', 'value', 'Disabled')))]" + }, "resources": [ { - "type": "Microsoft.OperationalInsights/workspaces", + "type": "Microsoft.Authorization/policyDefinitions", "apiVersion": "2021-06-01", - "name": "[replace(parameters('namingConvention').logAnalyticsWorkspace, parameters('serviceToken'), parameters('service'))]", - "location": "[parameters('location')]", - "tags": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.OperationalInsights/workspaces'), parameters('tags')['Microsoft.OperationalInsights/workspaces'], createObject()), parameters('mlzTags'))]", - "properties": { - "sku": { - "name": "[parameters('logAnalyticsWorkspaceSku')]" - }, - "retentionInDays": "[parameters('logAnalyticsWorkspaceRetention')]", - "workspaceCapping": { - "dailyQuotaGb": -1 - }, - "publicNetworkAccessForIngestion": "Disabled", - "publicNetworkAccessForQuery": "Enabled" - } - }, - { - "condition": "[parameters('enableAvdInsights')]", - "type": "Microsoft.Insights/dataCollectionRules", - "apiVersion": "2022-06-01", - "name": "[format('microsoft-avdi-{0}', replace(parameters('namingConvention').dataCollectionRule, parameters('serviceToken'), parameters('service')))]", - "location": "[parameters('location')]", - "tags": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.Insights/dataCollectionRules'), parameters('tags')['Microsoft.Insights/dataCollectionRules'], createObject()), parameters('mlzTags'))]", - "kind": "Windows", + "name": "DiskNetworkAccess", "properties": { - "dataSources": { - "performanceCounters": [ - { - "streams": [ - "Microsoft-Perf" - ], - "samplingFrequencyInSeconds": 30, - "counterSpecifiers": [ - "\\LogicalDisk(C:)\\Avg. Disk Queue Length", - "\\LogicalDisk(C:)\\Current Disk Queue Length", - "\\Memory\\Available Mbytes", - "\\Memory\\Page Faults/sec", - "\\Memory\\Pages/sec", - "\\Memory\\% Committed Bytes In Use", - "\\PhysicalDisk(*)\\Avg. Disk Queue Length", - "\\PhysicalDisk(*)\\Avg. Disk sec/Read", - "\\PhysicalDisk(*)\\Avg. Disk sec/Transfer", - "\\PhysicalDisk(*)\\Avg. Disk sec/Write", - "\\Processor Information(_Total)\\% Processor Time", - "\\User Input Delay per Process(*)\\Max Input Delay", - "\\User Input Delay per Session(*)\\Max Input Delay", - "\\RemoteFX Network(*)\\Current TCP RTT", - "\\RemoteFX Network(*)\\Current UDP Bandwidth" - ], - "name": "perfCounterDataSource10" - }, - { - "streams": [ - "Microsoft-Perf" - ], - "samplingFrequencyInSeconds": 60, - "counterSpecifiers": [ - "\\LogicalDisk(C:)\\% Free Space", - "\\LogicalDisk(C:)\\Avg. Disk sec/Transfer", - "\\Terminal Services(*)\\Active Sessions", - "\\Terminal Services(*)\\Inactive Sessions", - "\\Terminal Services(*)\\Total Sessions" - ], - "name": "perfCounterDataSource30" - } - ], - "windowsEventLogs": [ - { - "streams": [ - "Microsoft-Event" - ], - "xPathQueries": [ - "Microsoft-Windows-TerminalServices-RemoteConnectionManager/Admin!*[System[(Level=2 or Level=3 or Level=4 or Level=0)]]", - "Microsoft-Windows-TerminalServices-LocalSessionManager/Operational!*[System[(Level=2 or Level=3 or Level=4 or Level=0)]]", - "System!*", - "Microsoft-FSLogix-Apps/Operational!*[System[(Level=2 or Level=3 or Level=4 or Level=0)]]", - "Application!*[System[(Level=2 or Level=3)]]", - "Microsoft-FSLogix-Apps/Admin!*[System[(Level=2 or Level=3 or Level=4 or Level=0)]]" + "description": "Disable network access to managed disks.", + "displayName": "Disable Disk Access", + "mode": "All", + "parameters": "[variables('parameters')]", + "policyRule": { + "if": { + "field": "type", + "equals": "Microsoft.Compute/disks" + }, + "then": { + "effect": "modify", + "details": { + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/60fc6e62-5479-42d4-8bf4-67625fcc2840" ], - "name": "eventLogsDataSource" - } - ] - }, - "destinations": { - "logAnalytics": [ - { - "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', replace(parameters('namingConvention').logAnalyticsWorkspace, parameters('serviceToken'), parameters('service')))]", - "name": "la-workspace" + "operations": "[variables('operations')]" } - ] - }, - "dataFlows": [ - { - "streams": [ - "Microsoft-Perf", - "Microsoft-Event" - ], - "destinations": [ - "la-workspace" - ] } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', replace(parameters('namingConvention').logAnalyticsWorkspace, parameters('serviceToken'), parameters('service')))]" - ] - }, - { - "condition": "[parameters('enableAvdInsights')]", - "type": "Microsoft.Insights/dataCollectionEndpoints", - "apiVersion": "2021-04-01", - "name": "[replace(parameters('namingConvention').dataCollectionEndpoint, parameters('serviceToken'), parameters('service'))]", - "location": "[parameters('location')]", - "tags": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.Insights/dataCollectionEndpoints'), parameters('tags')['Microsoft.Insights/dataCollectionEndpoints'], createObject()), parameters('mlzTags'))]", - "kind": "Windows", - "properties": { - "networkAcls": { - "publicNetworkAccess": "Disabled" - } + }, + "policyType": "Custom" } }, { "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('deploy-private-link-scope-law-{0}', parameters('deploymentNameSuffix'))]", - "subscriptionId": "[split(parameters('privateLinkScopeResourceId'), '/')[2]]", - "resourceGroup": "[split(parameters('privateLinkScopeResourceId'), '/')[4]]", + "name": "assign-policy-disk-network-access", + "resourceGroup": "[parameters('resourceGroupName')]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "logAnalyticsWorkspaceResourceId": { - "value": "[resourceId('Microsoft.OperationalInsights/workspaces', replace(parameters('namingConvention').logAnalyticsWorkspace, parameters('serviceToken'), parameters('service')))]" + "diskAccessResourceId": { + "value": "[parameters('diskAccessResourceId')]" }, - "privateLinkScopeResourceId": { - "value": "[parameters('privateLinkScopeResourceId')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "4414484880639231715" - } + "location": { + "value": "[parameters('location')]" }, - "parameters": { - "applicationInsightsResourceId": { - "type": "string", - "defaultValue": "" - }, - "dataCollectionEndpointResourceId": { - "type": "string", - "defaultValue": "" - }, - "logAnalyticsWorkspaceResourceId": { - "type": "string", - "defaultValue": "" - }, - "privateLinkScopeResourceId": { - "type": "string" - } + "policyDefinitionId": { + "value": "[subscriptionResourceId('Microsoft.Authorization/policyDefinitions', 'DiskNetworkAccess')]" }, - "resources": [ - { - "condition": "[not(empty(parameters('applicationInsightsResourceId')))]", - "type": "Microsoft.Insights/privateLinkScopes/scopedResources", - "apiVersion": "2021-09-01", - "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('applicationInsightsResourceId')), 'applicationInsights', split(parameters('applicationInsightsResourceId'), '/')[8]))]", - "properties": { - "linkedResourceId": "[parameters('applicationInsightsResourceId')]" - } - }, - { - "condition": "[not(empty(parameters('dataCollectionEndpointResourceId')))]", - "type": "Microsoft.Insights/privateLinkScopes/scopedResources", - "apiVersion": "2021-09-01", - "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('dataCollectionEndpointResourceId')), 'dataCollectionEndpoint', split(parameters('dataCollectionEndpointResourceId'), '/')[8]))]", - "properties": { - "linkedResourceId": "[parameters('dataCollectionEndpointResourceId')]" - } - }, - { - "condition": "[not(empty(parameters('logAnalyticsWorkspaceResourceId')))]", - "type": "Microsoft.Insights/privateLinkScopes/scopedResources", - "apiVersion": "2021-09-01", - "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('logAnalyticsWorkspaceResourceId')), 'logAnalyticsWorkspace', split(parameters('logAnalyticsWorkspaceResourceId'), '/')[8]))]", - "properties": { - "linkedResourceId": "[parameters('logAnalyticsWorkspaceResourceId')]" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', replace(parameters('namingConvention').logAnalyticsWorkspace, parameters('serviceToken'), parameters('service')))]" - ] - }, - { - "condition": "[parameters('enableAvdInsights')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('deploy-private-link-scope-dce-{0}', parameters('deploymentNameSuffix'))]", - "subscriptionId": "[split(parameters('privateLinkScopeResourceId'), '/')[2]]", - "resourceGroup": "[split(parameters('privateLinkScopeResourceId'), '/')[4]]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "dataCollectionEndpointResourceId": { - "value": "[resourceId('Microsoft.Insights/dataCollectionEndpoints', replace(parameters('namingConvention').dataCollectionEndpoint, parameters('serviceToken'), parameters('service')))]" + "policyDisplayName": { + "value": "[reference(subscriptionResourceId('Microsoft.Authorization/policyDefinitions', 'DiskNetworkAccess'), '2021-06-01').displayName]" }, - "privateLinkScopeResourceId": { - "value": "[parameters('privateLinkScopeResourceId')]" + "policyName": { + "value": "[reference(subscriptionResourceId('Microsoft.Authorization/policyDefinitions', 'DiskNetworkAccess'), '2021-06-01').displayName]" } }, "template": { @@ -7775,163 +7007,86 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "4414484880639231715" + "version": "0.31.92.45157", + "templateHash": "1275953978872597894" } }, "parameters": { - "applicationInsightsResourceId": { - "type": "string", - "defaultValue": "" + "diskAccessResourceId": { + "type": "string" }, - "dataCollectionEndpointResourceId": { - "type": "string", - "defaultValue": "" + "location": { + "type": "string" }, - "logAnalyticsWorkspaceResourceId": { - "type": "string", - "defaultValue": "" + "policyDefinitionId": { + "type": "string" }, - "privateLinkScopeResourceId": { + "policyDisplayName": { + "type": "string" + }, + "policyName": { "type": "string" } }, "resources": [ { - "condition": "[not(empty(parameters('applicationInsightsResourceId')))]", - "type": "Microsoft.Insights/privateLinkScopes/scopedResources", - "apiVersion": "2021-09-01", - "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('applicationInsightsResourceId')), 'applicationInsights', split(parameters('applicationInsightsResourceId'), '/')[8]))]", - "properties": { - "linkedResourceId": "[parameters('applicationInsightsResourceId')]" - } - }, - { - "condition": "[not(empty(parameters('dataCollectionEndpointResourceId')))]", - "type": "Microsoft.Insights/privateLinkScopes/scopedResources", - "apiVersion": "2021-09-01", - "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('dataCollectionEndpointResourceId')), 'dataCollectionEndpoint', split(parameters('dataCollectionEndpointResourceId'), '/')[8]))]", - "properties": { - "linkedResourceId": "[parameters('dataCollectionEndpointResourceId')]" - } - }, - { - "condition": "[not(empty(parameters('logAnalyticsWorkspaceResourceId')))]", - "type": "Microsoft.Insights/privateLinkScopes/scopedResources", - "apiVersion": "2021-09-01", - "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('logAnalyticsWorkspaceResourceId')), 'logAnalyticsWorkspace', split(parameters('logAnalyticsWorkspaceResourceId'), '/')[8]))]", + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2022-06-01", + "name": "[parameters('policyName')]", + "location": "[parameters('location')]", + "identity": { + "type": "SystemAssigned" + }, "properties": { - "linkedResourceId": "[parameters('logAnalyticsWorkspaceResourceId')]" + "displayName": "[parameters('policyDisplayName')]", + "policyDefinitionId": "[parameters('policyDefinitionId')]", + "parameters": "[if(not(empty(parameters('diskAccessResourceId'))), createObject('diskAccessId', createObject('value', parameters('diskAccessResourceId'))), createObject())]" } } ] } }, "dependsOn": [ - "[resourceId('Microsoft.Insights/dataCollectionEndpoints', replace(parameters('namingConvention').dataCollectionEndpoint, parameters('serviceToken'), parameters('service')))]" + "[subscriptionResourceId('Microsoft.Authorization/policyDefinitions', 'DiskNetworkAccess')]" ] } ], "outputs": { - "logAnalyticsWorkspaceName": { - "type": "string", - "value": "[replace(parameters('namingConvention').logAnalyticsWorkspace, parameters('serviceToken'), parameters('service'))]" - }, - "logAnalyticsWorkspaceResourceId": { + "policyDefinitionId": { "type": "string", - "value": "[resourceId('Microsoft.OperationalInsights/workspaces', replace(parameters('namingConvention').logAnalyticsWorkspace, parameters('serviceToken'), parameters('service')))]" + "value": "[subscriptionResourceId('Microsoft.Authorization/policyDefinitions', 'DiskNetworkAccess')]" }, - "dataCollectionRuleResourceId": { + "policyDisplayName": { "type": "string", - "value": "[if(parameters('enableAvdInsights'), resourceId('Microsoft.Insights/dataCollectionRules', format('microsoft-avdi-{0}', replace(parameters('namingConvention').dataCollectionRule, parameters('serviceToken'), parameters('service')))), '')]" + "value": "[reference(subscriptionResourceId('Microsoft.Authorization/policyDefinitions', 'DiskNetworkAccess'), '2021-06-01').displayName]" } } } - } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-disk-access-{0}', parameters('deploymentNameSuffix')))]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" + ] }, { - "condition": "[or(parameters('scalingTool'), equals(parameters('fslogixStorageService'), 'AzureFiles Premium'))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('deploy-function-app-{0}', parameters('deploymentNameSuffix'))]", - "resourceGroup": "[parameters('resourceGroupManagement')]", + "name": "[format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[parameters('resourceGroupName')]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "delegatedSubnetResourceId": { - "value": "[filter(parameters('subnets'), lambda('subnet', contains(lambdaVariables('subnet').name, 'FunctionAppOutbound')))[0].id]" - }, - "deployFslogix": { - "value": "[parameters('deployFslogix')]" - }, - "deploymentNameSuffix": { - "value": "[parameters('deploymentNameSuffix')]" - }, - "enableApplicationInsights": { - "value": "[parameters('enableApplicationInsights')]" - }, - "environmentAbbreviation": { - "value": "[parameters('environmentAbbreviation')]" - }, - "hostPoolName": { - "value": "[variables('hostPoolName')]" - }, - "logAnalyticsWorkspaceResourceId": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-monitoring-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.logAnalyticsWorkspaceResourceId.value]" - }, - "namingConvention": { - "value": "[parameters('namingConvention')]" - }, - "privateDnsZoneResourceIdPrefix": { - "value": "[parameters('privateDnsZoneResourceIdPrefix')]" - }, - "privateDnsZones": { - "value": "[parameters('privateDnsZones')]" - }, - "privateLinkScopeResourceId": { - "value": "[parameters('privateLinkScopeResourceId')]" - }, - "resourceAbbreviations": { - "value": "[parameters('resourceAbbreviations')]" - }, - "resourceGroupControlPlane": { - "value": "[parameters('resourceGroupControlPlane')]" - }, - "resourceGroupHosts": { - "value": "[parameters('resourceGroupHosts')]" - }, - "resourceGroupStorage": { - "value": "[parameters('resourceGroupStorage')]" - }, - "scalingBeginPeakTime": { - "value": "[parameters('scalingBeginPeakTime')]" - }, - "scalingEndPeakTime": { - "value": "[parameters('scalingEndPeakTime')]" - }, - "scalingLimitSecondsToForceLogOffUser": { - "value": "[parameters('scalingLimitSecondsToForceLogOffUser')]" - }, - "scalingMinimumNumberOfRdsh": { - "value": "[parameters('scalingMinimumNumberOfRdsh')]" - }, - "scalingSessionThresholdPerCPU": { - "value": "[parameters('scalingSessionThresholdPerCPU')]" - }, - "serviceToken": { - "value": "[parameters('serviceToken')]" + "location": { + "value": "[parameters('locationVirtualMachines')]" }, - "subnetResourceId": { - "value": "[parameters('subnetResourceId')]" + "name": { + "value": "[replace(variables('userAssignedIdentityNamePrefix'), parameters('serviceToken'), 'deployment')]" }, "tags": { - "value": "[parameters('tags')]" - }, - "timeDifference": { - "value": "[parameters('timeDifference')]" + "value": "[union(createObject('cm-resource-parent', reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value), coalesce(tryGet(parameters('tags'), 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()), parameters('mlzTags'))]" } }, "template": { @@ -7940,691 +7095,646 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "15398537477314996235" + "version": "0.31.92.45157", + "templateHash": "10766099399924390355" } }, "parameters": { - "delegatedSubnetResourceId": { - "type": "string" - }, - "deployFslogix": { - "type": "bool" - }, - "deploymentNameSuffix": { - "type": "string" - }, - "enableApplicationInsights": { - "type": "bool" - }, - "environmentAbbreviation": { - "type": "string" - }, - "hostPoolName": { - "type": "string" - }, - "keyExpirationInDays": { - "type": "int", - "defaultValue": 30 - }, "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]" - }, - "logAnalyticsWorkspaceResourceId": { - "type": "string" - }, - "namingConvention": { - "type": "object" - }, - "privateDnsZoneResourceIdPrefix": { "type": "string" }, - "privateDnsZones": { - "type": "array" - }, - "privateLinkScopeResourceId": { + "name": { "type": "string" }, - "resourceAbbreviations": { + "tags": { "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2018-11-30", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]" + } + ], + "outputs": { + "clientId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2018-11-30').clientId]" }, - "resourceGroupControlPlane": { - "type": "string" + "resourceId": { + "type": "string", + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name'))]" }, - "resourceGroupHosts": { + "principalId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2018-11-30').principalId]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix')))]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('assign-role-mgmt-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "principalId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.principalId.value]" + }, + "principalType": { + "value": "ServicePrincipal" + }, + "roleDefinitionId": { + "value": "082f0a83-3be5-4ba1-904c-961cca79b387" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.92.45157", + "templateHash": "10174155731090308034" + } + }, + "parameters": { + "principalId": { "type": "string" }, - "resourceGroupStorage": { + "principalType": { "type": "string" }, - "scalingBeginPeakTime": { + "roleDefinitionId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('principalId'), parameters('roleDefinitionId'), resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionId'))]", + "principalId": "[parameters('principalId')]", + "principalType": "[parameters('principalType')]" + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix')))]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('deploy-mgmt-vm-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "deploymentUserAssignedIdentityPrincipalId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.principalId.value]" + }, + "deploymentUserAssignedIdentityResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" + }, + "diskEncryptionSetResourceId": { + "value": "[parameters('diskEncryptionSetResourceId')]" + }, + "diskName": { + "value": "[replace(parameters('namingConvention').virtualMachineDisk, parameters('serviceToken'), 'mgt')]" + }, + "diskSku": { + "value": "[parameters('diskSku')]" + }, + "domainJoinPassword": { + "value": "[parameters('domainJoinPassword')]" + }, + "domainJoinUserPrincipalName": { + "value": "[parameters('domainJoinUserPrincipalName')]" + }, + "domainName": { + "value": "[parameters('domainName')]" + }, + "hostPoolResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" + }, + "location": { + "value": "[parameters('locationVirtualMachines')]" + }, + "mlzTags": { + "value": "[parameters('mlzTags')]" + }, + "networkInterfaceName": { + "value": "[replace(parameters('namingConvention').virtualMachineNetworkInterface, parameters('serviceToken'), 'mgt')]" + }, + "organizationalUnitPath": { + "value": "[parameters('organizationalUnitPath')]" + }, + "subnetResourceId": { + "value": "[parameters('subnetResourceId')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "virtualMachineName": { + "value": "[replace(parameters('namingConvention').virtualMachine, parameters('serviceToken'), 'mgt')]" + }, + "virtualMachinePassword": { + "value": "[parameters('virtualMachinePassword')]" + }, + "virtualMachineUsername": { + "value": "[parameters('virtualMachineUsername')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.92.45157", + "templateHash": "3320903441430575822" + } + }, + "parameters": { + "deploymentUserAssignedIdentityPrincipalId": { "type": "string" }, - "scalingEndPeakTime": { + "deploymentUserAssignedIdentityResourceId": { "type": "string" }, - "scalingLimitSecondsToForceLogOffUser": { + "diskEncryptionSetResourceId": { "type": "string" }, - "scalingMinimumNumberOfRdsh": { + "diskName": { "type": "string" }, - "scalingSessionThresholdPerCPU": { + "diskSku": { "type": "string" }, - "serviceToken": { - "type": "string" + "domainJoinPassword": { + "type": "securestring" }, - "subnetResourceId": { + "domainJoinUserPrincipalName": { "type": "string" }, - "tags": { - "type": "object" - }, - "timeDifference": { + "domainName": { "type": "string" - } - }, - "variables": { - "cloudSuffix": "[replace(replace(environment().resourceManager, 'https://management.', ''), '/', '')]", - "functionAppKeyword": "[if(or(equals(environment().name, 'AzureCloud'), equals(environment().name, 'AzureUSGovernment')), 'azurewebsites', 'appservice')]", - "functionAppScmPrivateDnsZoneResourceId": "[format('{0}scm.{1}', parameters('privateDnsZoneResourceIdPrefix'), filter(parameters('privateDnsZones'), lambda('name', contains(lambdaVariables('name'), variables('functionAppKeyword'))))[0])]", - "roleAssignments": "[union(createArray(createObject('roleDefinitionId', '40c5ff49-9181-41f8-ae61-143b0e78555e', 'scope', parameters('resourceGroupControlPlane')), createObject('roleDefinitionId', '40c5ff49-9181-41f8-ae61-143b0e78555e', 'scope', parameters('resourceGroupHosts'))), if(parameters('deployFslogix'), createArray(createObject('roleDefinitionId', '17d1049b-9a84-46fb-8f53-869881c3d3ab', 'scope', parameters('resourceGroupStorage'))), createArray()))]", - "service": "mgmt", - "storagePrivateDnsZoneResourceIds": [ - "[format('{0}{1}', parameters('privateDnsZoneResourceIdPrefix'), filter(parameters('privateDnsZones'), lambda('name', contains(lambdaVariables('name'), 'blob')))[0])]", - "[format('{0}{1}', parameters('privateDnsZoneResourceIdPrefix'), filter(parameters('privateDnsZones'), lambda('name', contains(lambdaVariables('name'), 'file')))[0])]", - "[format('{0}{1}', parameters('privateDnsZoneResourceIdPrefix'), filter(parameters('privateDnsZones'), lambda('name', contains(lambdaVariables('name'), 'queue')))[0])]", - "[format('{0}{1}', parameters('privateDnsZoneResourceIdPrefix'), filter(parameters('privateDnsZones'), lambda('name', contains(lambdaVariables('name'), 'table')))[0])]" - ], - "storageSubResources": [ - "blob", - "file", - "queue", - "table" - ] - }, - "resources": [ - { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service'))]", - "location": "[parameters('location')]", - "tags": "[coalesce(tryGet(parameters('tags'), 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject())]" - }, - { - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2022-07-01", - "name": "[format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id))]", - "location": "[parameters('location')]", - "tags": "[coalesce(tryGet(parameters('tags'), 'Microsoft.KeyVault/vaults'), createObject())]", - "properties": { - "enabledForDeployment": false, - "enabledForDiskEncryption": false, - "enabledForTemplateDeployment": false, - "enablePurgeProtection": true, - "enableRbacAuthorization": true, - "enableSoftDelete": true, - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "Deny", - "ipRules": [], - "virtualNetworkRules": [] - }, - "publicNetworkAccess": "Disabled", - "sku": { - "family": "A", - "name": "premium" - }, - "softDeleteRetentionInDays": "[if(or(equals(parameters('environmentAbbreviation'), 'dev'), equals(parameters('environmentAbbreviation'), 'test')), 7, 90)]", - "tenantId": "[subscription().tenantId]" - } - }, - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2020-04-01-preview", - "scope": "[format('Microsoft.KeyVault/vaults/{0}', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id)))]", - "name": "[guid(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service'))), 'e147488a-f6f5-4113-8e2d-b22465e65bf6', resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id))))]", - "properties": { - "principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service'))), '2023-01-31').principalId]", - "principalType": "ServicePrincipal", - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]" - }, - "dependsOn": [ - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service')))]", - "[resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id)))]" - ] - }, - { - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-04-01", - "name": "[replace(parameters('namingConvention').keyVaultPrivateEndpoint, parameters('serviceToken'), variables('service'))]", - "location": "[parameters('location')]", - "tags": "[coalesce(tryGet(parameters('tags'), 'Microsoft.Network/privateEndpoints'), createObject())]", - "properties": { - "customNetworkInterfaceName": "[replace(parameters('namingConvention').keyVaultNetworkInterface, parameters('serviceToken'), variables('service'))]", - "privateLinkServiceConnections": [ - { - "name": "[replace(parameters('namingConvention').keyVaultPrivateEndpoint, parameters('serviceToken'), variables('service'))]", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id)))]", - "groupIds": [ - "vault" - ] - } - } - ], - "subnet": { - "id": "[parameters('subnetResourceId')]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id)))]" - ] - }, - { - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2021-08-01", - "name": "[format('{0}/{1}', replace(parameters('namingConvention').keyVaultPrivateEndpoint, parameters('serviceToken'), variables('service')), format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id)))]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "ipconfig1", - "properties": { - "privateDnsZoneId": "[format('{0}{1}', parameters('privateDnsZoneResourceIdPrefix'), filter(parameters('privateDnsZones'), lambda('name', contains(lambdaVariables('name'), 'vaultcore')))[0])]" - } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateEndpoints', replace(parameters('namingConvention').keyVaultPrivateEndpoint, parameters('serviceToken'), variables('service')))]", - "[resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id)))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/keys", - "apiVersion": "2022-07-01", - "name": "[format('{0}/{1}', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id)), 'StorageEncryptionKey')]", - "properties": { - "attributes": { - "enabled": true - }, - "keySize": 4096, - "kty": "RSA", - "rotationPolicy": { - "attributes": { - "expiryTime": "[format('P{0}D', string(parameters('keyExpirationInDays')))]" - }, - "lifetimeActions": [ - { - "action": { - "type": "Notify" - }, - "trigger": { - "timeBeforeExpiry": "P10D" - } - }, - { - "action": { - "type": "Rotate" - }, - "trigger": { - "timeAfterCreate": "[format('P{0}D', string(sub(parameters('keyExpirationInDays'), 7)))]" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id)))]" - ] }, - { - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id)]", - "location": "[parameters('location')]", - "tags": "[coalesce(tryGet(parameters('tags'), 'Microsoft.Storage/storageAccounts'), createObject())]", - "sku": { - "name": "Standard_LRS" - }, - "kind": "StorageV2", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service'))))]": {} - } - }, - "properties": { - "accessTier": "Hot", - "allowBlobPublicAccess": false, - "allowCrossTenantReplication": false, - "allowedCopyScope": "PrivateLink", - "allowSharedKeyAccess": false, - "azureFilesIdentityBasedAuthentication": { - "directoryServiceOptions": "None" - }, - "defaultToOAuthAuthentication": false, - "dnsEndpointType": "Standard", - "encryption": { - "identity": { - "userAssignedIdentity": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service')))]" - }, - "requireInfrastructureEncryption": true, - "keyvaultproperties": { - "keyvaulturi": "[reference(resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id))), '2022-07-01').vaultUri]", - "keyname": "StorageEncryptionKey" - }, - "services": { - "file": { - "keyType": "Account", - "enabled": true - }, - "table": { - "keyType": "Account", - "enabled": true - }, - "queue": { - "keyType": "Account", - "enabled": true - }, - "blob": { - "keyType": "Account", - "enabled": true - } - }, - "keySource": "Microsoft.KeyVault" - }, - "largeFileSharesState": "Disabled", - "minimumTlsVersion": "TLS1_2", - "networkAcls": { - "bypass": "AzureServices", - "virtualNetworkRules": [], - "ipRules": [], - "defaultAction": "Deny" - }, - "publicNetworkAccess": "Disabled", - "supportsHttpsTrafficOnly": true - }, - "dependsOn": [ - "[resourceId('Microsoft.KeyVault/vaults/keys', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id)), 'StorageEncryptionKey')]", - "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', replace(parameters('namingConvention').keyVaultPrivateEndpoint, parameters('serviceToken'), variables('service')), format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id)))]", - "[resourceId('Microsoft.Network/privateEndpoints', replace(parameters('namingConvention').keyVaultPrivateEndpoint, parameters('serviceToken'), variables('service')))]", - "[extensionResourceId(resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id))), 'Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service'))), 'e147488a-f6f5-4113-8e2d-b22465e65bf6', resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id)))))]", - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service')))]", - "[resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, variables('service'), 'cmk'), resourceGroup().id)))]" - ] + "hostPoolResourceId": { + "type": "string" }, - { - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2021-09-01", - "name": "[format('{0}/{1}', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id), 'default')]", - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id))]" - ] + "location": { + "type": "string" + }, + "mlzTags": { + "type": "object" + }, + "networkInterfaceName": { + "type": "string" + }, + "organizationalUnitPath": { + "type": "string" + }, + "subnetResourceId": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "timestamp": { + "type": "string", + "defaultValue": "[utcNow('yyyyMMddhhmmss')]" + }, + "virtualMachineName": { + "type": "string" }, + "virtualMachinePassword": { + "type": "securestring" + }, + "virtualMachineUsername": { + "type": "string" + } + }, + "variables": { + "tagsVirtualMachines": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Compute/virtualMachines'), createObject()), parameters('mlzTags'))]" + }, + "resources": [ { - "copy": { - "name": "privateEndpoints_storage", - "count": "[length(variables('storageSubResources'))]" - }, - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-04-01", - "name": "[replace(parameters('namingConvention').storageAccountPrivateEndpoint, format('{0}-{1}', parameters('serviceToken'), parameters('resourceAbbreviations').storageAccounts), format('{0}-{1}-scale', variables('storageSubResources')[copyIndex()], parameters('resourceAbbreviations').storageAccounts))]", + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2020-05-01", + "name": "[parameters('networkInterfaceName')]", "location": "[parameters('location')]", - "tags": "[coalesce(tryGet(parameters('tags'), 'Microsoft.Network/privateEndpoints'), createObject())]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Network/networkInterfaces'), createObject()), parameters('mlzTags'))]", "properties": { - "customNetworkInterfaceName": "[replace(parameters('namingConvention').storageAccountNetworkInterface, format('{0}-{1}', parameters('serviceToken'), parameters('resourceAbbreviations').storageAccounts), format('{0}-{1}-scale', variables('storageSubResources')[copyIndex()], parameters('resourceAbbreviations').storageAccounts))]", - "privateLinkServiceConnections": [ + "ipConfigurations": [ { - "name": "[replace(parameters('namingConvention').storageAccountPrivateEndpoint, format('{0}-{1}', parameters('serviceToken'), parameters('resourceAbbreviations').storageAccounts), format('{0}-{1}-scale', variables('storageSubResources')[copyIndex()], parameters('resourceAbbreviations').storageAccounts))]", + "name": "ipconfig", "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id))]", - "groupIds": [ - "[variables('storageSubResources')[copyIndex()]]" - ] + "privateIPAllocationMethod": "Dynamic", + "subnet": { + "id": "[parameters('subnetResourceId')]" + }, + "primary": true, + "privateIPAddressVersion": "IPv4" } } ], - "subnet": { - "id": "[parameters('subnetResourceId')]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id))]" - ] + "enableAcceleratedNetworking": false, + "enableIPForwarding": false + } }, { - "copy": { - "name": "privateDnsZoneGroups_storage", - "count": "[length(variables('storageSubResources'))]" - }, - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2021-08-01", - "name": "[format('{0}/{1}', replace(parameters('namingConvention').storageAccountPrivateEndpoint, format('{0}-{1}', parameters('serviceToken'), parameters('resourceAbbreviations').storageAccounts), format('{0}-{1}-scale', variables('storageSubResources')[copyIndex()], parameters('resourceAbbreviations').storageAccounts)), uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id))]", + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2021-11-01", + "name": "[parameters('virtualMachineName')]", + "location": "[parameters('location')]", + "tags": "[variables('tagsVirtualMachines')]", "properties": { - "privateDnsZoneConfigs": [ - { - "name": "ipconfig1", - "properties": { - "privateDnsZoneId": "[variables('storagePrivateDnsZoneResourceIds')[copyIndex()]]" + "hardwareProfile": { + "vmSize": "Standard_B2s" + }, + "storageProfile": { + "imageReference": { + "publisher": "MicrosoftWindowsServer", + "offer": "WindowsServer", + "sku": "2019-datacenter-core-g2", + "version": "latest" + }, + "osDisk": { + "deleteOption": "Delete", + "osType": "Windows", + "createOption": "FromImage", + "caching": "None", + "managedDisk": { + "diskEncryptionSet": { + "id": "[parameters('diskEncryptionSetResourceId')]" + }, + "storageAccountType": "[parameters('diskSku')]" + }, + "name": "[parameters('diskName')]" + }, + "dataDisks": [] + }, + "osProfile": { + "computerName": "[parameters('virtualMachineName')]", + "adminUsername": "[parameters('virtualMachineUsername')]", + "adminPassword": "[parameters('virtualMachinePassword')]", + "windowsConfiguration": { + "provisionVMAgent": true, + "enableAutomaticUpdates": false + }, + "secrets": [], + "allowExtensionOperations": true + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaceName'))]", + "properties": { + "deleteOption": "Delete" + } } + ] + }, + "securityProfile": { + "uefiSettings": { + "secureBootEnabled": true, + "vTpmEnabled": true + }, + "securityType": "TrustedLaunch", + "encryptionAtHost": true + }, + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": false } - ] + }, + "licenseType": "Windows_Server" }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateEndpoints', replace(parameters('namingConvention').storageAccountPrivateEndpoint, format('{0}-{1}', parameters('serviceToken'), parameters('resourceAbbreviations').storageAccounts), format('{0}-{1}-scale', variables('storageSubResources')[copyIndex()], parameters('resourceAbbreviations').storageAccounts)))]", - "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id))]" - ] - }, - { - "condition": "[parameters('enableApplicationInsights')]", - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2017-05-01-preview", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id), 'default')]", - "name": "[replace(parameters('namingConvention').storageAccountDiagnosticSetting, format('{0}-{1}', parameters('serviceToken'), parameters('resourceAbbreviations').storageAccounts), format('blob-{0}-scale', parameters('resourceAbbreviations').storageAccounts))]", - "properties": { - "logs": [ - { - "category": "StorageWrite", - "enabled": true - } - ], - "metrics": [ - { - "category": "Transaction", - "enabled": true - } - ], - "workspaceId": "[parameters('logAnalyticsWorkspaceResourceId')]" + "identity": { + "type": "SystemAssigned, UserAssigned", + "userAssignedIdentities": { + "[format('{0}', parameters('deploymentUserAssignedIdentityResourceId'))]": {} + } }, "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts/blobServices', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id), 'default')]" + "[resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaceName'))]" ] }, { - "condition": "[parameters('enableApplicationInsights')]", - "type": "Microsoft.Insights/components", - "apiVersion": "2020-02-02", - "name": "[replace(parameters('namingConvention').applicationInsights, parameters('serviceToken'), variables('service'))]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2021-03-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), 'IaaSAntimalware')]", "location": "[parameters('location')]", - "tags": "[coalesce(tryGet(parameters('tags'), 'Microsoft.Insights/components'), createObject())]", + "tags": "[variables('tagsVirtualMachines')]", "properties": { - "Application_Type": "web", - "publicNetworkAccessForIngestion": "Disabled", - "publicNetworkAccessForQuery": "Disabled" - }, - "kind": "web" - }, - { - "type": "Microsoft.Web/serverfarms", - "apiVersion": "2023-01-01", - "name": "[replace(parameters('namingConvention').appServicePlan, parameters('serviceToken'), variables('service'))]", - "location": "[parameters('location')]", - "tags": "[coalesce(tryGet(parameters('tags'), 'Microsoft.Web/serverfarms'), createObject())]", - "sku": { - "name": "P0v3", - "tier": "PremiumV3", - "size": "P0v3", - "family": "Pv3", - "capacity": 1 + "publisher": "Microsoft.Azure.Security", + "type": "IaaSAntimalware", + "typeHandlerVersion": "1.3", + "autoUpgradeMinorVersion": true, + "enableAutomaticUpgrade": false, + "settings": { + "AntimalwareEnabled": true, + "RealtimeProtectionEnabled": "true", + "ScheduledScanSettings": { + "isEnabled": "true", + "day": "7", + "time": "120", + "scanType": "Quick" + }, + "Exclusions": {} + } }, - "kind": "functionapp" + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachineName'))]" + ] }, { - "type": "Microsoft.Web/sites", - "apiVersion": "2023-01-01", - "name": "[uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id)]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2021-03-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), 'GuestAttestation')]", "location": "[parameters('location')]", - "tags": "[coalesce(tryGet(parameters('tags'), 'Microsoft.Web/sites'), createObject())]", - "kind": "functionapp", - "identity": { - "type": "SystemAssigned" - }, "properties": { - "clientAffinityEnabled": false, - "httpsOnly": true, - "publicNetworkAccess": "Disabled", - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', replace(parameters('namingConvention').appServicePlan, parameters('serviceToken'), variables('service')))]", - "siteConfig": { - "alwaysOn": true, - "appSettings": "[union(createArray(createObject('name', 'AzureWebJobsStorage__blobServiceUri', 'value', format('https://{0}.blob.{1}', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id), environment().suffixes.storage)), createObject('name', 'AzureWebJobsStorage__credential', 'value', 'managedidentity'), createObject('name', 'AzureWebJobsStorage__queueServiceUri', 'value', format('https://{0}.queue.{1}', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id), environment().suffixes.storage)), createObject('name', 'AzureWebJobsStorage__tableServiceUri', 'value', format('https://{0}.table.{1}', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id), environment().suffixes.storage)), createObject('name', 'FUNCTIONS_EXTENSION_VERSION', 'value', '~4'), createObject('name', 'FUNCTIONS_WORKER_RUNTIME', 'value', 'powershell'), createObject('name', 'WEBSITE_LOAD_USER_PROFILE', 'value', '1'), createObject('name', 'BeginPeakTime', 'value', parameters('scalingBeginPeakTime')), createObject('name', 'EndPeakTime', 'value', parameters('scalingEndPeakTime')), createObject('name', 'EnvironmentName', 'value', environment().name), createObject('name', 'FileShareName', 'value', 'profile-containers'), createObject('name', 'HostPoolName', 'value', parameters('hostPoolName')), createObject('name', 'HostPoolResourceGroupName', 'value', parameters('resourceGroupControlPlane')), createObject('name', 'LimitSecondsToForceLogOffUser', 'value', parameters('scalingLimitSecondsToForceLogOffUser')), createObject('name', 'LogOffMessageBody', 'value', 'This session is about to be logged off. Please save your work.'), createObject('name', 'LogOffMessageTitle', 'value', 'Session Log Off'), createObject('name', 'MaintenanceTagName', 'value', 'Maintenance'), createObject('name', 'MinimumNumberOfRDSH', 'value', parameters('scalingMinimumNumberOfRdsh')), createObject('name', 'ResourceGroupName', 'value', parameters('resourceGroupStorage')), createObject('name', 'ResourceManagerUrl', 'value', if(endsWith(environment().resourceManager, '/'), environment().resourceManager, format('{0}/', environment().resourceManager))), createObject('name', 'SessionThresholdPerCPU', 'value', parameters('scalingSessionThresholdPerCPU')), createObject('name', 'StorageSuffix', 'value', environment().suffixes.storage), createObject('name', 'SubscriptionId', 'value', subscription().subscriptionId), createObject('name', 'TenantId', 'value', subscription().tenantId), createObject('name', 'TimeDifference', 'value', parameters('timeDifference'))), if(parameters('enableApplicationInsights'), createArray(createObject('name', 'APPLICATIONINSIGHTS_CONNECTION_STRING', 'value', reference(resourceId('Microsoft.Insights/components', replace(parameters('namingConvention').applicationInsights, parameters('serviceToken'), variables('service'))), '2020-02-02').ConnectionString)), createArray()))]", - "cors": { - "allowedOrigins": [ - "[format('{0}', environment().portal)]", - "[format('https://functions-next.{0}', variables('cloudSuffix'))]", - "[format('https://functions-staging.{0}', variables('cloudSuffix'))]", - "[format('https://functions.{0}', variables('cloudSuffix'))]" - ] - }, - "ftpsState": "Disabled", - "netFrameworkVersion": "v6.0", - "powerShellVersion": "7.2", - "publicNetworkAccess": "Disabled", - "use32BitWorkerProcess": false - }, - "virtualNetworkSubnetId": "[parameters('delegatedSubnetResourceId')]", - "vnetContentShareEnabled": false, - "vnetRouteAllEnabled": true + "publisher": "Microsoft.Azure.Security.WindowsAttestation", + "type": "GuestAttestation", + "typeHandlerVersion": "1.0", + "autoUpgradeMinorVersion": true, + "settings": { + "AttestationConfig": { + "MaaSettings": { + "maaEndpoint": "", + "maaTenantName": "GuestAttestation" + }, + "AscSettings": { + "ascReportingEndpoint": "", + "ascReportingFrequency": "" + }, + "useCustomToken": "false", + "disableAlerts": "false" + } + } }, "dependsOn": [ - "[resourceId('Microsoft.Insights/components', replace(parameters('namingConvention').applicationInsights, parameters('serviceToken'), variables('service')))]", - "[resourceId('Microsoft.Web/serverfarms', replace(parameters('namingConvention').appServicePlan, parameters('serviceToken'), variables('service')))]", - "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id))]" + "[resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachineName'))]" ] }, { - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-04-01", - "name": "[replace(parameters('namingConvention').functionAppPrivateEndpoint, parameters('serviceToken'), variables('service'))]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2019-07-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), 'JsonADDomainExtension')]", "location": "[parameters('location')]", + "tags": "[variables('tagsVirtualMachines')]", "properties": { - "customNetworkInterfaceName": "[replace(parameters('namingConvention').functionAppNetworkInterface, parameters('serviceToken'), variables('service'))]", - "privateLinkServiceConnections": [ - { - "name": "[replace(parameters('namingConvention').functionAppPrivateEndpoint, parameters('serviceToken'), variables('service'))]", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Web/sites', uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id))]", - "groupIds": [ - "sites" - ] - } - } - ], - "subnet": { - "id": "[parameters('subnetResourceId')]" + "forceUpdateTag": "[parameters('timestamp')]", + "publisher": "Microsoft.Compute", + "type": "JsonADDomainExtension", + "typeHandlerVersion": "1.3", + "autoUpgradeMinorVersion": true, + "settings": { + "Name": "[parameters('domainName')]", + "Options": "3", + "OUPath": "[parameters('organizationalUnitPath')]", + "Restart": "true", + "User": "[parameters('domainJoinUserPrincipalName')]" + }, + "protectedSettings": { + "Password": "[parameters('domainJoinPassword')]" } }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites', uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id))]" + "[resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachineName'))]" ] }, { - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2021-08-01", - "name": "[format('{0}/{1}', replace(parameters('namingConvention').functionAppPrivateEndpoint, parameters('serviceToken'), variables('service')), 'default')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Compute/virtualMachines/{0}', parameters('virtualMachineName'))]", + "name": "[guid(parameters('deploymentUserAssignedIdentityPrincipalId'), 'a959dbd1-f747-45e3-8ba6-dd80f235f97c', resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachineName')))]", "properties": { - "privateDnsZoneConfigs": [ - { - "name": "ipconfig1", - "properties": { - "privateDnsZoneId": "[format('{0}{1}', parameters('privateDnsZoneResourceIdPrefix'), filter(parameters('privateDnsZones'), lambda('name', contains(lambdaVariables('name'), variables('functionAppKeyword'))))[0])]" - } - } - ] + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'a959dbd1-f747-45e3-8ba6-dd80f235f97c')]", + "principalId": "[parameters('deploymentUserAssignedIdentityPrincipalId')]", + "principalType": "ServicePrincipal" }, "dependsOn": [ - "[resourceId('Microsoft.Network/privateEndpoints', replace(parameters('namingConvention').functionAppPrivateEndpoint, parameters('serviceToken'), variables('service')))]" + "[resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachineName'))]" ] + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[parameters('virtualMachineName')]" + }, + "resourceId": { + "type": "string", + "value": "[resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachineName'))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix')))]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('deploy-vdag-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "deploymentNameSuffix": { + "value": "[parameters('deploymentNameSuffix')]" + }, + "deploymentUserAssignedIdentityClientId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.clientId.value]" + }, + "desktopApplicationGroupName": { + "value": "[parameters('namingConvention').applicationGroup]" + }, + "hostPoolResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" + }, + "locationControlPlane": { + "value": "[parameters('locationControlPlane')]" + }, + "locationVirtualMachines": { + "value": "[parameters('locationVirtualMachines')]" + }, + "mlzTags": { + "value": "[parameters('mlzTags')]" + }, + "securityPrincipalObjectIds": { + "value": "[parameters('securityPrincipalObjectIds')]" + }, + "desktopFriendlyName": { + "value": "[parameters('desktopFriendlyName')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "virtualMachineName": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-mgmt-vm-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.92.45157", + "templateHash": "15585962471976115155" + } + }, + "parameters": { + "deploymentNameSuffix": { + "type": "string" + }, + "deploymentUserAssignedIdentityClientId": { + "type": "string" + }, + "desktopApplicationGroupName": { + "type": "string" + }, + "desktopFriendlyName": { + "type": "string" + }, + "hostPoolResourceId": { + "type": "string" + }, + "locationControlPlane": { + "type": "string" + }, + "locationVirtualMachines": { + "type": "string" + }, + "mlzTags": { + "type": "object" }, + "securityPrincipalObjectIds": { + "type": "array" + }, + "tags": { + "type": "object" + }, + "virtualMachineName": { + "type": "string" + } + }, + "variables": { + "$fxv#0": "Param(\r\n [string]$ApplicationGroupName,\r\n [string]$FriendlyName,\r\n [string]$ResourceGroupName,\r\n [string]$ResourceManagerUri,\r\n [string]$SubscriptionId,\r\n [string]$UserAssignedIdentityClientId\r\n)\r\n\r\n$ErrorActionPreference = 'Stop'\r\n$WarningPreference = 'SilentlyContinue'\r\n\r\n# Wait for role assignment propagation\r\nStart-Sleep -Seconds 30\r\n\r\n# Fix the resource manager URI since only AzureCloud contains a trailing slash\r\n$ResourceManagerUriFixed = if ($ResourceManagerUri[-1] -eq '/') {$ResourceManagerUri} else {$ResourceManagerUri + '/'}\r\n\r\n# Get an access token for Azure resources\r\n$AzureManagementAccessToken = (Invoke-RestMethod `\r\n -Headers @{Metadata=\"true\"} `\r\n -Uri $('http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' + $ResourceManagerUriFixed + '&client_id=' + $UserAssignedIdentityClientId)).access_token\r\n\r\n# Set header for Azure Management API\r\n$AzureManagementHeader = @{\r\n 'Content-Type'='application/json'\r\n 'Authorization'='Bearer ' + $AzureManagementAccessToken\r\n}\r\n\r\n# Update the friendly name on the session desktop\r\nInvoke-RestMethod `\r\n -Body (@{properties = @{friendlyName = $FriendlyName.Replace('\"', '')}} | ConvertTo-Json) `\r\n -Headers $AzureManagementHeader `\r\n -Method 'PATCH' `\r\n -Uri $($ResourceManagerUriFixed + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $ResourceGroupName + '/providers/Microsoft.DesktopVirtualization/applicationGroups/' + $ApplicationGroupName + '/desktops/SessionDesktop?api-version=2022-02-10-preview') | Out-Null" + }, + "resources": [ { - "condition": "[parameters('enableApplicationInsights')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('deploy-private-link-scope-appi-{0}', parameters('deploymentNameSuffix'))]", - "subscriptionId": "[split(parameters('privateLinkScopeResourceId'), '/')[2]]", - "resourceGroup": "[split(parameters('privateLinkScopeResourceId'), '/')[4]]", + "type": "Microsoft.DesktopVirtualization/applicationGroups", + "apiVersion": "2023-09-05", + "name": "[parameters('desktopApplicationGroupName')]", + "location": "[parameters('locationControlPlane')]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.DesktopVirtualization/applicationGroups'), createObject()), parameters('mlzTags'))]", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "applicationInsightsResourceId": { - "value": "[resourceId('Microsoft.Insights/components', replace(parameters('namingConvention').applicationInsights, parameters('serviceToken'), variables('service')))]" - }, - "privateLinkScopeResourceId": { - "value": "[parameters('privateLinkScopeResourceId')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "4414484880639231715" - } - }, - "parameters": { - "applicationInsightsResourceId": { - "type": "string", - "defaultValue": "" - }, - "dataCollectionEndpointResourceId": { - "type": "string", - "defaultValue": "" - }, - "logAnalyticsWorkspaceResourceId": { - "type": "string", - "defaultValue": "" - }, - "privateLinkScopeResourceId": { - "type": "string" - } - }, - "resources": [ - { - "condition": "[not(empty(parameters('applicationInsightsResourceId')))]", - "type": "Microsoft.Insights/privateLinkScopes/scopedResources", - "apiVersion": "2021-09-01", - "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('applicationInsightsResourceId')), 'applicationInsights', split(parameters('applicationInsightsResourceId'), '/')[8]))]", - "properties": { - "linkedResourceId": "[parameters('applicationInsightsResourceId')]" - } - }, - { - "condition": "[not(empty(parameters('dataCollectionEndpointResourceId')))]", - "type": "Microsoft.Insights/privateLinkScopes/scopedResources", - "apiVersion": "2021-09-01", - "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('dataCollectionEndpointResourceId')), 'dataCollectionEndpoint', split(parameters('dataCollectionEndpointResourceId'), '/')[8]))]", - "properties": { - "linkedResourceId": "[parameters('dataCollectionEndpointResourceId')]" - } - }, - { - "condition": "[not(empty(parameters('logAnalyticsWorkspaceResourceId')))]", - "type": "Microsoft.Insights/privateLinkScopes/scopedResources", - "apiVersion": "2021-09-01", - "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('logAnalyticsWorkspaceResourceId')), 'logAnalyticsWorkspace', split(parameters('logAnalyticsWorkspaceResourceId'), '/')[8]))]", - "properties": { - "linkedResourceId": "[parameters('logAnalyticsWorkspaceResourceId')]" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Insights/components', replace(parameters('namingConvention').applicationInsights, parameters('serviceToken'), variables('service')))]" - ] + "hostPoolArmPath": "[parameters('hostPoolResourceId')]", + "applicationGroupType": "Desktop" + } }, { "copy": { - "name": "roleAssignments_resourceGroups", - "count": "[length(range(0, length(variables('roleAssignments'))))]" + "name": "roleAssignment_ApplicationGroup", + "count": "[length(range(0, length(parameters('securityPrincipalObjectIds'))))]" }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('set-role-assignment-{0}-{1}', range(0, length(variables('roleAssignments')))[copyIndex()], parameters('deploymentNameSuffix'))]", - "resourceGroup": "[variables('roleAssignments')[range(0, length(variables('roleAssignments')))[copyIndex()]].scope]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DesktopVirtualization/applicationGroups/{0}', parameters('desktopApplicationGroupName'))]", + "name": "[guid(parameters('securityPrincipalObjectIds')[range(0, length(parameters('securityPrincipalObjectIds')))[copyIndex()]], '1d18fff3-a72a-46b5-b4a9-0b38a3cd7e63', resourceId('Microsoft.DesktopVirtualization/applicationGroups', parameters('desktopApplicationGroupName')))]", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "principalId": { - "value": "[reference(resourceId('Microsoft.Web/sites', uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id)), '2023-01-01', 'full').identity.principalId]" - }, - "principalType": { - "value": "ServicePrincipal" - }, - "roleDefinitionId": { - "value": "[variables('roleAssignments')[range(0, length(variables('roleAssignments')))[copyIndex()]].roleDefinitionId]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "15359000832124286075" - } - }, - "parameters": { - "principalId": { - "type": "string" - }, - "principalType": { - "type": "string" - }, - "roleDefinitionId": { - "type": "string" - } - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(parameters('principalId'), parameters('roleDefinitionId'), resourceGroup().id)]", - "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionId'))]", - "principalId": "[parameters('principalId')]", - "principalType": "[parameters('principalType')]" - } - } - ] - } + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '1d18fff3-a72a-46b5-b4a9-0b38a3cd7e63')]", + "principalId": "[parameters('securityPrincipalObjectIds')[range(0, length(parameters('securityPrincipalObjectIds')))[copyIndex()]]]" }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites', uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id))]" + "[resourceId('Microsoft.DesktopVirtualization/applicationGroups', parameters('desktopApplicationGroupName'))]" ] }, { + "condition": "[not(empty(parameters('desktopFriendlyName')))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('set-role-assignment-storage-{0}', parameters('deploymentNameSuffix'))]", + "name": "[format('deploy-vdapp-friendly-name-{0}', parameters('deploymentNameSuffix'))]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "principalId": { - "value": "[reference(resourceId('Microsoft.Web/sites', uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id)), '2023-01-01', 'full').identity.principalId]" + "location": { + "value": "[parameters('locationVirtualMachines')]" }, - "principalType": { - "value": "ServicePrincipal" + "name": { + "value": "Update-AvdDesktop" }, - "roleDefinitionId": { - "value": "b7e6dc6d-f1e8-4753-8033-0f276bb0955b" + "parameters": { + "value": [ + { + "name": "ApplicationGroupName", + "value": "[parameters('desktopApplicationGroupName')]" + }, + { + "name": "FriendlyName", + "value": "[parameters('desktopFriendlyName')]" + }, + { + "name": "ResourceGroupName", + "value": "[resourceGroup().name]" + }, + { + "name": "ResourceManagerUri", + "value": "[environment().resourceManager]" + }, + { + "name": "SubscriptionId", + "value": "[subscription().subscriptionId]" + }, + { + "name": "UserAssignedIdentityClientId", + "value": "[parameters('deploymentUserAssignedIdentityClientId')]" + } + ] }, - "storageAccountName": { - "value": "[uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id)]" + "script": { + "value": "[variables('$fxv#0')]" + }, + "tags": { + "value": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Compute/virtualMachines'), createObject()), parameters('mlzTags'))]" + }, + "virtualMachineName": { + "value": "[parameters('virtualMachineName')]" } }, "template": { @@ -8633,128 +7743,84 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "4556566909157652727" + "version": "0.31.92.45157", + "templateHash": "6896852651895359166" } }, "parameters": { - "principalId": { - "type": "string" + "asyncExecution": { + "type": "bool", + "defaultValue": false }, - "principalType": { + "location": { "type": "string" }, - "roleDefinitionId": { + "name": { "type": "string" }, - "storageAccountName": { - "type": "string" - } - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", - "name": "[guid(parameters('principalId'), parameters('roleDefinitionId'), resourceGroup().id)]", - "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionId'))]", - "principalId": "[parameters('principalId')]", - "principalType": "[parameters('principalType')]" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites', uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id))]", - "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('deploy-scm-a-record-{0}', parameters('deploymentNameSuffix'))]", - "subscriptionId": "[split(variables('functionAppScmPrivateDnsZoneResourceId'), '/')[2]]", - "resourceGroup": "[split(variables('functionAppScmPrivateDnsZoneResourceId'), '/')[4]]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "functionAppName": { - "value": "[uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id)]" - }, - "ipv4Address": { - "value": "[filter(reference(resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', replace(parameters('namingConvention').functionAppPrivateEndpoint, parameters('serviceToken'), variables('service')), 'default'), '2021-08-01').privateDnsZoneConfigs[0].properties.recordSets, lambda('record', equals(lambdaVariables('record').recordSetName, uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id))))[0].ipAddresses[0]]" - }, - "privateDnsZoneName": { - "value": "[split(variables('functionAppScmPrivateDnsZoneResourceId'), '/')[8]]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10076124156656611378" - } - }, - "parameters": { - "functionAppName": { - "type": "string" + "parameters": { + "type": "array", + "defaultValue": [] }, - "ipv4Address": { + "script": { "type": "string" }, - "privateDnsZoneName": { + "tags": { + "type": "object" + }, + "treatFailureAsDeploymentFailure": { + "type": "bool", + "defaultValue": true + }, + "virtualMachineName": { "type": "string" } }, "resources": [ { - "type": "Microsoft.Network/privateDnsZones/A", - "apiVersion": "2020-06-01", - "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('functionAppName'))]", + "type": "Microsoft.Compute/virtualMachines/runCommands", + "apiVersion": "2023-09-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", "properties": { - "aRecords": [ - { - "ipv4Address": "[parameters('ipv4Address')]" - } - ], - "ttl": 3600 + "asyncExecution": "[parameters('asyncExecution')]", + "parameters": "[parameters('parameters')]", + "source": { + "script": "[parameters('script')]" + }, + "treatFailureAsDeploymentFailure": "[parameters('treatFailureAsDeploymentFailure')]" } } ] } }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites', uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id))]", - "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', replace(parameters('namingConvention').functionAppPrivateEndpoint, parameters('serviceToken'), variables('service')), 'default')]" + "[resourceId('Microsoft.DesktopVirtualization/applicationGroups', parameters('desktopApplicationGroupName'))]" ] } ], "outputs": { - "functionAppName": { + "resourceId": { "type": "string", - "value": "[uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id)]" + "value": "[resourceId('Microsoft.DesktopVirtualization/applicationGroups', parameters('desktopApplicationGroupName'))]" } } } }, "dependsOn": [ - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-monitoring-{0}', parameters('deploymentNameSuffix')))]" + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix')))]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-mgmt-vm-{0}', parameters('deploymentNameSuffix')))]" ] }, { - "condition": "[and(parameters('recoveryServices'), or(and(and(contains(parameters('activeDirectorySolution'), 'DomainServices'), contains(parameters('hostPoolType'), 'Pooled')), contains(parameters('fslogixStorageService'), 'AzureFiles')), contains(parameters('hostPoolType'), 'Personal')))]", + "condition": "[parameters('recoveryServices')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "[format('deploy-rsv-{0}', parameters('deploymentNameSuffix'))]", - "resourceGroup": "[parameters('resourceGroupManagement')]", + "resourceGroup": "[parameters('resourceGroupName')]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -8770,8 +7836,8 @@ "deployFslogix": { "value": "[parameters('deployFslogix')]" }, - "hostPoolName": { - "value": "[variables('hostPoolName')]" + "hostPoolResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" }, "location": { "value": "[parameters('locationVirtualMachines')]" @@ -8791,9 +7857,6 @@ "recoveryServicesVaultPrivateEndpointName": { "value": "[parameters('namingConvention').recoveryServicesVaultPrivateEndpoint]" }, - "resourceGroupControlPlane": { - "value": "[parameters('resourceGroupControlPlane')]" - }, "storageService": { "value": "[parameters('storageService')]" }, @@ -8813,8 +7876,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "18245536038700947918" + "version": "0.31.92.45157", + "templateHash": "15719775133926232352" } }, "parameters": { @@ -8827,7 +7890,7 @@ "deployFslogix": { "type": "bool" }, - "hostPoolName": { + "hostPoolResourceId": { "type": "string" }, "location": { @@ -8848,9 +7911,6 @@ "recoveryServicesVaultPrivateEndpointName": { "type": "string" }, - "resourceGroupControlPlane": { - "type": "string" - }, "storageService": { "type": "string" }, @@ -8870,7 +7930,7 @@ "apiVersion": "2022-03-01", "name": "[parameters('recoveryServicesVaultName')]", "location": "[parameters('location')]", - "tags": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.RecoveryServices/vaults'), parameters('tags')['Microsoft.RecoveryServices/vaults'], createObject()), parameters('mlzTags'))]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.RecoveryServices/vaults'), createObject()), parameters('mlzTags'))]", "sku": { "name": "RS0", "tier": "Standard" @@ -8883,7 +7943,7 @@ "apiVersion": "2022-03-01", "name": "[format('{0}/{1}', parameters('recoveryServicesVaultName'), 'AvdPolicyStorage')]", "location": "[parameters('location')]", - "tags": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.RecoveryServices/vaults'), parameters('tags')['Microsoft.RecoveryServices/vaults'], createObject()), parameters('mlzTags'))]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.RecoveryServices/vaults'), createObject()), parameters('mlzTags'))]", "properties": { "backupManagementType": "AzureStorage", "schedulePolicy": { @@ -8918,7 +7978,7 @@ "apiVersion": "2022-03-01", "name": "[format('{0}/{1}', parameters('recoveryServicesVaultName'), 'AvdPolicyVm')]", "location": "[parameters('location')]", - "tags": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.RecoveryServices/vaults'), parameters('tags')['Microsoft.RecoveryServices/vaults'], createObject()), parameters('mlzTags'))]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.RecoveryServices/vaults'), createObject()), parameters('mlzTags'))]", "properties": { "backupManagementType": "AzureIaasVM", "instantRpRetentionRangeInDays": 2, @@ -8955,7 +8015,7 @@ "apiVersion": "2023-04-01", "name": "[parameters('recoveryServicesVaultPrivateEndpointName')]", "location": "[parameters('location')]", - "tags": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.Network/privateEndpoints'), parameters('tags')['Microsoft.Network/privateEndpoints'], createObject()), parameters('mlzTags'))]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Network/privateEndpoints'), createObject()), parameters('mlzTags'))]", "properties": { "customNetworkInterfaceName": "[parameters('recoveryServicesVaultNetworkInterfaceName')]", "privateLinkServiceConnections": [ @@ -8965,429 +8025,122 @@ "privateLinkServiceId": "[resourceId('Microsoft.RecoveryServices/vaults', parameters('recoveryServicesVaultName'))]", "groupIds": [ "AzureBackup" - ] - } - } - ], - "subnet": { - "id": "[parameters('subnetId')]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.RecoveryServices/vaults', parameters('recoveryServicesVaultName'))]" - ] - }, - { - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2021-08-01", - "name": "[format('{0}/{1}', parameters('recoveryServicesVaultPrivateEndpointName'), parameters('recoveryServicesVaultName'))]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[replace(parameters('recoveryServicesPrivateDnsZoneResourceId'), '.', '-')]", - "properties": { - "privateDnsZoneId": "[parameters('recoveryServicesPrivateDnsZoneResourceId')]" - } - }, - { - "name": "[replace(parameters('azureQueueStoragePrivateDnsZoneResourceId'), '.', '-')]", - "properties": { - "privateDnsZoneId": "[parameters('azureQueueStoragePrivateDnsZoneResourceId')]" - } - }, - { - "name": "[replace(parameters('azureBlobsPrivateDnsZoneResourceId'), '.', '-')]", - "properties": { - "privateDnsZoneId": "[parameters('azureBlobsPrivateDnsZoneResourceId')]" + ] } } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateEndpoints', parameters('recoveryServicesVaultPrivateEndpointName'))]", - "[resourceId('Microsoft.RecoveryServices/vaults', parameters('recoveryServicesVaultName'))]" - ] - } - ], - "outputs": { - "name": { - "type": "string", - "value": "[parameters('recoveryServicesVaultName')]" - } - } - } - } - } - ], - "outputs": { - "dataCollectionRuleResourceId": { - "type": "string", - "value": "[if(parameters('enableAvdInsights'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-monitoring-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.dataCollectionRuleResourceId.value, '')]" - }, - "deploymentUserAssignedIdentityClientId": { - "type": "string", - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.clientId.value]" - }, - "deploymentUserAssignedIdentityPrincipalId": { - "type": "string", - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.principalId.value]" - }, - "deploymentUserAssignedIdentityResourceId": { - "type": "string", - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" - }, - "functionAppName": { - "type": "string", - "value": "[if(or(parameters('scalingTool'), equals(parameters('fslogixStorageService'), 'AzureFiles Premium')), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-function-app-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.functionAppName.value, '')]" - }, - "logAnalyticsWorkspaceName": { - "type": "string", - "value": "[if(or(parameters('enableApplicationInsights'), parameters('enableAvdInsights')), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-monitoring-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.logAnalyticsWorkspaceName.value, '')]" - }, - "logAnalyticsWorkspaceResourceId": { - "type": "string", - "value": "[if(or(parameters('enableApplicationInsights'), parameters('enableAvdInsights')), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-monitoring-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.logAnalyticsWorkspaceResourceId.value, '')]" - }, - "recoveryServicesVaultName": { - "type": "string", - "value": "[if(and(parameters('recoveryServices'), or(and(and(contains(parameters('activeDirectorySolution'), 'DomainServices'), contains(parameters('hostPoolType'), 'Pooled')), contains(parameters('fslogixStorageService'), 'AzureFiles')), contains(parameters('hostPoolType'), 'Personal'))), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-rsv-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value, '')]" - }, - "virtualMachineName": { - "type": "string", - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-mgmt-vm-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" - }, - "virtualMachineResourceId": { - "type": "string", - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-mgmt-vm-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" - } - } - } - }, - "dependsOn": [ - "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[1], parameters('deploymentNameSuffix')))]", - "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[0], parameters('deploymentNameSuffix')))]", - "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[2], parameters('deploymentNameSuffix')))]", - "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix')))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('deploy-control-plane-{0}', parameters('deploymentNameSuffix'))]", - "location": "[deployment().location]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "activeDirectorySolution": { - "value": "[parameters('activeDirectorySolution')]" - }, - "avdPrivateDnsZoneResourceId": { - "value": "[format('{0}{1}', variables('privateDnsZoneResourceIdPrefix'), filter(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.privateDnsZones.value, lambda('name', startsWith(lambdaVariables('name'), 'privatelink.wvd')))[0])]" - }, - "customImageId": { - "value": "[variables('customImageId')]" - }, - "customRdpProperty": { - "value": "[parameters('customRdpProperty')]" - }, - "deploymentNameSuffix": { - "value": "[parameters('deploymentNameSuffix')]" - }, - "deploymentUserAssignedIdentityClientId": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.deploymentUserAssignedIdentityClientId.value]" - }, - "desktopFriendlyName": "[if(empty(parameters('desktopFriendlyName')), createObject('value', string(parameters('stampIndex'))), createObject('value', parameters('desktopFriendlyName')))]", - "diskSku": { - "value": "[parameters('diskSku')]" - }, - "domainName": { - "value": "[parameters('domainName')]" - }, - "enableAvdInsights": { - "value": "[parameters('enableAvdInsights')]" - }, - "hostPoolPublicNetworkAccess": { - "value": "[parameters('hostPoolPublicNetworkAccess')]" - }, - "hostPoolType": { - "value": "[parameters('hostPoolType')]" - }, - "imageOffer": { - "value": "[parameters('imageOffer')]" - }, - "imagePublisher": { - "value": "[parameters('imagePublisher')]" - }, - "imageSku": { - "value": "[parameters('imageSku')]" - }, - "imageVersionResourceId": { - "value": "[parameters('imageVersionResourceId')]" - }, - "locationControlPlane": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Network/virtualNetworks', split(parameters('hubVirtualNetworkResourceId'), '/')[8]), '2023-11-01', 'full').location]" - }, - "locationVirtualMachines": { - "value": "[parameters('locationVirtualMachines')]" - }, - "logAnalyticsWorkspaceResourceId": "[if(parameters('enableAvdInsights'), createObject('value', reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.logAnalyticsWorkspaceResourceId.value), createObject('value', ''))]", - "managementVirtualMachineName": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.virtualMachineName.value]" - }, - "maxSessionLimit": { - "value": "[mul(parameters('usersPerCore'), parameters('virtualMachineVirtualCpuCount'))]" - }, - "mlzTags": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.mlzTags.value]" - }, - "namingConvention": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value]" - }, - "resourceGroupControlPlane": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[0], parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" - }, - "resourceGroupManagement": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[2], parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" - }, - "roleDefinitions": { - "value": "[variables('roleDefinitions')]" - }, - "securityPrincipalObjectIds": { - "value": "[map(parameters('securityPrincipals'), lambda('item', lambdaVariables('item').objectId))]" - }, - "serviceToken": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service]" - }, - "sessionHostNamePrefix": { - "value": "[replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.namingConvention.value.virtualMachine, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, '')]" - }, - "subnetResourceId": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.subnets.value[1].id]" - }, - "tags": { - "value": "[parameters('tags')]" - }, - "validationEnvironment": { - "value": "[parameters('validationEnvironment')]" - }, - "virtualMachineSize": { - "value": "[parameters('virtualMachineSize')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8101049597382289461" - } - }, - "parameters": { - "activeDirectorySolution": { - "type": "string" - }, - "avdPrivateDnsZoneResourceId": { - "type": "string" - }, - "customImageId": { - "type": "string" - }, - "customRdpProperty": { - "type": "string" - }, - "deploymentNameSuffix": { - "type": "string" - }, - "deploymentUserAssignedIdentityClientId": { - "type": "string" - }, - "desktopFriendlyName": { - "type": "string" - }, - "diskSku": { - "type": "string" - }, - "domainName": { - "type": "string" - }, - "enableAvdInsights": { - "type": "bool" - }, - "hostPoolPublicNetworkAccess": { - "type": "string" - }, - "hostPoolType": { - "type": "string" - }, - "imageOffer": { - "type": "string" - }, - "imagePublisher": { - "type": "string" - }, - "imageSku": { - "type": "string" - }, - "imageVersionResourceId": { - "type": "string" - }, - "locationControlPlane": { - "type": "string" - }, - "locationVirtualMachines": { - "type": "string" - }, - "logAnalyticsWorkspaceResourceId": { - "type": "string" - }, - "managementVirtualMachineName": { - "type": "string" - }, - "maxSessionLimit": { - "type": "int" - }, - "mlzTags": { - "type": "object" - }, - "namingConvention": { - "type": "object" - }, - "resourceGroupControlPlane": { - "type": "string" - }, - "resourceGroupManagement": { - "type": "string" - }, - "roleDefinitions": { - "type": "object" - }, - "securityPrincipalObjectIds": { - "type": "array" - }, - "serviceToken": { - "type": "string" - }, - "sessionHostNamePrefix": { - "type": "string" - }, - "subnetResourceId": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "validationEnvironment": { - "type": "bool" + ], + "subnet": { + "id": "[parameters('subnetId')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.RecoveryServices/vaults', parameters('recoveryServicesVaultName'))]" + ] + }, + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2021-08-01", + "name": "[format('{0}/{1}', parameters('recoveryServicesVaultPrivateEndpointName'), parameters('recoveryServicesVaultName'))]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[replace(parameters('recoveryServicesPrivateDnsZoneResourceId'), '.', '-')]", + "properties": { + "privateDnsZoneId": "[parameters('recoveryServicesPrivateDnsZoneResourceId')]" + } + }, + { + "name": "[replace(parameters('azureQueueStoragePrivateDnsZoneResourceId'), '.', '-')]", + "properties": { + "privateDnsZoneId": "[parameters('azureQueueStoragePrivateDnsZoneResourceId')]" + } + }, + { + "name": "[replace(parameters('azureBlobsPrivateDnsZoneResourceId'), '.', '-')]", + "properties": { + "privateDnsZoneId": "[parameters('azureBlobsPrivateDnsZoneResourceId')]" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', parameters('recoveryServicesVaultPrivateEndpointName'))]", + "[resourceId('Microsoft.RecoveryServices/vaults', parameters('recoveryServicesVaultName'))]" + ] + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[parameters('recoveryServicesVaultName')]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix')))]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" + ] }, - "virtualMachineSize": { - "type": "string" - } - }, - "variables": { - "galleryImageOffer": "[if(empty(parameters('imageVersionResourceId')), format('\"{0}\"', parameters('imageOffer')), 'null')]", - "galleryImagePublisher": "[if(empty(parameters('imageVersionResourceId')), format('\"{0}\"', parameters('imagePublisher')), 'null')]", - "galleryImageSku": "[if(empty(parameters('imageVersionResourceId')), format('\"{0}\"', parameters('imageSku')), 'null')]", - "galleryItemId": "[if(empty(parameters('imageVersionResourceId')), format('\"{0}.{1}{2}\"', parameters('imagePublisher'), parameters('imageOffer'), parameters('imageSku')), 'null')]", - "hostPoolName": "[parameters('namingConvention').hostPool]", - "imageType": "[if(empty(parameters('imageVersionResourceId')), '\"Gallery\"', '\"CustomImage\"')]" - }, - "resources": [ { + "condition": "[equals(parameters('fslogixStorageService'), 'AzureFiles Premium')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('deploy-vdpool-{0}', parameters('deploymentNameSuffix'))]", - "resourceGroup": "[parameters('resourceGroupControlPlane')]", + "name": "[format('deploy-function-app-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[parameters('resourceGroupName')]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "activeDirectorySolution": { - "value": "[parameters('activeDirectorySolution')]" - }, - "avdPrivateDnsZoneResourceId": { - "value": "[parameters('avdPrivateDnsZoneResourceId')]" - }, - "customImageId": { - "value": "[parameters('customImageId')]" - }, - "customRdpProperty": { - "value": "[parameters('customRdpProperty')]" - }, - "diskSku": { - "value": "[parameters('diskSku')]" - }, - "domainName": { - "value": "[parameters('domainName')]" - }, - "enableAvdInsights": { - "value": "[parameters('enableAvdInsights')]" - }, - "galleryImageOffer": { - "value": "[variables('galleryImageOffer')]" - }, - "galleryImagePublisher": { - "value": "[variables('galleryImagePublisher')]" - }, - "galleryImageSku": { - "value": "[variables('galleryImageSku')]" + "delegatedSubnetResourceId": { + "value": "[filter(parameters('subnets'), lambda('subnet', contains(lambdaVariables('subnet').name, 'FunctionAppOutbound')))[0].id]" }, - "galleryItemId": { - "value": "[variables('galleryItemId')]" + "deploymentNameSuffix": { + "value": "[parameters('deploymentNameSuffix')]" }, - "hostPoolDiagnosticSettingName": { - "value": "[parameters('namingConvention').hostPoolDiagnosticSetting]" + "enableApplicationInsights": { + "value": "[parameters('enableApplicationInsights')]" }, - "hostPoolName": { - "value": "[variables('hostPoolName')]" + "environmentAbbreviation": { + "value": "[parameters('environmentAbbreviation')]" }, - "hostPoolNetworkInterfaceName": { - "value": "[parameters('namingConvention').hostPoolNetworkInterface]" + "hostPoolResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" }, - "hostPoolPrivateEndpointName": { - "value": "[parameters('namingConvention').hostPoolPrivateEndpoint]" + "logAnalyticsWorkspaceResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-monitoring-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.logAnalyticsWorkspaceResourceId.value]" }, - "hostPoolPublicNetworkAccess": { - "value": "[parameters('hostPoolPublicNetworkAccess')]" + "mlzTags": { + "value": "[parameters('mlzTags')]" }, - "hostPoolType": { - "value": "[parameters('hostPoolType')]" + "namingConvention": { + "value": "[parameters('namingConvention')]" }, - "imageType": { - "value": "[variables('imageType')]" + "privateDnsZoneResourceIdPrefix": { + "value": "[parameters('privateDnsZoneResourceIdPrefix')]" }, - "location": { - "value": "[parameters('locationControlPlane')]" + "privateDnsZones": { + "value": "[parameters('privateDnsZones')]" }, - "logAnalyticsWorkspaceResourceId": { - "value": "[parameters('logAnalyticsWorkspaceResourceId')]" + "privateLinkScopeResourceId": { + "value": "[parameters('privateLinkScopeResourceId')]" }, - "maxSessionLimit": { - "value": "[parameters('maxSessionLimit')]" + "resourceAbbreviations": { + "value": "[parameters('resourceAbbreviations')]" }, - "mlzTags": { - "value": "[parameters('mlzTags')]" + "resourceGroupProfiles": { + "value": "[parameters('resourceGroupProfiles')]" }, - "sessionHostNamePrefix": { - "value": "[parameters('sessionHostNamePrefix')]" + "serviceToken": { + "value": "[parameters('serviceToken')]" }, "subnetResourceId": { "value": "[parameters('subnetResourceId')]" }, "tags": { "value": "[parameters('tags')]" - }, - "validationEnvironment": { - "value": "[parameters('validationEnvironment')]" - }, - "virtualMachineSize": { - "value": "[parameters('virtualMachineSize')]" } }, "template": { @@ -9396,139 +8149,487 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "12277474104461216961" + "version": "0.31.92.45157", + "templateHash": "1960226860650229376" } }, "parameters": { - "activeDirectorySolution": { + "delegatedSubnetResourceId": { "type": "string" }, - "avdPrivateDnsZoneResourceId": { + "deploymentNameSuffix": { "type": "string" }, - "customImageId": { - "type": "string" + "enableApplicationInsights": { + "type": "bool" }, - "customRdpProperty": { + "environmentAbbreviation": { "type": "string" }, - "diskSku": { + "hostPoolResourceId": { "type": "string" }, - "domainName": { - "type": "string" + "keyExpirationInDays": { + "type": "int", + "defaultValue": 30 }, - "enableAvdInsights": { - "type": "bool" + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" }, - "galleryImageOffer": { + "logAnalyticsWorkspaceResourceId": { "type": "string" }, - "galleryImagePublisher": { - "type": "string" + "mlzTags": { + "type": "object" }, - "galleryImageSku": { - "type": "string" + "namingConvention": { + "type": "object" }, - "galleryItemId": { + "privateDnsZoneResourceIdPrefix": { "type": "string" }, - "hostPoolDiagnosticSettingName": { - "type": "string" + "privateDnsZones": { + "type": "array" }, - "hostPoolName": { + "privateLinkScopeResourceId": { "type": "string" }, - "hostPoolNetworkInterfaceName": { - "type": "string" + "resourceAbbreviations": { + "type": "object" }, - "hostPoolPrivateEndpointName": { + "resourceGroupProfiles": { "type": "string" }, - "hostPoolPublicNetworkAccess": { + "serviceToken": { "type": "string" }, - "hostPoolType": { + "subnetResourceId": { "type": "string" }, - "imageType": { - "type": "string" + "tags": { + "type": "object" + } + }, + "variables": { + "$fxv#0": "# This file enables modules to be automatically managed by the Functions service.\r\n# See https://aka.ms/functionsmanageddependency for additional information.\r\n#\r\n@{\r\n # For latest supported version, go to 'https://www.powershellgallery.com/packages/Az'. \r\n # To use the Az module in your function app, please uncomment the line below.\r\n # 'Az' = '7.*'\r\n}", + "$fxv#1": "param($Timer)\r\n\r\ntry\r\n{\r\n\t[string]$FileShareName = $env:FileShareName\r\n\t[string]$ResourceGroupName = $env:ResourceGroupName\r\n\t[string]$ResourceManagerUrl = $env:ResourceManagerUrl\r\n\t[string]$StorageSuffix = $env:StorageSuffix\r\n\t[string]$SubscriptionId = $env:SubscriptionId\r\n\r\n\t$ErrorActionPreference = 'Stop'\r\n\t$WarningPreference = 'SilentlyContinue'\r\n\r\n\t#region Functions\r\n\tfunction Write-Log \r\n {\r\n\t\t[CmdletBinding()]\r\n\t\tparam (\r\n\t\t\t[Parameter(Mandatory = $false)]\r\n\t\t\t[switch]$Err,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$Message,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$ResourceName,\r\n\r\n\t\t\t[Parameter(Mandatory = $false)]\r\n\t\t\t[switch]$Warn\r\n\t\t)\r\n\r\n\t\t[string]$MessageTimeStamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'\r\n\t\t$Message = \"[$($MyInvocation.ScriptLineNumber)] [$($ResourceName)] $Message\"\r\n\t\t[string]$WriteMessage = \"[$($MessageTimeStamp)] $Message\"\r\n\r\n\t\tif ($Err)\r\n {\r\n\t\t\tWrite-Error $WriteMessage\r\n\t\t\t$Message = \"ERROR: $Message\"\r\n\t\t}\r\n\t\telseif ($Warn)\r\n {\r\n\t\t\tWrite-Warning $WriteMessage\r\n\t\t\t$Message = \"WARN: $Message\"\r\n\t\t}\r\n\t\telse \r\n {\r\n\t\t\tWrite-Output $WriteMessage\r\n\t\t}\r\n\t}\r\n\t#endregion Functions\r\n\r\n\t# Note: https://stackoverflow.com/questions/41674518/powershell-setting-security-protocol-to-tls-1-2\r\n\t[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\r\n\r\n\r\n\t#region Azure Authentication\r\n $AccessToken = $null\r\n try\r\n {\r\n\t\t$TokenAuthURI = $env:IDENTITY_ENDPOINT + '?resource=' + $ResourceManagerUrl + '&api-version=2019-08-01'\r\n\t\t$TokenResponse = Invoke-RestMethod -Method Get -Headers @{\"X-IDENTITY-HEADER\"=\"$env:IDENTITY_HEADER\"} -Uri $TokenAuthURI\r\n\t\t$AccessToken = $TokenResponse.access_token\r\n\t\t$Header = @{\r\n\t\t\t'Content-Type'='application/json'\r\n\t\t\t'Authorization'='Bearer ' + $AccessToken\r\n\t\t}\r\n }\r\n catch\r\n {\r\n throw [System.Exception]::new('Failed to authenticate Azure with application ID, tenant ID, subscription ID', $PSItem.Exception)\r\n }\r\n Write-Log -ResourceName \"$SubscriptionId\" -Message \"Successfully authenticated with Azure using a managed identity\"\r\n\t#endregion Azure Authentication\r\n\r\n\t# Get storage accounts\r\n\t$Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $ResourceGroupName + '/providers/Microsoft.Storage/storageAccounts?api-version=2023-05-01'\r\n\t$StorageAccountNames = (Invoke-RestMethod -Headers $Header -Method 'GET' -Uri $Uri).value.name\r\n\r\n\tforeach($StorageAccountName in $StorageAccountNames)\r\n\t{\r\n\t\t$ShareUpdateUri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $ResourceGroupName + '/providers/Microsoft.Storage/storageAccounts/' + $StorageAccountName + '/fileServices/default/shares/' + $FileShareName + '?api-version=2023-05-01'\r\n\r\n\t\t# Get file share info\r\n\t\t$ShareGetUri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $ResourceGroupName + '/providers/Microsoft.Storage/storageAccounts/' + $StorageAccountName + '/fileServices/default/shares/' + $FileShareName + '?api-version=2023-05-01&$expand=stats'\r\n\t\t$PFS = (Invoke-RestMethod -Headers $Header -Method 'GET' -Uri $ShareGetUri).properties\r\n\r\n\t\t# Set variables for provisioned capacity and used capacity\r\n\t\t$ProvisionedCapacity = $PFS.shareQuota\r\n\t\t$UsedCapacity = $PFS.ShareUsageBytes\r\n\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"Share Capacity: $($ProvisionedCapacity)GB\"\r\n\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"Share Usage: $([math]::Round($UsedCapacity/1GB, 0))GB\"\r\n\r\n\t\t# GB Based Scaling\r\n\t\t# No scaling if no usage\r\n\t\tif($UsedCapacity -eq 0)\r\n\t\t{\r\n\t\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"Share Usage is 0GB. No Changes.\"\r\n\t\t}\r\n\t\t# Slow scaling up to 500GB\r\n\t\t# Increases share quota by 100GB if less than 50GB remains on the share\r\n\t\t# This allows time for an AVD Stamp to be rolled out \r\n\t\telseif ($ProvisionedCapacity -lt 500)\r\n\t\t{\r\n\t\t\tif (($ProvisionedCapacity - ($UsedCapacity / ([Math]::Pow(2,30)))) -lt 50) {\r\n\t\t\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"Share Usage has surpassed the Share Quota remaining threshold of 50GB. Increasing the file share quota by 100GB.\" \r\n\t\t\t\t$Quota = $ProvisionedCapacity + 100\r\n\t\t\t\tInvoke-RestMethod `\r\n\t\t\t\t\t-Body (@{properties = @{shareQuota = $Quota}} | ConvertTo-Json) `\r\n\t\t\t\t\t-Headers $Header `\r\n\t\t\t\t\t-Method 'PATCH' `\r\n\t\t\t\t\t-Uri $ShareUpdateUri | Out-Null\r\n\t\t\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"New Capacity: $($Quota)GB\"\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"Share Usage is below Share Quota remaining threshold of 50GB. No Changes.\"\r\n\t\t\t}\r\n\t\t}\r\n\t\t# Aggressive scaling\r\n\t\t# Increases share quota by 500GB if less than 500GB remains on the share\r\n\t\t# This ensures plenty of space is available during mass onboarding\r\n\t\telse \r\n\t\t{\r\n\t\t\tif (($ProvisionedCapacity - ($UsedCapacity / ([Math]::Pow(2,30)))) -lt 500) {\r\n\t\t\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"Share Usage has surpassed the Share Quota remaining threshold of 500GB. Increasing the file share quota by 500GB.\" \r\n\t\t\t\t$Quota = $ProvisionedCapacity + 500\r\n\t\t\t\tInvoke-RestMethod `\r\n\t\t\t\t\t-Body (@{properties = @{shareQuota = $Quota}} | ConvertTo-Json) `\r\n\t\t\t\t\t-Headers $Header `\r\n\t\t\t\t\t-Method 'PATCH' `\r\n\t\t\t\t\t-Uri $ShareUpdateUri | Out-Null\r\n\t\t\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"New Capacity: $($Quota)GB\"\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"Share Usage is below Share Quota remaining threshold of 500GB. No Changes.\"\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\ncatch \r\n{\r\n\t$ErrContainer = $PSItem\r\n\t# $ErrContainer = $_\r\n\r\n\t[string]$ErrMsg = $ErrContainer | Format-List -Force | Out-String\r\n\t$ErrMsg += \"Version: $Version`n\"\r\n\r\n\tif (Get-Command 'Write-Log' -ErrorAction:SilentlyContinue)\r\n {\r\n\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Err -Message $ErrMsg -ErrorAction:Continue\r\n\t}\r\n\telse\r\n {\r\n\t\tWrite-Error $ErrMsg -ErrorAction:Continue\r\n\t}\r\n\r\n\tthrow [System.Exception]::new($ErrMsg, $ErrContainer.Exception)\r\n}", + "$fxv#2": "# Authentication is provided in the script", + "cloudSuffix": "[replace(replace(environment().resourceManager, 'https://management.', ''), '/', '')]", + "functionAppKeyword": "[if(or(equals(environment().name, 'AzureCloud'), equals(environment().name, 'AzureUSGovernment')), 'azurewebsites', 'appservice')]", + "functionAppScmPrivateDnsZoneResourceId": "[format('{0}scm.{1}', parameters('privateDnsZoneResourceIdPrefix'), filter(parameters('privateDnsZones'), lambda('name', contains(lambdaVariables('name'), variables('functionAppKeyword'))))[0])]", + "service": "aipfsq", + "storageSubResources": [ + { + "name": "blob", + "id": "[format('{0}{1}', parameters('privateDnsZoneResourceIdPrefix'), filter(parameters('privateDnsZones'), lambda('name', contains(lambdaVariables('name'), 'blob')))[0])]", + "nic": "[parameters('namingConvention').storageAccountBlobNetworkInterface]", + "pe": "[parameters('namingConvention').storageAccountBlobPrivateEndpoint]" + }, + { + "name": "file", + "id": "[format('{0}{1}', parameters('privateDnsZoneResourceIdPrefix'), filter(parameters('privateDnsZones'), lambda('name', contains(lambdaVariables('name'), 'file')))[0])]", + "nic": "[parameters('namingConvention').storageAccountFileNetworkInterface]", + "pe": "[parameters('namingConvention').storageAccountFilePrivateEndpoint]" + }, + { + "name": "queue", + "id": "[format('{0}{1}', parameters('privateDnsZoneResourceIdPrefix'), filter(parameters('privateDnsZones'), lambda('name', contains(lambdaVariables('name'), 'queue')))[0])]", + "nic": "[parameters('namingConvention').storageAccountQueueNetworkInterface]", + "pe": "[parameters('namingConvention').storageAccountQueuePrivateEndpoint]" + }, + { + "name": "table", + "id": "[format('{0}{1}', parameters('privateDnsZoneResourceIdPrefix'), filter(parameters('privateDnsZones'), lambda('name', contains(lambdaVariables('name'), 'table')))[0])]", + "nic": "[parameters('namingConvention').storageAccountTableNetworkInterface]", + "pe": "[parameters('namingConvention').storageAccountTablePrivateEndpoint]" + } + ] + }, + "resources": [ + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service'))]", + "location": "[parameters('location')]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()), parameters('mlzTags'))]" + }, + { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2022-07-01", + "name": "[format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id))]", + "location": "[parameters('location')]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.KeyVault/vaults'), createObject()), parameters('mlzTags'))]", + "properties": { + "enabledForDeployment": false, + "enabledForDiskEncryption": false, + "enabledForTemplateDeployment": false, + "enablePurgeProtection": true, + "enableRbacAuthorization": true, + "enableSoftDelete": true, + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Deny", + "ipRules": [], + "virtualNetworkRules": [] + }, + "publicNetworkAccess": "Disabled", + "sku": { + "family": "A", + "name": "premium" + }, + "softDeleteRetentionInDays": "[if(or(equals(parameters('environmentAbbreviation'), 'dev'), equals(parameters('environmentAbbreviation'), 'test')), 7, 90)]", + "tenantId": "[subscription().tenantId]" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2020-04-01-preview", + "scope": "[format('Microsoft.KeyVault/vaults/{0}', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id)))]", + "name": "[guid(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service'))), 'e147488a-f6f5-4113-8e2d-b22465e65bf6', resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id))))]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service'))), '2023-01-31').principalId]", + "principalType": "ServicePrincipal", + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service')))]", + "[resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id)))]" + ] }, - "location": { - "type": "string" + { + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-04-01", + "name": "[replace(parameters('namingConvention').keyVaultPrivateEndpoint, parameters('serviceToken'), variables('service'))]", + "location": "[parameters('location')]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Network/privateEndpoints'), createObject()), parameters('mlzTags'))]", + "properties": { + "customNetworkInterfaceName": "[replace(parameters('namingConvention').keyVaultNetworkInterface, parameters('serviceToken'), variables('service'))]", + "privateLinkServiceConnections": [ + { + "name": "[replace(parameters('namingConvention').keyVaultPrivateEndpoint, parameters('serviceToken'), variables('service'))]", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id)))]", + "groupIds": [ + "vault" + ] + } + } + ], + "subnet": { + "id": "[parameters('subnetResourceId')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id)))]" + ] }, - "logAnalyticsWorkspaceResourceId": { - "type": "string" + { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2021-08-01", + "name": "[format('{0}/{1}', replace(parameters('namingConvention').keyVaultPrivateEndpoint, parameters('serviceToken'), variables('service')), format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id)))]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "ipconfig1", + "properties": { + "privateDnsZoneId": "[format('{0}{1}', parameters('privateDnsZoneResourceIdPrefix'), filter(parameters('privateDnsZones'), lambda('name', contains(lambdaVariables('name'), 'vaultcore')))[0])]" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', replace(parameters('namingConvention').keyVaultPrivateEndpoint, parameters('serviceToken'), variables('service')))]", + "[resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id)))]" + ] }, - "maxSessionLimit": { - "type": "int" + { + "type": "Microsoft.KeyVault/vaults/keys", + "apiVersion": "2022-07-01", + "name": "[format('{0}/{1}', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id)), 'StorageEncryptionKey')]", + "properties": { + "attributes": { + "enabled": true + }, + "keySize": 4096, + "kty": "RSA", + "rotationPolicy": { + "attributes": { + "expiryTime": "[format('P{0}D', string(parameters('keyExpirationInDays')))]" + }, + "lifetimeActions": [ + { + "action": { + "type": "Notify" + }, + "trigger": { + "timeBeforeExpiry": "P10D" + } + }, + { + "action": { + "type": "Rotate" + }, + "trigger": { + "timeAfterCreate": "[format('P{0}D', string(sub(parameters('keyExpirationInDays'), 7)))]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id)))]" + ] }, - "mlzTags": { - "type": "object" + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id)]", + "location": "[parameters('location')]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Storage/storageAccounts'), createObject()), parameters('mlzTags'))]", + "sku": { + "name": "Standard_LRS" + }, + "kind": "StorageV2", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service'))))]": {} + } + }, + "properties": { + "accessTier": "Hot", + "allowBlobPublicAccess": false, + "allowCrossTenantReplication": false, + "allowedCopyScope": "PrivateLink", + "allowSharedKeyAccess": false, + "azureFilesIdentityBasedAuthentication": { + "directoryServiceOptions": "None" + }, + "defaultToOAuthAuthentication": false, + "dnsEndpointType": "Standard", + "encryption": { + "identity": { + "userAssignedIdentity": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service')))]" + }, + "requireInfrastructureEncryption": true, + "keyvaultproperties": { + "keyvaulturi": "[reference(resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id))), '2022-07-01').vaultUri]", + "keyname": "StorageEncryptionKey" + }, + "services": { + "file": { + "keyType": "Account", + "enabled": true + }, + "table": { + "keyType": "Account", + "enabled": true + }, + "queue": { + "keyType": "Account", + "enabled": true + }, + "blob": { + "keyType": "Account", + "enabled": true + } + }, + "keySource": "Microsoft.KeyVault" + }, + "largeFileSharesState": "Disabled", + "minimumTlsVersion": "TLS1_2", + "networkAcls": { + "bypass": "AzureServices", + "virtualNetworkRules": [], + "ipRules": [], + "defaultAction": "Deny" + }, + "publicNetworkAccess": "Disabled", + "supportsHttpsTrafficOnly": true + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults/keys', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id)), 'StorageEncryptionKey')]", + "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', replace(parameters('namingConvention').keyVaultPrivateEndpoint, parameters('serviceToken'), variables('service')), format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id)))]", + "[resourceId('Microsoft.Network/privateEndpoints', replace(parameters('namingConvention').keyVaultPrivateEndpoint, parameters('serviceToken'), variables('service')))]", + "[extensionResourceId(resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id))), 'Microsoft.Authorization/roleAssignments', guid(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service'))), 'e147488a-f6f5-4113-8e2d-b22465e65bf6', resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id)))))]", + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', replace(parameters('namingConvention').userAssignedIdentity, parameters('serviceToken'), variables('service')))]", + "[resourceId('Microsoft.KeyVault/vaults', format('{0}{1}', parameters('resourceAbbreviations').keyVaults, uniqueString(replace(parameters('namingConvention').keyVault, parameters('serviceToken'), variables('service')), resourceGroup().id)))]" + ] }, - "sessionHostNamePrefix": { - "type": "string" + { + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id), 'default')]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id))]" + ] }, - "subnetResourceId": { - "type": "string" + { + "copy": { + "name": "privateEndpoints_storage", + "count": "[length(variables('storageSubResources'))]" + }, + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-04-01", + "name": "[replace(variables('storageSubResources')[copyIndex()].pe, parameters('serviceToken'), variables('service'))]", + "location": "[parameters('location')]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Network/privateEndpoints'), createObject()), parameters('mlzTags'))]", + "properties": { + "customNetworkInterfaceName": "[replace(variables('storageSubResources')[copyIndex()].nic, parameters('serviceToken'), variables('service'))]", + "privateLinkServiceConnections": [ + { + "name": "[replace(variables('storageSubResources')[copyIndex()].pe, parameters('serviceToken'), variables('service'))]", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id))]", + "groupIds": [ + "[variables('storageSubResources')[copyIndex()].name]" + ] + } + } + ], + "subnet": { + "id": "[parameters('subnetResourceId')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id))]" + ] }, - "tags": { - "type": "object" + { + "copy": { + "name": "privateDnsZoneGroups_storage", + "count": "[length(variables('storageSubResources'))]" + }, + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2021-08-01", + "name": "[format('{0}/{1}', replace(variables('storageSubResources')[copyIndex()].pe, parameters('serviceToken'), variables('service')), uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id))]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "ipconfig1", + "properties": { + "privateDnsZoneId": "[variables('storageSubResources')[copyIndex()].id]" + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', replace(variables('storageSubResources')[copyIndex()].pe, parameters('serviceToken'), variables('service')))]", + "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id))]" + ] }, - "time": { - "type": "string", - "defaultValue": "[utcNow('u')]" + { + "condition": "[parameters('enableApplicationInsights')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}/blobServices/{1}', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id), 'default')]", + "name": "[replace(parameters('namingConvention').storageAccountDiagnosticSetting, format('{0}-{1}', parameters('serviceToken'), parameters('resourceAbbreviations').storageAccounts), format('blob-{0}-scale', parameters('resourceAbbreviations').storageAccounts))]", + "properties": { + "logs": [ + { + "category": "StorageWrite", + "enabled": true + } + ], + "metrics": [ + { + "category": "Transaction", + "enabled": true + } + ], + "workspaceId": "[parameters('logAnalyticsWorkspaceResourceId')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/blobServices', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id), 'default')]" + ] }, - "validationEnvironment": { - "type": "bool" + { + "condition": "[parameters('enableApplicationInsights')]", + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", + "name": "[replace(parameters('namingConvention').applicationInsights, parameters('serviceToken'), variables('service'))]", + "location": "[parameters('location')]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Insights/components'), createObject()), parameters('mlzTags'))]", + "properties": { + "Application_Type": "web", + "publicNetworkAccessForIngestion": "Disabled", + "publicNetworkAccessForQuery": "Disabled" + }, + "kind": "web" }, - "virtualMachineSize": { - "type": "string" - } - }, - "variables": { - "customRdpProperty_Complete": "[if(contains(parameters('activeDirectorySolution'), 'MicrosoftEntraId'), format('{0}targetisaadjoined:i:1;enablerdsaadauth:i:1;', parameters('customRdpProperty')), parameters('customRdpProperty'))]" - }, - "resources": [ { - "type": "Microsoft.DesktopVirtualization/hostPools", - "apiVersion": "2023-09-05", - "name": "[parameters('hostPoolName')]", + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2023-01-01", + "name": "[replace(parameters('namingConvention').appServicePlan, parameters('serviceToken'), variables('service'))]", + "location": "[parameters('location')]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Web/serverfarms'), createObject()), parameters('mlzTags'))]", + "sku": { + "name": "P1v3", + "tier": "PremiumV3", + "size": "P1v3", + "family": "Pv3", + "capacity": 1 + }, + "kind": "functionapp" + }, + { + "type": "Microsoft.Web/sites", + "apiVersion": "2023-01-01", + "name": "[uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id)]", "location": "[parameters('location')]", - "tags": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, resourceGroup().name, parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.DesktopVirtualization/hostPools'), parameters('tags')['Microsoft.DesktopVirtualization/hostPools'], createObject()), parameters('mlzTags'))]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Web/sites'), createObject()), parameters('mlzTags'))]", + "kind": "functionapp", + "identity": { + "type": "SystemAssigned" + }, "properties": { - "customRdpProperty": "[variables('customRdpProperty_Complete')]", - "hostPoolType": "[split(parameters('hostPoolType'), ' ')[0]]", - "loadBalancerType": "[if(contains(parameters('hostPoolType'), 'Pooled'), split(parameters('hostPoolType'), ' ')[1], 'Persistent')]", - "maxSessionLimit": "[parameters('maxSessionLimit')]", - "personalDesktopAssignmentType": "[if(contains(parameters('hostPoolType'), 'Personal'), split(parameters('hostPoolType'), ' ')[1], null())]", - "preferredAppGroupType": "Desktop", - "publicNetworkAccess": "[parameters('hostPoolPublicNetworkAccess')]", - "registrationInfo": { - "expirationTime": "[dateTimeAdd(parameters('time'), 'PT2H')]", - "registrationTokenOperation": "Update" + "clientAffinityEnabled": false, + "httpsOnly": true, + "publicNetworkAccess": "Disabled", + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', replace(parameters('namingConvention').appServicePlan, parameters('serviceToken'), variables('service')))]", + "siteConfig": { + "alwaysOn": true, + "appSettings": "[union(createArray(createObject('name', 'AzureWebJobsStorage__blobServiceUri', 'value', format('https://{0}.blob.{1}', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id), environment().suffixes.storage)), createObject('name', 'AzureWebJobsStorage__credential', 'value', 'managedidentity'), createObject('name', 'AzureWebJobsStorage__queueServiceUri', 'value', format('https://{0}.queue.{1}', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id), environment().suffixes.storage)), createObject('name', 'AzureWebJobsStorage__tableServiceUri', 'value', format('https://{0}.table.{1}', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id), environment().suffixes.storage)), createObject('name', 'FUNCTIONS_EXTENSION_VERSION', 'value', '~4'), createObject('name', 'FUNCTIONS_WORKER_RUNTIME', 'value', 'powershell'), createObject('name', 'WEBSITE_LOAD_USER_PROFILE', 'value', '1'), createObject('name', 'FileShareName', 'value', 'profile-containers'), createObject('name', 'ResourceGroupName', 'value', parameters('resourceGroupProfiles')), createObject('name', 'ResourceManagerUrl', 'value', if(endsWith(environment().resourceManager, '/'), environment().resourceManager, format('{0}/', environment().resourceManager))), createObject('name', 'StorageSuffix', 'value', environment().suffixes.storage), createObject('name', 'SubscriptionId', 'value', subscription().subscriptionId)), if(parameters('enableApplicationInsights'), createArray(createObject('name', 'APPLICATIONINSIGHTS_CONNECTION_STRING', 'value', reference(resourceId('Microsoft.Insights/components', replace(parameters('namingConvention').applicationInsights, parameters('serviceToken'), variables('service'))), '2020-02-02').ConnectionString)), createArray()))]", + "cors": { + "allowedOrigins": [ + "[format('{0}', environment().portal)]", + "[format('https://functions-next.{0}', variables('cloudSuffix'))]", + "[format('https://functions-staging.{0}', variables('cloudSuffix'))]", + "[format('https://functions.{0}', variables('cloudSuffix'))]" + ] + }, + "ftpsState": "Disabled", + "netFrameworkVersion": "v6.0", + "powerShellVersion": "7.4", + "publicNetworkAccess": "Disabled", + "use32BitWorkerProcess": false }, - "startVMOnConnect": true, - "validationEnvironment": "[parameters('validationEnvironment')]", - "vmTemplate": "[format('{{\"domain\":\"{0}\",\"galleryImageOffer\":{1},\"galleryImagePublisher\":{2},\"galleryImageSKU\":{3},\"imageType\":{4},\"customImageId\":{5},\"namePrefix\":\"{6}\",\"osDiskType\":\"{7}\",\"vmSize\":{{\"id\":\"{8}\",\"cores\":null,\"ram\":null,\"rdmaEnabled\": false,\"supportsMemoryPreservingMaintenance\": true}},\"galleryItemId\":{9},\"hibernate\":false,\"diskSizeGB\":0,\"securityType\":\"TrustedLaunch\",\"secureBoot\":true,\"vTPM\":true,\"vmInfrastructureType\":\"Cloud\",\"virtualProcessorCount\":null,\"memoryGB\":null,\"maximumMemoryGB\":null,\"minimumMemoryGB\":null,\"dynamicMemoryConfig\":false}}', parameters('domainName'), parameters('galleryImageOffer'), parameters('galleryImagePublisher'), parameters('galleryImageSku'), parameters('imageType'), parameters('customImageId'), parameters('sessionHostNamePrefix'), parameters('diskSku'), parameters('virtualMachineSize'), parameters('galleryItemId'))]" - } + "virtualNetworkSubnetId": "[parameters('delegatedSubnetResourceId')]", + "vnetContentShareEnabled": false, + "vnetRouteAllEnabled": true + }, + "dependsOn": [ + "[resourceId('Microsoft.Insights/components', replace(parameters('namingConvention').applicationInsights, parameters('serviceToken'), variables('service')))]", + "[resourceId('Microsoft.Web/serverfarms', replace(parameters('namingConvention').appServicePlan, parameters('serviceToken'), variables('service')))]", + "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id))]" + ] }, { "type": "Microsoft.Network/privateEndpoints", "apiVersion": "2023-04-01", - "name": "[parameters('hostPoolPrivateEndpointName')]", + "name": "[replace(parameters('namingConvention').functionAppPrivateEndpoint, parameters('serviceToken'), variables('service'))]", "location": "[parameters('location')]", - "tags": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, resourceGroup().name, parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.Network/privateEndpoints'), parameters('tags')['Microsoft.Network/privateEndpoints'], createObject()), parameters('mlzTags'))]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Network/privateEndpoints'), createObject()), parameters('mlzTags'))]", "properties": { - "customNetworkInterfaceName": "[parameters('hostPoolNetworkInterfaceName')]", + "customNetworkInterfaceName": "[replace(parameters('namingConvention').functionAppNetworkInterface, parameters('serviceToken'), variables('service'))]", "privateLinkServiceConnections": [ { - "name": "[parameters('hostPoolPrivateEndpointName')]", + "name": "[replace(parameters('namingConvention').functionAppPrivateEndpoint, parameters('serviceToken'), variables('service'))]", "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.DesktopVirtualization/hostPools', parameters('hostPoolName'))]", + "privateLinkServiceId": "[resourceId('Microsoft.Web/sites', uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id))]", "groupIds": [ - "connection" + "sites" ] } } @@ -9538,248 +8639,156 @@ } }, "dependsOn": [ - "[resourceId('Microsoft.DesktopVirtualization/hostPools', parameters('hostPoolName'))]" + "[resourceId('Microsoft.Web/sites', uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id))]" ] }, { "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-05-01", - "name": "[format('{0}/{1}', parameters('hostPoolPrivateEndpointName'), 'default')]", + "apiVersion": "2021-08-01", + "name": "[format('{0}/{1}', replace(parameters('namingConvention').functionAppPrivateEndpoint, parameters('serviceToken'), variables('service')), 'default')]", "properties": { "privateDnsZoneConfigs": [ { - "name": "[replace(split(parameters('avdPrivateDnsZoneResourceId'), '/')[8], '.', '-')]", + "name": "ipconfig1", "properties": { - "privateDnsZoneId": "[parameters('avdPrivateDnsZoneResourceId')]" + "privateDnsZoneId": "[format('{0}{1}', parameters('privateDnsZoneResourceIdPrefix'), filter(parameters('privateDnsZones'), lambda('name', contains(lambdaVariables('name'), variables('functionAppKeyword'))))[0])]" } } ] }, "dependsOn": [ - "[resourceId('Microsoft.Network/privateEndpoints', parameters('hostPoolPrivateEndpointName'))]" + "[resourceId('Microsoft.Network/privateEndpoints', replace(parameters('namingConvention').functionAppPrivateEndpoint, parameters('serviceToken'), variables('service')))]" ] }, { - "condition": "[parameters('enableAvdInsights')]", - "type": "Microsoft.Insights/diagnosticSettings", - "apiVersion": "2021-05-01-preview", - "scope": "[format('Microsoft.DesktopVirtualization/hostPools/{0}', parameters('hostPoolName'))]", - "name": "[parameters('hostPoolDiagnosticSettingName')]", + "type": "Microsoft.Web/sites/functions", + "apiVersion": "2020-12-01", + "name": "[format('{0}/{1}', uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id), 'auto-increase-file-share-quota')]", "properties": { - "logs": [ - { - "categoryGroup": "allLogs", - "enabled": true - } - ], - "workspaceId": "[parameters('logAnalyticsWorkspaceResourceId')]" + "config": { + "disabled": false, + "bindings": [ + { + "name": "Timer", + "type": "timerTrigger", + "direction": "in", + "schedule": "0 */15 * * * *" + } + ] + }, + "files": { + "requirements.psd1": "[variables('$fxv#0')]", + "run.ps1": "[variables('$fxv#1')]", + "../profile.ps1": "[variables('$fxv#2')]" + } }, "dependsOn": [ - "[resourceId('Microsoft.DesktopVirtualization/hostPools', parameters('hostPoolName'))]" + "[resourceId('Microsoft.Web/sites', uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id))]" ] - } - ], - "outputs": { - "name": { - "type": "string", - "value": "[parameters('hostPoolName')]" - }, - "resourceId": { - "type": "string", - "value": "[resourceId('Microsoft.DesktopVirtualization/hostPools', parameters('hostPoolName'))]" - } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('deploy-vdag-{0}', parameters('deploymentNameSuffix'))]", - "resourceGroup": "[parameters('resourceGroupControlPlane')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "deploymentNameSuffix": { - "value": "[parameters('deploymentNameSuffix')]" - }, - "deploymentUserAssignedIdentityClientId": { - "value": "[parameters('deploymentUserAssignedIdentityClientId')]" - }, - "desktopApplicationGroupName": { - "value": "[replace(parameters('namingConvention').applicationGroup, parameters('serviceToken'), 'desktop')]" - }, - "hostPoolResourceId": { - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupControlPlane')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" - }, - "locationControlPlane": { - "value": "[parameters('locationControlPlane')]" - }, - "locationVirtualMachines": { - "value": "[parameters('locationVirtualMachines')]" - }, - "mlzTags": { - "value": "[parameters('mlzTags')]" - }, - "resourceGroupManagement": { - "value": "[parameters('resourceGroupManagement')]" - }, - "roleDefinitions": { - "value": "[parameters('roleDefinitions')]" - }, - "securityPrincipalObjectIds": { - "value": "[parameters('securityPrincipalObjectIds')]" - }, - "desktopFriendlyName": { - "value": "[parameters('desktopFriendlyName')]" - }, - "tags": { - "value": "[parameters('tags')]" - }, - "virtualMachineName": { - "value": "[parameters('managementVirtualMachineName')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "13010021933042504102" - } - }, - "parameters": { - "deploymentNameSuffix": { - "type": "string" - }, - "deploymentUserAssignedIdentityClientId": { - "type": "string" - }, - "desktopApplicationGroupName": { - "type": "string" - }, - "desktopFriendlyName": { - "type": "string" - }, - "hostPoolResourceId": { - "type": "string" - }, - "locationControlPlane": { - "type": "string" - }, - "locationVirtualMachines": { - "type": "string" - }, - "mlzTags": { - "type": "object" - }, - "resourceGroupManagement": { - "type": "string" - }, - "roleDefinitions": { - "type": "object" - }, - "securityPrincipalObjectIds": { - "type": "array" - }, - "tags": { - "type": "object" - }, - "virtualMachineName": { - "type": "string" - } - }, - "variables": { - "$fxv#0": "Param(\r\n [string]$ApplicationGroupName,\r\n [string]$FriendlyName,\r\n [string]$ResourceGroupName,\r\n [string]$ResourceManagerUri,\r\n [string]$SubscriptionId,\r\n [string]$UserAssignedIdentityClientId\r\n)\r\n\r\n$ErrorActionPreference = 'Stop'\r\n$WarningPreference = 'SilentlyContinue'\r\n\r\n# Wait for role assignment propagation\r\nStart-Sleep -Seconds 30\r\n\r\n# Fix the resource manager URI since only AzureCloud contains a trailing slash\r\n$ResourceManagerUriFixed = if ($ResourceManagerUri[-1] -eq '/') {$ResourceManagerUri} else {$ResourceManagerUri + '/'}\r\n\r\n# Get an access token for Azure resources\r\n$AzureManagementAccessToken = (Invoke-RestMethod `\r\n -Headers @{Metadata=\"true\"} `\r\n -Uri $('http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' + $ResourceManagerUriFixed + '&client_id=' + $UserAssignedIdentityClientId)).access_token\r\n\r\n# Set header for Azure Management API\r\n$AzureManagementHeader = @{\r\n 'Content-Type'='application/json'\r\n 'Authorization'='Bearer ' + $AzureManagementAccessToken\r\n}\r\n\r\n# Update the friendly name on the session desktop\r\nInvoke-RestMethod `\r\n -Body (@{properties = @{friendlyName = $FriendlyName.Replace('\"', '')}} | ConvertTo-Json) `\r\n -Headers $AzureManagementHeader `\r\n -Method 'PATCH' `\r\n -Uri $($ResourceManagerUriFixed + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $ResourceGroupName + '/providers/Microsoft.DesktopVirtualization/applicationGroups/' + $ApplicationGroupName + '/desktops/SessionDesktop?api-version=2022-02-10-preview') | Out-Null" - }, - "resources": [ - { - "type": "Microsoft.DesktopVirtualization/applicationGroups", - "apiVersion": "2021-03-09-preview", - "name": "[parameters('desktopApplicationGroupName')]", - "location": "[parameters('locationControlPlane')]", - "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), if(contains(parameters('tags'), 'Microsoft.DesktopVirtualization/applicationGroups'), parameters('tags')['Microsoft.DesktopVirtualization/applicationGroups'], createObject()), parameters('mlzTags'))]", - "properties": { - "hostPoolArmPath": "[parameters('hostPoolResourceId')]", - "applicationGroupType": "Desktop" - } }, { - "copy": { - "name": "roleAssignment", - "count": "[length(range(0, length(parameters('securityPrincipalObjectIds'))))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.DesktopVirtualization/applicationGroups/{0}', parameters('desktopApplicationGroupName'))]", - "name": "[guid(parameters('securityPrincipalObjectIds')[range(0, length(parameters('securityPrincipalObjectIds')))[copyIndex()]], parameters('roleDefinitions').DesktopVirtualizationUser, parameters('desktopApplicationGroupName'))]", + "condition": "[parameters('enableApplicationInsights')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('deploy-private-link-scope-appi-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('privateLinkScopeResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('privateLinkScopeResourceId'), '/')[4]]", "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitions').DesktopVirtualizationUser)]", - "principalId": "[parameters('securityPrincipalObjectIds')[range(0, length(parameters('securityPrincipalObjectIds')))[copyIndex()]]]" + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "applicationInsightsResourceId": { + "value": "[resourceId('Microsoft.Insights/components', replace(parameters('namingConvention').applicationInsights, parameters('serviceToken'), variables('service')))]" + }, + "privateLinkScopeResourceId": { + "value": "[parameters('privateLinkScopeResourceId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.92.45157", + "templateHash": "6666075809401495349" + } + }, + "parameters": { + "applicationInsightsResourceId": { + "type": "string", + "defaultValue": "" + }, + "dataCollectionEndpointResourceId": { + "type": "string", + "defaultValue": "" + }, + "logAnalyticsWorkspaceResourceId": { + "type": "string", + "defaultValue": "" + }, + "privateLinkScopeResourceId": { + "type": "string" + } + }, + "resources": [ + { + "condition": "[not(empty(parameters('applicationInsightsResourceId')))]", + "type": "Microsoft.Insights/privateLinkScopes/scopedResources", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('applicationInsightsResourceId')), 'applicationInsights', split(parameters('applicationInsightsResourceId'), '/')[8]))]", + "properties": { + "linkedResourceId": "[parameters('applicationInsightsResourceId')]" + } + }, + { + "condition": "[not(empty(parameters('dataCollectionEndpointResourceId')))]", + "type": "Microsoft.Insights/privateLinkScopes/scopedResources", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('dataCollectionEndpointResourceId')), 'dataCollectionEndpoint', split(parameters('dataCollectionEndpointResourceId'), '/')[8]))]", + "properties": { + "linkedResourceId": "[parameters('dataCollectionEndpointResourceId')]" + } + }, + { + "condition": "[not(empty(parameters('logAnalyticsWorkspaceResourceId')))]", + "type": "Microsoft.Insights/privateLinkScopes/scopedResources", + "apiVersion": "2021-09-01", + "name": "[format('{0}/{1}', split(parameters('privateLinkScopeResourceId'), '/')[8], if(empty(parameters('logAnalyticsWorkspaceResourceId')), 'logAnalyticsWorkspace', split(parameters('logAnalyticsWorkspaceResourceId'), '/')[8]))]", + "properties": { + "linkedResourceId": "[parameters('logAnalyticsWorkspaceResourceId')]" + } + } + ] + } }, "dependsOn": [ - "[resourceId('Microsoft.DesktopVirtualization/applicationGroups', parameters('desktopApplicationGroupName'))]" + "[resourceId('Microsoft.Insights/components', replace(parameters('namingConvention').applicationInsights, parameters('serviceToken'), variables('service')))]" ] }, { - "condition": "[not(empty(parameters('desktopFriendlyName')))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('deploy-vdapp-friendly-name-{0}', parameters('deploymentNameSuffix'))]", - "resourceGroup": "[parameters('resourceGroupManagement')]", + "name": "[format('set-role-assignment-storage-{0}', parameters('deploymentNameSuffix'))]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "location": { - "value": "[parameters('locationVirtualMachines')]" - }, - "name": { - "value": "Update-AvdDesktop" - }, - "parameters": { - "value": [ - { - "name": "ApplicationGroupName", - "value": "[parameters('desktopApplicationGroupName')]" - }, - { - "name": "FriendlyName", - "value": "[parameters('desktopFriendlyName')]" - }, - { - "name": "ResourceGroupName", - "value": "[resourceGroup().name]" - }, - { - "name": "ResourceManagerUri", - "value": "[environment().resourceManager]" - }, - { - "name": "SubscriptionId", - "value": "[subscription().subscriptionId]" - }, - { - "name": "UserAssignedIdentityClientId", - "value": "[parameters('deploymentUserAssignedIdentityClientId')]" - } - ] + "principalId": { + "value": "[reference(resourceId('Microsoft.Web/sites', uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id)), '2023-01-01', 'full').identity.principalId]" }, - "script": { - "value": "[variables('$fxv#0')]" + "principalType": { + "value": "ServicePrincipal" }, - "tags": { - "value": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), if(contains(parameters('tags'), 'Microsoft.Compute/virtualMachines'), parameters('tags')['Microsoft.Compute/virtualMachines'], createObject()), parameters('mlzTags'))]" + "roleDefinitionId": { + "value": "b7e6dc6d-f1e8-4753-8033-0f276bb0955b" }, - "virtualMachineName": { - "value": "[parameters('virtualMachineName')]" + "storageAccountName": { + "value": "[uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id)]" } }, "template": { @@ -9788,96 +8797,203 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17500708096438980911" + "version": "0.31.92.45157", + "templateHash": "9123948093985510582" } }, "parameters": { - "asyncExecution": { - "type": "bool", - "defaultValue": false - }, - "location": { + "principalId": { "type": "string" }, - "name": { + "principalType": { "type": "string" }, - "parameters": { - "type": "array", - "defaultValue": [] - }, - "script": { + "roleDefinitionId": { "type": "string" }, - "tags": { - "type": "object" + "storageAccountName": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(parameters('principalId'), parameters('roleDefinitionId'), resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionId'))]", + "principalId": "[parameters('principalId')]", + "principalType": "[parameters('principalType')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id))]", + "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), variables('service')), resourceGroup().id))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('deploy-scm-a-record-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(variables('functionAppScmPrivateDnsZoneResourceId'), '/')[2]]", + "resourceGroup": "[split(variables('functionAppScmPrivateDnsZoneResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "functionAppName": { + "value": "[uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id)]" + }, + "ipv4Address": { + "value": "[filter(reference(resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', replace(parameters('namingConvention').functionAppPrivateEndpoint, parameters('serviceToken'), variables('service')), 'default'), '2021-08-01').privateDnsZoneConfigs[0].properties.recordSets, lambda('record', equals(lambdaVariables('record').recordSetName, uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id))))[0].ipAddresses[0]]" + }, + "privateDnsZoneName": { + "value": "[split(variables('functionAppScmPrivateDnsZoneResourceId'), '/')[8]]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.92.45157", + "templateHash": "2293260329720441287" + } + }, + "parameters": { + "functionAppName": { + "type": "string" }, - "treatFailureAsDeploymentFailure": { - "type": "bool", - "defaultValue": true + "ipv4Address": { + "type": "string" }, - "virtualMachineName": { + "privateDnsZoneName": { "type": "string" } }, "resources": [ { - "type": "Microsoft.Compute/virtualMachines/runCommands", - "apiVersion": "2023-09-01", - "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", - "location": "[parameters('location')]", - "tags": "[parameters('tags')]", + "type": "Microsoft.Network/privateDnsZones/A", + "apiVersion": "2020-06-01", + "name": "[format('{0}/{1}', parameters('privateDnsZoneName'), parameters('functionAppName'))]", "properties": { - "asyncExecution": "[parameters('asyncExecution')]", - "parameters": "[parameters('parameters')]", - "source": { - "script": "[parameters('script')]" - }, - "treatFailureAsDeploymentFailure": "[parameters('treatFailureAsDeploymentFailure')]" + "aRecords": [ + { + "ipv4Address": "[parameters('ipv4Address')]" + } + ], + "ttl": 3600 } } ] } }, "dependsOn": [ - "[resourceId('Microsoft.DesktopVirtualization/applicationGroups', parameters('desktopApplicationGroupName'))]" + "[resourceId('Microsoft.Web/sites', uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id))]", + "[resourceId('Microsoft.Network/privateEndpoints/privateDnsZoneGroups', replace(parameters('namingConvention').functionAppPrivateEndpoint, parameters('serviceToken'), variables('service')), 'default')]" ] } ], "outputs": { - "resourceId": { + "functionAppName": { "type": "string", - "value": "[resourceId('Microsoft.DesktopVirtualization/applicationGroups', parameters('desktopApplicationGroupName'))]" + "value": "[uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id)]" + }, + "functionAppPrincipalId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Web/sites', uniqueString(replace(parameters('namingConvention').functionApp, parameters('serviceToken'), variables('service')), resourceGroup().id)), '2023-01-01', 'full').identity.principalId]" } } } }, "dependsOn": [ - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupControlPlane')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix')))]" + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-monitoring-{0}', parameters('deploymentNameSuffix')))]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" ] } ], "outputs": { "applicationGroupResourceId": { "type": "string", - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupControlPlane')), 'Microsoft.Resources/deployments', format('deploy-vdag-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-vdag-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" + }, + "dataCollectionRuleResourceId": { + "type": "string", + "value": "[if(parameters('enableAvdInsights'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-monitoring-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.dataCollectionRuleResourceId.value, '')]" + }, + "deploymentUserAssignedIdentityClientId": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.clientId.value]" + }, + "deploymentUserAssignedIdentityPrincipalId": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.principalId.value]" + }, + "deploymentUserAssignedIdentityResourceId": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-id-deployment-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" + }, + "diskAccessPolicyDefinitionId": { + "type": "string", + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-policy-disks-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.policyDefinitionId.value]" + }, + "diskAccessPolicyDisplayName": { + "type": "string", + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-policy-disks-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.policyDisplayName.value]" + }, + "diskAccessResourceId": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-disk-access-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" + }, + "functionAppPrincipalId": { + "type": "string", + "value": "[if(equals(parameters('fslogixStorageService'), 'AzureFiles Premium'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-function-app-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.functionAppPrincipalId.value, '')]" + }, + "hostPoolName": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" + }, + "hostPoolResourceId": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "value": "[if(or(parameters('enableApplicationInsights'), parameters('enableAvdInsights')), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-monitoring-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.logAnalyticsWorkspaceName.value, '')]" + }, + "logAnalyticsWorkspaceResourceId": { + "type": "string", + "value": "[if(or(parameters('enableApplicationInsights'), parameters('enableAvdInsights')), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-monitoring-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.logAnalyticsWorkspaceResourceId.value, '')]" + }, + "recoveryServicesVaultName": { + "type": "string", + "value": "[if(parameters('recoveryServices'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-rsv-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value, '')]" }, - "hostPoolName": { + "resourceGroupName": { "type": "string", - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupControlPlane')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" + "value": "[parameters('resourceGroupName')]" }, - "hostPoolResourceId": { + "virtualMachineName": { + "type": "string", + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-mgmt-vm-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" + }, + "virtualMachineResourceId": { "type": "string", - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupControlPlane')), 'Microsoft.Resources/deployments', format('deploy-vdpool-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-mgmt-vm-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceId.value]" } } } }, "dependsOn": [ - "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix')))]", - "[subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix')))]", - "rgs", + "[subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix')))]", "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix')))]" ] }, @@ -9894,7 +9010,7 @@ "mode": "Incremental", "parameters": { "applicationGroupResourceId": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-control-plane-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.applicationGroupResourceId.value]" + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.applicationGroupResourceId.value]" }, "avdPrivateDnsZoneResourceId": { "value": "[format('{0}{1}', variables('privateDnsZoneResourceIdPrefix'), filter(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.privateDnsZones.value, lambda('name', startsWith(lambdaVariables('name'), 'privatelink.wvd')))[0])]" @@ -9919,7 +9035,7 @@ "value": "[not(empty(parameters('existingFeedWorkspaceResourceId')))]" }, "hostPoolName": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-control-plane-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.hostPoolName.value]" + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.hostPoolName.value]" }, "locationControlPlane": { "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Network/virtualNetworks', split(parameters('hubVirtualNetworkResourceId'), '/')[8]), '2023-11-01', 'full').location]" @@ -9937,7 +9053,7 @@ "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.mlzTags.value]" }, "resourceGroupManagement": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[2], parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceGroupName.value]" }, "sharedServicesSubnetResourceId": { "value": "[parameters('sharedServicesSubnetResourceId')]" @@ -9946,35 +9062,35 @@ "value": "[parameters('tags')]" }, "workspaceFeedDiagnoticSettingName": { - "value": "[replace(replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-hub-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceFeedDiagnosticSetting, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-hub-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'feed'), format('-{0}', parameters('stampIndex')), '')]" + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceFeedDiagnosticSetting]" }, "workspaceFeedName": { - "value": "[replace(replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceFeed, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'feed'), format('-{0}', parameters('stampIndex')), '')]" + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceFeed]" }, "workspaceFeedNetworkInterfaceName": { - "value": "[replace(replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-hub-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceFeedNetworkInterface, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-hub-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'feed'), format('-{0}', parameters('stampIndex')), '')]" + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceFeedNetworkInterface]" }, "workspaceFeedPrivateEndpointName": { - "value": "[replace(replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-hub-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceFeedPrivateEndpoint, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-hub-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'feed'), format('-{0}', parameters('stampIndex')), '')]" + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceFeedPrivateEndpoint]" }, "workspaceFeedResourceGroupName": { - "value": "[replace(replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.resourceGroup, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'feedWorkspace'), format('-{0}', parameters('stampIndex')), '')]" + "value": "[replace(replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.resourceGroup, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'feedWorkspace'), format('-{0}', parameters('stampIndex')), '')]" }, - "workspaceFriendlyName": "[if(empty(parameters('workspaceFriendlyName')), createObject('value', replace(replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceFeed, format('-{0}', reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service), ''), format('-{0}', parameters('stampIndex')), '')), createObject('value', format('{0} ({1})', parameters('workspaceFriendlyName'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Network/virtualNetworks', split(parameters('hubVirtualNetworkResourceId'), '/')[8]), '2023-11-01', 'full').location)))]", + "workspaceFriendlyName": "[if(empty(parameters('workspaceFriendlyName')), createObject('value', replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceFeed, format('-{0}', reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service), '')), createObject('value', format('{0} ({1})', parameters('workspaceFriendlyName'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Network/virtualNetworks', split(parameters('hubVirtualNetworkResourceId'), '/')[8]), '2023-11-01', 'full').location)))]", "workspaceGlobalName": { - "value": "[replace(replace(replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceGlobal, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'global'), format('-{0}', parameters('stampIndex')), ''), parameters('identifier'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Network/virtualNetworks', split(parameters('hubVirtualNetworkResourceId'), '/')[8]), '2023-11-01', 'full').tags.resourcePrefix)]" + "value": "[replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceGlobal, parameters('identifier'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Network/virtualNetworks', split(parameters('hubVirtualNetworkResourceId'), '/')[8]), '2023-11-01', 'full').tags.resourcePrefix)]" }, "workspaceGlobalNetworkInterfaceName": { - "value": "[replace(replace(replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-hub-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceGlobalNetworkInterface, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-hub-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'global'), format('-{0}', parameters('stampIndex')), ''), parameters('identifier'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Network/virtualNetworks', split(parameters('hubVirtualNetworkResourceId'), '/')[8]), '2023-11-01', 'full').tags.resourcePrefix)]" + "value": "[replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceGlobalNetworkInterface, parameters('identifier'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Network/virtualNetworks', split(parameters('hubVirtualNetworkResourceId'), '/')[8]), '2023-11-01', 'full').tags.resourcePrefix)]" }, "workspaceGlobalPrivateDnsZoneResourceId": { "value": "[format('{0}{1}', variables('privateDnsZoneResourceIdPrefix'), filter(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.privateDnsZones.value, lambda('name', startsWith(lambdaVariables('name'), 'privatelink-global.wvd')))[0])]" }, "workspaceGlobalPrivateEndpointName": { - "value": "[replace(replace(replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-hub-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceGlobalPrivateEndpoint, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-hub-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'global'), format('-{0}', parameters('stampIndex')), ''), parameters('identifier'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Network/virtualNetworks', split(parameters('hubVirtualNetworkResourceId'), '/')[8]), '2023-11-01', 'full').tags.resourcePrefix)]" + "value": "[replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.workspaceGlobalPrivateEndpoint, parameters('identifier'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Network/virtualNetworks', split(parameters('hubVirtualNetworkResourceId'), '/')[8]), '2023-11-01', 'full').tags.resourcePrefix)]" }, "workspaceGlobalResourceGroupName": { - "value": "[replace(replace(replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.resourceGroup, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'globalWorkspace'), format('-{0}', parameters('stampIndex')), ''), parameters('identifier'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Network/virtualNetworks', split(parameters('hubVirtualNetworkResourceId'), '/')[8]), '2023-11-01', 'full').tags.resourcePrefix)]" + "value": "[replace(replace(replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value.resourceGroup, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'globalWorkspace'), format('-{0}', parameters('stampIndex')), ''), parameters('identifier'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Network/virtualNetworks', split(parameters('hubVirtualNetworkResourceId'), '/')[8]), '2023-11-01', 'full').tags.resourcePrefix)]" }, "workspacePublicNetworkAccess": { "value": "[parameters('workspacePublicNetworkAccess')]" @@ -9986,8 +9102,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3599289091308371499" + "version": "0.31.92.45157", + "templateHash": "140763294646618507" } }, "parameters": { @@ -10114,8 +9230,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10109660736443081073" + "version": "0.31.92.45157", + "templateHash": "9371138343009436350" } }, "parameters": { @@ -10202,8 +9318,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10739312966612375627" + "version": "0.31.92.45157", + "templateHash": "3251230175900717239" } }, "parameters": { @@ -10322,8 +9438,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10109660736443081073" + "version": "0.31.92.45157", + "templateHash": "9371138343009436350" } }, "parameters": { @@ -10404,8 +9520,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "15359000832124286075" + "version": "0.31.92.45157", + "templateHash": "10174155731090308034" } }, "parameters": { @@ -10465,8 +9581,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "15359000832124286075" + "version": "0.31.92.45157", + "templateHash": "10174155731090308034" } }, "parameters": { @@ -10579,8 +9695,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "226606255218378017" + "version": "0.31.92.45157", + "templateHash": "10426142527062787497" } }, "parameters": { @@ -10787,8 +9903,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17500708096438980911" + "version": "0.31.92.45157", + "templateHash": "6896852651895359166" } }, "parameters": { @@ -10852,15 +9968,13 @@ } }, "dependsOn": [ - "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-control-plane-{0}', parameters('deploymentNameSuffix')))]", "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix')))]", - "[subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-cp-{0}', parameters('deploymentNameSuffix')))]", - "[subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-hub-{0}', parameters('deploymentNameSuffix')))]", - "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[2], parameters('deploymentNameSuffix')))]", + "[subscriptionResourceId('Microsoft.Resources/deployments', format('get-naming-mgmt-{0}', parameters('deploymentNameSuffix')))]", "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix')))]" ] }, { + "condition": "[variables('deployFslogix')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "[format('deploy-fslogix-{0}', parameters('deploymentNameSuffix'))]", @@ -10886,6 +10000,9 @@ "deploymentUserAssignedIdentityClientId": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.deploymentUserAssignedIdentityClientId.value]" }, + "deploymentUserAssignedIdentityPrincipalId": { + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.deploymentUserAssignedIdentityPrincipalId.value]" + }, "dnsServers": { "value": "[string(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.dnsServers.value)]" }, @@ -10916,11 +10033,11 @@ "fslogixStorageService": { "value": "[parameters('fslogixStorageService')]" }, - "functionAppName": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.functionAppName.value]" + "functionAppPrincipalId": { + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.functionAppPrincipalId.value]" }, - "hostPoolType": { - "value": "[parameters('hostPoolType')]" + "hostPoolResourceId": { + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.hostPoolResourceId.value]" }, "keyVaultUri": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.keyVaultUri.value]" @@ -10946,15 +10063,14 @@ "recoveryServices": { "value": "[parameters('recoveryServices')]" }, - "resourceGroupControlPlane": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[0], parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" - }, "resourceGroupManagement": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[2], parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceGroupName.value]" + }, + "resourceGroupName": { + "value": "[replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.namingConvention.value.resourceGroup, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'profiles')]" }, - "resourceGroupStorage": "[if(variables('deployFslogix'), createObject('value', reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[3], parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value), createObject('value', ''))]", "securityPrincipalNames": { - "value": "[map(parameters('securityPrincipals'), lambda('item', lambdaVariables('item').name))]" + "value": "[map(parameters('securityPrincipals'), lambda('item', lambdaVariables('item').displayName))]" }, "securityPrincipalObjectIds": { "value": "[map(parameters('securityPrincipals'), lambda('item', lambdaVariables('item').objectId))]" @@ -10996,14 +10112,11 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "4149294850802164869" + "version": "0.31.92.45157", + "templateHash": "14643897230641833486" } }, "parameters": { - "existingSharedActiveDirectoryConnection": { - "type": "bool" - }, "activeDirectorySolution": { "type": "string" }, @@ -11022,6 +10135,9 @@ "deploymentUserAssignedIdentityClientId": { "type": "string" }, + "deploymentUserAssignedIdentityPrincipalId": { + "type": "string" + }, "dnsServers": { "type": "string" }, @@ -11037,22 +10153,25 @@ "encryptionUserAssignedIdentityResourceId": { "type": "string" }, + "existingSharedActiveDirectoryConnection": { + "type": "bool" + }, "fileShares": { "type": "array" }, - "fslogixShareSizeInGB": { - "type": "int" - }, "fslogixContainerType": { "type": "string" }, + "fslogixShareSizeInGB": { + "type": "int" + }, "fslogixStorageService": { "type": "string" }, - "functionAppName": { + "functionAppPrincipalId": { "type": "string" }, - "hostPoolType": { + "hostPoolResourceId": { "type": "string" }, "keyVaultUri": { @@ -11079,13 +10198,10 @@ "recoveryServices": { "type": "bool" }, - "resourceGroupControlPlane": { - "type": "string" - }, "resourceGroupManagement": { "type": "string" }, - "resourceGroupStorage": { + "resourceGroupName": { "type": "string" }, "securityPrincipalObjectIds": { @@ -11123,17 +10239,144 @@ } }, "variables": { - "hostPoolName": "[parameters('namingConvention').hostPool]", - "tagsNetAppAccount": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), variables('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.NetApp/netAppAccounts'), parameters('tags')['Microsoft.NetApp/netAppAccounts'], createObject()), parameters('mlzTags'))]", - "tagsVirtualMachines": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), variables('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.Compute/virtualMachines'), parameters('tags')['Microsoft.Compute/virtualMachines'], createObject()), parameters('mlzTags'))]" + "tagsNetAppAccount": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.NetApp/netAppAccounts'), createObject()), parameters('mlzTags'))]", + "tagsVirtualMachines": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Compute/virtualMachines'), createObject()), parameters('mlzTags'))]" }, "resources": [ { - "condition": "[and(equals(parameters('storageService'), 'AzureNetAppFiles'), contains(parameters('activeDirectorySolution'), 'DomainServices'))]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2023-07-01", + "name": "[parameters('resourceGroupName')]", + "location": "[parameters('location')]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Resources/resourceGroups'), createObject()), parameters('mlzTags'))]" + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('assign-role-storage-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "principalId": { + "value": "[parameters('deploymentUserAssignedIdentityPrincipalId')]" + }, + "principalType": { + "value": "ServicePrincipal" + }, + "roleDefinitionId": { + "value": "17d1049b-9a84-46fb-8f53-869881c3d3ab" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.92.45157", + "templateHash": "10174155731090308034" + } + }, + "parameters": { + "principalId": { + "type": "string" + }, + "principalType": { + "type": "string" + }, + "roleDefinitionId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('principalId'), parameters('roleDefinitionId'), resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionId'))]", + "principalId": "[parameters('principalId')]", + "principalType": "[parameters('principalType')]" + } + } + ] + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" + ] + }, + { + "condition": "[equals(parameters('fslogixStorageService'), 'AzureFiles Premium')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('set-role-assignment-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "principalId": { + "value": "[parameters('functionAppPrincipalId')]" + }, + "principalType": { + "value": "ServicePrincipal" + }, + "roleDefinitionId": { + "value": "17d1049b-9a84-46fb-8f53-869881c3d3ab" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.92.45157", + "templateHash": "10174155731090308034" + } + }, + "parameters": { + "principalId": { + "type": "string" + }, + "principalType": { + "type": "string" + }, + "roleDefinitionId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('principalId'), parameters('roleDefinitionId'), resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionId'))]", + "principalId": "[parameters('principalId')]", + "principalType": "[parameters('principalType')]" + } + } + ] + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" + ] + }, + { + "condition": "[equals(parameters('storageService'), 'AzureNetAppFiles')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "[format('deploy-anf-{0}', parameters('deploymentNameSuffix'))]", - "resourceGroup": "[parameters('resourceGroupStorage')]", + "resourceGroup": "[parameters('resourceGroupName')]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -11210,8 +10453,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "14014510525859848056" + "version": "0.31.92.45157", + "templateHash": "17417543434535800430" } }, "parameters": { @@ -11412,8 +10655,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "5010063642680411887" + "version": "0.31.92.45157", + "templateHash": "849953818393496765" } }, "parameters": { @@ -11484,14 +10727,17 @@ } } } - } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" + ] }, { - "condition": "[and(equals(parameters('storageService'), 'AzureFiles'), contains(parameters('activeDirectorySolution'), 'DomainServices'))]", + "condition": "[equals(parameters('storageService'), 'AzureFiles')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "[format('deploy-azure-files-{0}', parameters('deploymentNameSuffix'))]", - "resourceGroup": "[parameters('resourceGroupStorage')]", + "resourceGroup": "[parameters('resourceGroupName')]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -11534,14 +10780,8 @@ "fslogixShareSizeInGB": { "value": "[parameters('fslogixShareSizeInGB')]" }, - "fslogixStorageService": { - "value": "[parameters('fslogixStorageService')]" - }, - "functionAppName": { - "value": "[parameters('functionAppName')]" - }, - "hostPoolType": { - "value": "[parameters('hostPoolType')]" + "hostPoolResourceId": { + "value": "[parameters('hostPoolResourceId')]" }, "keyVaultUri": { "value": "[parameters('keyVaultUri')]" @@ -11567,9 +10807,6 @@ "resourceGroupManagement": { "value": "[parameters('resourceGroupManagement')]" }, - "resourceGroupStorage": { - "value": "[parameters('resourceGroupStorage')]" - }, "securityPrincipalNames": { "value": "[parameters('securityPrincipalNames')]" }, @@ -11600,14 +10837,8 @@ "tags": { "value": "[parameters('tags')]" }, - "hostPoolName": { - "value": "[variables('hostPoolName')]" - }, "mlzTags": { "value": "[parameters('mlzTags')]" - }, - "resourceGroupControlPlane": { - "value": "[parameters('resourceGroupControlPlane')]" } }, "template": { @@ -11616,8 +10847,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "6082593095415470269" + "version": "0.31.92.45157", + "templateHash": "2895952650836829415" } }, "parameters": { @@ -11657,16 +10888,7 @@ "fslogixContainerType": { "type": "string" }, - "fslogixStorageService": { - "type": "string" - }, - "functionAppName": { - "type": "string" - }, - "hostPoolName": { - "type": "string" - }, - "hostPoolType": { + "hostPoolResourceId": { "type": "string" }, "keyVaultUri": { @@ -11693,15 +10915,9 @@ "recoveryServicesVaultName": { "type": "string" }, - "resourceGroupControlPlane": { - "type": "string" - }, "resourceGroupManagement": { "type": "string" }, - "resourceGroupStorage": { - "type": "string" - }, "securityPrincipalObjectIds": { "type": "array" }, @@ -11735,9 +10951,6 @@ }, "variables": { "$fxv#0": "param \r\n(\r\n [String]$ActiveDirectorySolution,\r\n [String]$DomainJoinPassword,\r\n [String]$DomainJoinUserPrincipalName,\r\n [String]$FslogixContainerType,\r\n [String]$Netbios,\r\n [String]$OrganizationalUnitPath,\r\n [string]$ResourceManagerUri,\r\n [String]$SecurityPrincipalNames,\r\n [String]$SmbServerLocation,\r\n [String]$StorageAccountPrefix,\r\n [String]$StorageAccountResourceGroupName,\r\n [Int]$StorageCount,\r\n [Int]$StorageIndex,\r\n [String]$StorageService,\r\n [String]$StorageSuffix,\r\n [String]$SubscriptionId,\r\n [String]$UserAssignedIdentityClientId\r\n)\r\n\r\n$ErrorActionPreference = 'Stop'\r\n$WarningPreference = 'SilentlyContinue'\r\n\r\n##############################################################\r\n# Install Active Directory PowerShell module\r\n##############################################################\r\nif($StorageService -eq 'AzureNetAppFiles' -or ($StorageService -eq 'AzureFiles' -and $ActiveDirectorySolution -eq 'ActiveDirectoryDomainServices'))\r\n{\r\n $RsatInstalled = (Get-WindowsFeature -Name 'RSAT-AD-PowerShell').Installed\r\n if(!$RsatInstalled)\r\n {\r\n Install-WindowsFeature -Name 'RSAT-AD-PowerShell' | Out-Null\r\n }\r\n}\r\n\r\n\r\n##############################################################\r\n# Variables\r\n##############################################################\r\n# Convert Security Principal Names from a JSON array to a PowerShell array\r\n[array]$SecurityPrincipalNames = $SecurityPrincipalNames.Replace('\\','') | ConvertFrom-Json\r\n\r\n# Selects the appropraite share names based on the FslogixContainerType param from the deployment\r\n$Shares = switch($FslogixContainerType)\r\n{\r\n 'CloudCacheProfileContainer' {@('profile-containers')}\r\n 'CloudCacheProfileOfficeContainer' {@('office-containers','profile-containers')}\r\n 'ProfileContainer' {@('profile-containers')}\r\n 'ProfileOfficeContainer' {@('office-containers','profile-containers')}\r\n}\r\n\r\nif($StorageService -eq 'AzureNetAppFiles' -or ($StorageService -eq 'AzureFiles' -and $ActiveDirectorySolution -eq 'ActiveDirectoryDomainServices'))\r\n{\r\n # Create Domain credential\r\n $DomainUsername = $DomainJoinUserPrincipalName\r\n $DomainPassword = ConvertTo-SecureString -String $DomainJoinPassword -AsPlainText -Force\r\n [pscredential]$DomainCredential = New-Object System.Management.Automation.PSCredential ($DomainUsername, $DomainPassword)\r\n\r\n # Get Domain information\r\n $Domain = Get-ADDomain -Credential $DomainCredential -Current 'LocalComputer'\r\n}\r\n\r\nif($StorageService -eq 'AzureFiles')\r\n{\r\n $FilesSuffix = '.file.' + $StorageSuffix\r\n\r\n # Fix the resource manager URI since only AzureCloud contains a trailing slash\r\n $ResourceManagerUriFixed = if ($ResourceManagerUri[-1] -eq '/') { $ResourceManagerUri.Substring(0, $ResourceManagerUri.Length - 1) } else { $ResourceManagerUri }\r\n\r\n # Get an access token for Azure resources\r\n $AzureManagementAccessToken = (Invoke-RestMethod `\r\n -Headers @{Metadata = \"true\" } `\r\n -Uri $('http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' + $ResourceManagerUriFixed + '&client_id=' + $UserAssignedIdentityClientId)).access_token\r\n\r\n # Set header for Azure Management API\r\n $AzureManagementHeader = @{\r\n 'Content-Type' = 'application/json'\r\n 'Authorization' = 'Bearer ' + $AzureManagementAccessToken\r\n }\r\n}\r\n\r\n\r\n##############################################################\r\n# Process Storage Resources\r\n##############################################################\r\nfor($i = 0; $i -lt $StorageCount; $i++)\r\n{\r\n # Determine Principal for assignment\r\n $SecurityPrincipalName = $SecurityPrincipalNames[$i]\r\n $Group = $Netbios + '\\' + $SecurityPrincipalName\r\n\r\n # Get storage resource details\r\n switch($StorageService)\r\n {\r\n 'AzureNetAppFiles' {\r\n $Credential = $DomainCredential\r\n $SmbServerName = (Get-ADComputer -Filter \"Name -like 'anf-$SmbServerLocation*'\" -Credential $DomainCredential).Name\r\n $FileServer = '\\\\' + $SmbServerName + '.' + $Domain.DNSRoot\r\n }\r\n 'AzureFiles' {\r\n $StorageAccountName = $($StorageAccountPrefix + ($i + $StorageIndex).ToString().PadLeft(2,'0')).Substring(0,15)\r\n $FileServer = '\\\\' + $StorageAccountName + $FilesSuffix\r\n\r\n # Get the storage account key\r\n $StorageKey = (Invoke-RestMethod `\r\n -Headers $AzureManagementHeader `\r\n -Method 'POST' `\r\n -Uri $($ResourceManagerUriFixed + '/subscriptions/' + $SubscriptionId + '/resourceGroups/' + $StorageAccountResourceGroupName + '/providers/Microsoft.Storage/storageAccounts/' + $StorageAccountName + '/listKeys?api-version=2023-05-01')).keys[0].value\r\n\r\n # Create credential for accessing the storage account\r\n $StorageUsername = 'Azure\\' + $StorageAccountName\r\n $StoragePassword = ConvertTo-SecureString -String \"$($StorageKey)\" -AsPlainText -Force\r\n [pscredential]$StorageKeyCredential = New-Object System.Management.Automation.PSCredential ($StorageUsername, $StoragePassword)\r\n $Credential = $StorageKeyCredential\r\n\r\n if($ActiveDirectorySolution -eq 'ActiveDirectoryDomainServices')\r\n {\r\n # Get / create kerberos key for Azure Storage Account\r\n $KerberosKey = ((Invoke-RestMethod `\r\n -Headers $AzureManagementHeader `\r\n -Method 'POST' `\r\n -Uri $($ResourceManagerUriFixed + '/subscriptions/' + $SubscriptionId + '/resourceGroups/' + $StorageAccountResourceGroupName + '/providers/Microsoft.Storage/storageAccounts/' + $StorageAccountName + '/listKeys?api-version=2023-05-01&$expand=kerb')).keys | Where-Object { $_.Keyname -contains 'kerb1' }).Value\r\n \r\n if (!$KerberosKey) \r\n {\r\n Invoke-RestMethod `\r\n -Body (@{keyName = 'kerb1' } | ConvertTo-Json) `\r\n -Headers $AzureManagementHeader `\r\n -Method 'POST' `\r\n -Uri $($ResourceManagerUriFixed + '/subscriptions/' + $SubscriptionId + '/resourceGroups/' + $StorageAccountResourceGroupName + '/providers/Microsoft.Storage/storageAccounts/' + $StorageAccountName + '/regenerateKey?api-version=2023-05-01')\r\n \r\n $Key = ((Invoke-RestMethod `\r\n -Headers $AzureManagementHeader `\r\n -Method 'POST' `\r\n -Uri $($ResourceManagerUriFixed + '/subscriptions/' + $SubscriptionId + '/resourceGroups/' + $StorageAccountResourceGroupName + '/providers/Microsoft.Storage/storageAccounts/' + $StorageAccountName + '/listKeys?api-version=2023-05-01&$expand=kerb')).keys | Where-Object { $_.Keyname -contains 'kerb1' }).Value\r\n } \r\n else \r\n {\r\n $Key = $KerberosKey\r\n }\r\n\r\n # Creates a password for the Azure Storage Account in AD using the Kerberos key\r\n $ComputerPassword = ConvertTo-SecureString -String $Key.Replace(\"'\",\"\") -AsPlainText -Force\r\n\r\n # Create the SPN value for the Azure Storage Account; attribute for computer object in AD \r\n $SPN = 'cifs/' + $StorageAccountName + $FilesSuffix\r\n\r\n # Create the Description value for the Azure Storage Account; attribute for computer object in AD \r\n $Description = \"Computer account object for Azure storage account $($StorageAccountName).\"\r\n\r\n # Create the AD computer object for the Azure Storage Account\r\n $Computer = Get-ADComputer -Credential $DomainCredential -Filter {Name -eq $StorageAccountName}\r\n if($Computer)\r\n {\r\n Remove-ADComputer -Credential $DomainCredential -Identity $StorageAccountName -Confirm:$false\r\n }\r\n $ComputerObject = New-ADComputer -Credential $DomainCredential -Name $StorageAccountName -Path $OrganizationalUnitPath -ServicePrincipalNames $SPN -AccountPassword $ComputerPassword -Description $Description -PassThru\r\n\r\n $Body = (@{\r\n properties = @{\r\n azureFilesIdentityBasedAuthentication = @{\r\n activeDirectoryProperties = @{\r\n accountType = 'Computer'\r\n azureStorageSid = $ComputerObject.SID.Value\r\n domainGuid = $Domain.ObjectGUID.Guid\r\n domainName = $Domain.DNSRoot\r\n domainSid = $Domain.DomainSID.Value\r\n forestName = $Domain.Forest\r\n netBiosDomainName = $Domain.NetBIOSName\r\n samAccountName = $StorageAccountName\r\n }\r\n directoryServiceOptions = 'AD'\r\n }\r\n }\r\n } | ConvertTo-Json -Depth 6 -Compress)\r\n\r\n Invoke-RestMethod `\r\n -Body $Body `\r\n -Headers $AzureManagementHeader `\r\n -Method 'PATCH' `\r\n -Uri $($ResourceManagerUriFixed + '/subscriptions/' + $SubscriptionId + '/resourceGroups/' + $StorageAccountResourceGroupName + '/providers/Microsoft.Storage/storageAccounts/' + $StorageAccountName + '?api-version=2023-05-01')\r\n \r\n # Set the Kerberos encryption on the computer object\r\n $DistinguishedName = 'CN=' + $StorageAccountName + ',' + $OrganizationalUnitPath\r\n Set-ADComputer -Credential $DomainCredential -Identity $DistinguishedName -KerberosEncryptionType 'AES256' | Out-Null\r\n \r\n # Reset the Kerberos key on the Storage Account\r\n Invoke-RestMethod `\r\n -Body (@{keyName = 'kerb1' } | ConvertTo-Json) `\r\n -Headers $AzureManagementHeader `\r\n -Method 'POST' `\r\n -Uri $($ResourceManagerUriFixed + '/subscriptions/' + $SubscriptionId + '/resourceGroups/' + $StorageAccountResourceGroupName + '/providers/Microsoft.Storage/storageAccounts/' + $StorageAccountName + '/regenerateKey?api-version=2023-05-01')\r\n \r\n $Key = ((Invoke-RestMethod `\r\n -Headers $AzureManagementHeader `\r\n -Method 'POST' `\r\n -Uri $($ResourceManagerUriFixed + '/subscriptions/' + $SubscriptionId + '/resourceGroups/' + $StorageAccountResourceGroupName + '/providers/Microsoft.Storage/storageAccounts/' + $StorageAccountName + '/listKeys?api-version=2023-05-01&$expand=kerb')).keys | Where-Object { $_.Keyname -contains 'kerb1' }).Value\r\n \r\n # Update the password on the computer object with the new Kerberos key on the Storage Account\r\n $NewPassword = ConvertTo-SecureString -String $Key -AsPlainText -Force\r\n Set-ADAccountPassword -Credential $DomainCredential -Identity $DistinguishedName -Reset -NewPassword $NewPassword | Out-Null\r\n }\r\n }\r\n }\r\n \r\n foreach($Share in $Shares)\r\n {\r\n # Mount file share\r\n $FileShare = $FileServer + '\\' + $Share\r\n New-PSDrive -Name 'Z' -PSProvider 'FileSystem' -Root $FileShare -Credential $Credential | Out-Null\r\n\r\n # Set recommended NTFS permissions on the file share\r\n $ACL = Get-Acl -Path 'Z:'\r\n $CreatorOwner = New-Object System.Security.Principal.Ntaccount (\"Creator Owner\")\r\n $ACL.PurgeAccessRules($CreatorOwner)\r\n $AuthenticatedUsers = New-Object System.Security.Principal.Ntaccount (\"Authenticated Users\")\r\n $ACL.PurgeAccessRules($AuthenticatedUsers)\r\n $Users = New-Object System.Security.Principal.Ntaccount (\"Users\")\r\n $ACL.PurgeAccessRules($Users)\r\n $DomainUsers = New-Object System.Security.AccessControl.FileSystemAccessRule(\"$Group\",\"Modify\",\"None\",\"None\",\"Allow\")\r\n $ACL.SetAccessRule($DomainUsers)\r\n $CreatorOwner = New-Object System.Security.AccessControl.FileSystemAccessRule(\"Creator Owner\",\"Modify\",\"ContainerInherit,ObjectInherit\",\"InheritOnly\",\"Allow\")\r\n $ACL.AddAccessRule($CreatorOwner)\r\n $ACL | Set-Acl -Path 'Z:' | Out-Null\r\n\r\n # Unmount file share\r\n Remove-PSDrive -Name 'Z' -PSProvider 'FileSystem' -Force | Out-Null\r\n Start-Sleep -Seconds 5 | Out-Null\r\n }\r\n}", - "$fxv#1": "# This file enables modules to be automatically managed by the Functions service.\r\n# See https://aka.ms/functionsmanageddependency for additional information.\r\n#\r\n@{\r\n # For latest supported version, go to 'https://www.powershellgallery.com/packages/Az'. \r\n # To use the Az module in your function app, please uncomment the line below.\r\n # 'Az' = '7.*'\r\n}", - "$fxv#2": "param($Timer)\r\n\r\ntry\r\n{\r\n\t[string]$FileShareName = $env:FileShareName\r\n\t[string]$ResourceGroupName = $env:ResourceGroupName\r\n\t[string]$ResourceManagerUrl = $env:ResourceManagerUrl\r\n\t[string]$StorageSuffix = $env:StorageSuffix\r\n\t[string]$SubscriptionId = $env:SubscriptionId\r\n\r\n\t$ErrorActionPreference = 'Stop'\r\n\t$WarningPreference = 'SilentlyContinue'\r\n\r\n\t#region Functions\r\n\tfunction Write-Log \r\n {\r\n\t\t[CmdletBinding()]\r\n\t\tparam (\r\n\t\t\t[Parameter(Mandatory = $false)]\r\n\t\t\t[switch]$Err,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$Message,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$ResourceName,\r\n\r\n\t\t\t[Parameter(Mandatory = $false)]\r\n\t\t\t[switch]$Warn\r\n\t\t)\r\n\r\n\t\t[string]$MessageTimeStamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'\r\n\t\t$Message = \"[$($MyInvocation.ScriptLineNumber)] [$($ResourceName)] $Message\"\r\n\t\t[string]$WriteMessage = \"[$($MessageTimeStamp)] $Message\"\r\n\r\n\t\tif ($Err)\r\n {\r\n\t\t\tWrite-Error $WriteMessage\r\n\t\t\t$Message = \"ERROR: $Message\"\r\n\t\t}\r\n\t\telseif ($Warn)\r\n {\r\n\t\t\tWrite-Warning $WriteMessage\r\n\t\t\t$Message = \"WARN: $Message\"\r\n\t\t}\r\n\t\telse \r\n {\r\n\t\t\tWrite-Output $WriteMessage\r\n\t\t}\r\n\t}\r\n\t#endregion Functions\r\n\r\n\t# Note: https://stackoverflow.com/questions/41674518/powershell-setting-security-protocol-to-tls-1-2\r\n\t[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\r\n\r\n\r\n\t#region Azure Authentication\r\n $AccessToken = $null\r\n try\r\n {\r\n\t\t$TokenAuthURI = $env:IDENTITY_ENDPOINT + '?resource=' + $ResourceManagerUrl + '&api-version=2019-08-01'\r\n\t\t$TokenResponse = Invoke-RestMethod -Method Get -Headers @{\"X-IDENTITY-HEADER\"=\"$env:IDENTITY_HEADER\"} -Uri $TokenAuthURI\r\n\t\t$AccessToken = $TokenResponse.access_token\r\n\t\t$Header = @{\r\n\t\t\t'Content-Type'='application/json'\r\n\t\t\t'Authorization'='Bearer ' + $AccessToken\r\n\t\t}\r\n }\r\n catch\r\n {\r\n throw [System.Exception]::new('Failed to authenticate Azure with application ID, tenant ID, subscription ID', $PSItem.Exception)\r\n }\r\n Write-Log -ResourceName \"$SubscriptionId\" -Message \"Successfully authenticated with Azure using a managed identity\"\r\n\t#endregion Azure Authentication\r\n\r\n\t# Get storage accounts\r\n\t$Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $ResourceGroupName + '/providers/Microsoft.Storage/storageAccounts?api-version=2023-05-01'\r\n\t$StorageAccountNames = (Invoke-RestMethod -Headers $Header -Method 'GET' -Uri $Uri).value.name\r\n\r\n\tforeach($StorageAccountName in $StorageAccountNames)\r\n\t{\r\n\t\t$ShareUpdateUri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $ResourceGroupName + '/providers/Microsoft.Storage/storageAccounts/' + $StorageAccountName + '/fileServices/default/shares/' + $FileShareName + '?api-version=2023-05-01'\r\n\r\n\t\t# Get file share info\r\n\t\t$ShareGetUri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $ResourceGroupName + '/providers/Microsoft.Storage/storageAccounts/' + $StorageAccountName + '/fileServices/default/shares/' + $FileShareName + '?api-version=2023-05-01&$expand=stats'\r\n\t\t$PFS = (Invoke-RestMethod -Headers $Header -Method 'GET' -Uri $ShareGetUri).properties\r\n\r\n\t\t# Set variables for provisioned capacity and used capacity\r\n\t\t$ProvisionedCapacity = $PFS.shareQuota\r\n\t\t$UsedCapacity = $PFS.ShareUsageBytes\r\n\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"Share Capacity: $($ProvisionedCapacity)GB\"\r\n\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"Share Usage: $([math]::Round($UsedCapacity/1GB, 0))GB\"\r\n\r\n\t\t# GB Based Scaling\r\n\t\t# No scaling if no usage\r\n\t\tif($UsedCapacity -eq 0)\r\n\t\t{\r\n\t\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"Share Usage is 0GB. No Changes.\"\r\n\t\t}\r\n\t\t# Slow scaling up to 500GB\r\n\t\t# Increases share quota by 100GB if less than 50GB remains on the share\r\n\t\t# This allows time for an AVD Stamp to be rolled out \r\n\t\telseif ($ProvisionedCapacity -lt 500)\r\n\t\t{\r\n\t\t\tif (($ProvisionedCapacity - ($UsedCapacity / ([Math]::Pow(2,30)))) -lt 50) {\r\n\t\t\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"Share Usage has surpassed the Share Quota remaining threshold of 50GB. Increasing the file share quota by 100GB.\" \r\n\t\t\t\t$Quota = $ProvisionedCapacity + 100\r\n\t\t\t\tInvoke-RestMethod `\r\n\t\t\t\t\t-Body (@{properties = @{shareQuota = $Quota}} | ConvertTo-Json) `\r\n\t\t\t\t\t-Headers $Header `\r\n\t\t\t\t\t-Method 'PATCH' `\r\n\t\t\t\t\t-Uri $ShareUpdateUri | Out-Null\r\n\t\t\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"New Capacity: $($Quota)GB\"\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"Share Usage is below Share Quota remaining threshold of 50GB. No Changes.\"\r\n\t\t\t}\r\n\t\t}\r\n\t\t# Aggressive scaling\r\n\t\t# Increases share quota by 500GB if less than 500GB remains on the share\r\n\t\t# This ensures plenty of space is available during mass onboarding\r\n\t\telse \r\n\t\t{\r\n\t\t\tif (($ProvisionedCapacity - ($UsedCapacity / ([Math]::Pow(2,30)))) -lt 500) {\r\n\t\t\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"Share Usage has surpassed the Share Quota remaining threshold of 500GB. Increasing the file share quota by 500GB.\" \r\n\t\t\t\t$Quota = $ProvisionedCapacity + 500\r\n\t\t\t\tInvoke-RestMethod `\r\n\t\t\t\t\t-Body (@{properties = @{shareQuota = $Quota}} | ConvertTo-Json) `\r\n\t\t\t\t\t-Headers $Header `\r\n\t\t\t\t\t-Method 'PATCH' `\r\n\t\t\t\t\t-Uri $ShareUpdateUri | Out-Null\r\n\t\t\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"New Capacity: $($Quota)GB\"\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Message \"Share Usage is below Share Quota remaining threshold of 500GB. No Changes.\"\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\ncatch \r\n{\r\n\t$ErrContainer = $PSItem\r\n\t# $ErrContainer = $_\r\n\r\n\t[string]$ErrMsg = $ErrContainer | Format-List -Force | Out-String\r\n\t$ErrMsg += \"Version: $Version`n\"\r\n\r\n\tif (Get-Command 'Write-Log' -ErrorAction:SilentlyContinue)\r\n {\r\n\t\tWrite-Log -ResourceName \"$StorageAccountName/$FileShareName\" -Err -Message $ErrMsg -ErrorAction:Continue\r\n\t}\r\n\telse\r\n {\r\n\t\tWrite-Error $ErrMsg -ErrorAction:Continue\r\n\t}\r\n\r\n\tthrow [System.Exception]::new($ErrMsg, $ErrContainer.Exception)\r\n}", - "$fxv#3": "# Authentication is provided in the script", "roleDefinitionId": "0c867c2a-1d8c-454a-a3db-ab2ea1bdc8bb", "smbMultiChannel": { "multichannel": { @@ -11752,10 +10965,10 @@ }, "storageAccountNamePrefix": "[uniqueString(replace(parameters('namingConvention').storageAccount, parameters('serviceToken'), 'file-fslogix'), resourceGroup().id)]", "storageRedundancy": "[if(equals(parameters('availability'), 'availabilityZones'), '_ZRS', '_LRS')]", - "tagsPrivateEndpoints": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.Network/privateEndpoints'), parameters('tags')['Microsoft.Network/privateEndpoints'], createObject()), parameters('mlzTags'))]", - "tagsStorageAccounts": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.Storage/storageAccounts'), parameters('tags')['Microsoft.Storage/storageAccounts'], createObject()), parameters('mlzTags'))]", - "tagsRecoveryServicesVault": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.recoveryServices/vaults'), parameters('tags')['Microsoft.recoveryServices/vaults'], createObject()), parameters('mlzTags'))]", - "tagsVirtualMachines": "[union(createObject('cm-resource-parent', format('{0}}}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupControlPlane'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.Compute/virtualMachines'), parameters('tags')['Microsoft.Compute/virtualMachines'], createObject()), parameters('mlzTags'))]" + "tagsPrivateEndpoints": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Network/privateEndpoints'), createObject()), parameters('mlzTags'))]", + "tagsStorageAccounts": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Storage/storageAccounts'), createObject()), parameters('mlzTags'))]", + "tagsRecoveryServicesVault": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.recoveryServices/vaults'), createObject()), parameters('mlzTags'))]", + "tagsVirtualMachines": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Compute/virtualMachines'), createObject()), parameters('mlzTags'))]" }, "resources": [ { @@ -11858,14 +11071,14 @@ }, "type": "Microsoft.Network/privateEndpoints", "apiVersion": "2023-04-01", - "name": "[format('{0}-{1}', replace(parameters('namingConvention').storageAccountPrivateEndpoint, parameters('serviceToken'), 'file-fslogix'), padLeft(add(range(0, parameters('storageCount'))[copyIndex()], parameters('storageIndex')), 2, '0'))]", + "name": "[format('{0}-{1}', parameters('namingConvention').storageAccountFilePrivateEndpoint, padLeft(add(range(0, parameters('storageCount'))[copyIndex()], parameters('storageIndex')), 2, '0'))]", "location": "[parameters('location')]", "tags": "[variables('tagsPrivateEndpoints')]", "properties": { - "customNetworkInterfaceName": "[format('{0}-{1}', replace(parameters('namingConvention').storageAccountNetworkInterface, parameters('serviceToken'), 'file-fslogix'), padLeft(add(range(0, parameters('storageCount'))[copyIndex()], parameters('storageIndex')), 2, '0'))]", + "customNetworkInterfaceName": "[format('{0}-{1}', parameters('namingConvention').storageAccountFileNetworkInterface, padLeft(add(range(0, parameters('storageCount'))[copyIndex()], parameters('storageIndex')), 2, '0'))]", "privateLinkServiceConnections": [ { - "name": "[format('{0}-{1}', replace(parameters('namingConvention').storageAccountPrivateEndpoint, parameters('serviceToken'), 'file-fslogix'), padLeft(add(range(0, parameters('storageCount'))[copyIndex()], parameters('storageIndex')), 2, '0'))]", + "name": "[format('{0}-{1}', parameters('namingConvention').storageAccountFilePrivateEndpoint, padLeft(add(range(0, parameters('storageCount'))[copyIndex()], parameters('storageIndex')), 2, '0'))]", "properties": { "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', take(format('{0}{1}', variables('storageAccountNamePrefix'), padLeft(add(range(0, parameters('storageCount'))[range(0, parameters('storageCount'))[copyIndex()]], parameters('storageIndex')), 2, '0')), 15))]", "groupIds": [ @@ -11889,7 +11102,7 @@ }, "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", "apiVersion": "2021-08-01", - "name": "[format('{0}/{1}', format('{0}-{1}', replace(parameters('namingConvention').storageAccountPrivateEndpoint, parameters('serviceToken'), 'file-fslogix'), padLeft(add(range(0, parameters('storageCount'))[range(0, parameters('storageCount'))[copyIndex()]], parameters('storageIndex')), 2, '0')), take(format('{0}{1}', variables('storageAccountNamePrefix'), padLeft(add(range(0, parameters('storageCount'))[range(0, parameters('storageCount'))[copyIndex()]], parameters('storageIndex')), 2, '0')), 15))]", + "name": "[format('{0}/{1}', format('{0}-{1}', parameters('namingConvention').storageAccountFilePrivateEndpoint, padLeft(add(range(0, parameters('storageCount'))[range(0, parameters('storageCount'))[copyIndex()]], parameters('storageIndex')), 2, '0')), take(format('{0}{1}', variables('storageAccountNamePrefix'), padLeft(add(range(0, parameters('storageCount'))[range(0, parameters('storageCount'))[copyIndex()]], parameters('storageIndex')), 2, '0')), 15))]", "properties": { "privateDnsZoneConfigs": [ { @@ -11901,7 +11114,7 @@ ] }, "dependsOn": [ - "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-{1}', replace(parameters('namingConvention').storageAccountPrivateEndpoint, parameters('serviceToken'), 'file-fslogix'), padLeft(add(range(0, parameters('storageCount'))[range(0, parameters('storageCount'))[copyIndex()]], parameters('storageIndex')), 2, '0')))]", + "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-{1}', parameters('namingConvention').storageAccountFilePrivateEndpoint, padLeft(add(range(0, parameters('storageCount'))[range(0, parameters('storageCount'))[copyIndex()]], parameters('storageIndex')), 2, '0')))]", "storageAccounts" ] }, @@ -11938,8 +11151,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "11815683316027655066" + "version": "0.31.92.45157", + "templateHash": "2560784574355796510" } }, "parameters": { @@ -11975,15 +11188,15 @@ } }, "dependsOn": [ + "fileServices", "roleAssignment", "[resourceId('Microsoft.Storage/storageAccounts', take(format('{0}{1}', variables('storageAccountNamePrefix'), padLeft(add(range(0, parameters('storageCount'))[range(0, parameters('storageCount'))[copyIndex()]], parameters('storageIndex')), 2, '0')), 15))]" ] }, { - "condition": "[contains(parameters('activeDirectorySolution'), 'DomainServices')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('deploy-fslogix-ntfs-permissions-{0}', parameters('deploymentNameSuffix'))]", + "name": "[format('set-ntfs-permissions-{0}', parameters('deploymentNameSuffix'))]", "resourceGroup": "[parameters('resourceGroupManagement')]", "properties": { "expressionEvaluationOptions": { @@ -12035,7 +11248,7 @@ }, { "name": "StorageAccountResourceGroupName", - "value": "[parameters('resourceGroupStorage')]" + "value": "[resourceGroup().name]" }, { "name": "StorageCount", @@ -12079,8 +11292,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "5010063642680411887" + "version": "0.31.92.45157", + "templateHash": "849953818393496765" } }, "parameters": { @@ -12146,10 +11359,10 @@ ] }, { - "condition": "[and(parameters('enableRecoveryServices'), contains(parameters('hostPoolType'), 'Pooled'))]", + "condition": "[parameters('enableRecoveryServices')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('deploy-backup-azure-files-{0}', parameters('deploymentNameSuffix'))]", + "name": "[format('deploy-backup-{0}', parameters('deploymentNameSuffix'))]", "resourceGroup": "[parameters('resourceGroupManagement')]", "properties": { "expressionEvaluationOptions": { @@ -12170,7 +11383,7 @@ "value": "[parameters('recoveryServicesVaultName')]" }, "resourceGroupStorage": { - "value": "[parameters('resourceGroupStorage')]" + "value": "[resourceGroup().name]" }, "storageAccountNamePrefix": { "value": "[variables('storageAccountNamePrefix')]" @@ -12191,8 +11404,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "476825279659345843" + "version": "0.31.92.45157", + "templateHash": "11840486447312746268" } }, "parameters": { @@ -12278,8 +11491,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "5833668119144644566" + "version": "0.31.92.45157", + "templateHash": "13786800560391201998" } }, "parameters": { @@ -12331,87 +11544,7 @@ } }, "dependsOn": [ - "shares" - ] - }, - { - "condition": "[and(equals(parameters('fslogixStorageService'), 'AzureFiles Premium'), greater(parameters('storageCount'), 0))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('deploy-file-share-scaling-{0}', parameters('deploymentNameSuffix'))]", - "resourceGroup": "[parameters('resourceGroupManagement')]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "files": { - "value": { - "requirements.psd1": "[variables('$fxv#1')]", - "run.ps1": "[variables('$fxv#2')]", - "../profile.ps1": "[variables('$fxv#3')]" - } - }, - "functionAppName": { - "value": "[parameters('functionAppName')]" - }, - "functionName": { - "value": "auto-increase-file-share-quota" - }, - "schedule": { - "value": "0 */15 * * * *" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3901504540519683161" - } - }, - "parameters": { - "files": { - "type": "object" - }, - "functionAppName": { - "type": "string" - }, - "functionName": { - "type": "string" - }, - "schedule": { - "type": "string" - } - }, - "resources": [ - { - "type": "Microsoft.Web/sites/functions", - "apiVersion": "2020-12-01", - "name": "[format('{0}/{1}', parameters('functionAppName'), parameters('functionName'))]", - "properties": { - "config": { - "disabled": false, - "bindings": [ - { - "name": "Timer", - "type": "timerTrigger", - "direction": "in", - "schedule": "[parameters('schedule')]" - } - ] - }, - "files": "[parameters('files')]" - } - } - ] - } - }, - "dependsOn": [ - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-fslogix-ntfs-permissions-{0}', parameters('deploymentNameSuffix')))]" + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('set-ntfs-permissions-{0}', parameters('deploymentNameSuffix')))]" ] } ], @@ -12422,25 +11555,26 @@ } } } - } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" + ] } ], "outputs": { "netAppShares": { "type": "array", - "value": "[if(equals(parameters('storageService'), 'AzureNetAppFiles'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupStorage')), 'Microsoft.Resources/deployments', format('deploy-anf-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.fileShares.value, createArray('None'))]" + "value": "[if(equals(parameters('storageService'), 'AzureNetAppFiles'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-anf-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.fileShares.value, createArray('None'))]" }, "storageAccountNamePrefix": { "type": "string", - "value": "[if(equals(parameters('storageService'), 'AzureFiles'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupStorage')), 'Microsoft.Resources/deployments', format('deploy-azure-files-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.storageAccountNamePrefix.value, '')]" + "value": "[if(equals(parameters('storageService'), 'AzureFiles'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-azure-files-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.storageAccountNamePrefix.value, '')]" } } } }, "dependsOn": [ - "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-control-plane-{0}', parameters('deploymentNameSuffix')))]", "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix')))]", - "rgs", "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix')))]" ] }, @@ -12470,6 +11604,9 @@ "availabilityZones": { "value": "[parameters('availabilityZones')]" }, + "avdConfigurationZipFileName": { + "value": "[parameters('avdConfigurationZipFileName')]" + }, "dataCollectionRuleResourceId": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.dataCollectionRuleResourceId.value]" }, @@ -12482,6 +11619,18 @@ "deploymentUserAssignedIdentityClientId": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.deploymentUserAssignedIdentityClientId.value]" }, + "deploymentUserAssignedIdentityPrincipalId": { + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.deploymentUserAssignedIdentityPrincipalId.value]" + }, + "diskAccessPolicyDefinitionId": { + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.diskAccessPolicyDefinitionId.value]" + }, + "diskAccessPolicyDisplayName": { + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.diskAccessPolicyDisplayName.value]" + }, + "diskAccessResourceId": { + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.diskAccessResourceId.value]" + }, "diskEncryptionSetResourceId": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.diskEncryptionSetResourceId.value]" }, @@ -12512,20 +11661,17 @@ "enableRecoveryServices": { "value": "[parameters('recoveryServices')]" }, - "enableScalingTool": { - "value": "[parameters('scalingTool')]" - }, "environmentAbbreviation": { "value": "[parameters('environmentAbbreviation')]" }, "fslogixContainerType": { "value": "[parameters('fslogixContainerType')]" }, - "functionAppName": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.functionAppName.value]" - }, "hostPoolName": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-control-plane-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.hostPoolName.value]" + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.hostPoolName.value]" + }, + "hostPoolResourceId": { + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.hostPoolResourceId.value]" }, "hostPoolType": { "value": "[parameters('hostPoolType')]" @@ -12548,6 +11694,9 @@ "location": { "value": "[parameters('locationVirtualMachines')]" }, + "logAnalyticsWorkspaceResourceId": { + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.logAnalyticsWorkspaceResourceId.value]" + }, "managementVirtualMachineName": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.virtualMachineName.value]" }, @@ -12564,23 +11713,29 @@ "organizationalUnitPath": { "value": "[parameters('organizationalUnitPath')]" }, - "pooledHostPool": { - "value": "[variables('pooledHostPool')]" + "profile": { + "value": "[parameters('profile')]" }, "recoveryServicesVaultName": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.recoveryServicesVaultName.value]" }, - "resourceGroupControlPlane": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[0], parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" + "resourceGroupManagement": { + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceGroupName.value]" + }, + "resourceGroupName": { + "value": "[replace(reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.namingConvention.value.resourceGroup, reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tokens.value.service, 'hosts')]" }, - "resourceGroupHosts": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[1], parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" + "scalingWeekdaysOffPeakStartTime": { + "value": "[parameters('scalingWeekdaysOffPeakStartTime')]" }, - "resourceGroupManagement": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[2], parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" + "scalingWeekdaysPeakStartTime": { + "value": "[parameters('scalingWeekdaysPeakStartTime')]" }, - "roleDefinitions": { - "value": "[variables('roleDefinitions')]" + "scalingWeekendsOffPeakStartTime": { + "value": "[parameters('scalingWeekendsOffPeakStartTime')]" + }, + "scalingWeekendsPeakStartTime": { + "value": "[parameters('scalingWeekendsPeakStartTime')]" }, "securityPrincipalObjectIds": { "value": "[map(parameters('securityPrincipals'), lambda('item', lambdaVariables('item').objectId))]" @@ -12594,9 +11749,7 @@ "sessionHostIndex": { "value": "[parameters('sessionHostIndex')]" }, - "storageAccountNamePrefix": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-fslogix-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.storageAccountNamePrefix.value]" - }, + "storageAccountNamePrefix": "[if(variables('deployFslogix'), createObject('value', reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-fslogix-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.storageAccountNamePrefix.value), createObject('value', ''))]", "storageCount": { "value": "[parameters('storageCount')]" }, @@ -12615,6 +11768,9 @@ "tags": { "value": "[parameters('tags')]" }, + "timeZone": { + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.locationProperties.value.timeZone]" + }, "virtualMachinePassword": { "value": "[parameters('virtualMachinePassword')]" }, @@ -12631,8 +11787,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "15054116968510662395" + "version": "0.31.92.45157", + "templateHash": "8274254996075869850" } }, "parameters": { @@ -12651,6 +11807,9 @@ "availabilityZones": { "type": "array" }, + "avdConfigurationZipFileName": { + "type": "string" + }, "dataCollectionRuleResourceId": { "type": "string" }, @@ -12663,6 +11822,18 @@ "deploymentUserAssignedIdentityClientId": { "type": "string" }, + "deploymentUserAssignedIdentityPrincipalId": { + "type": "string" + }, + "diskAccessPolicyDefinitionId": { + "type": "string" + }, + "diskAccessPolicyDisplayName": { + "type": "string" + }, + "diskAccessResourceId": { + "type": "string" + }, "diskEncryptionSetResourceId": { "type": "string" }, @@ -12690,16 +11861,19 @@ "enableAvdInsights": { "type": "bool" }, + "enableRecoveryServices": { + "type": "bool" + }, "environmentAbbreviation": { "type": "string" }, "fslogixContainerType": { "type": "string" }, - "functionAppName": { + "hostPoolName": { "type": "string" }, - "hostPoolName": { + "hostPoolResourceId": { "type": "string" }, "hostPoolType": { @@ -12723,6 +11897,9 @@ "location": { "type": "string" }, + "logAnalyticsWorkspaceResourceId": { + "type": "string" + }, "managementVirtualMachineName": { "type": "string" }, @@ -12741,29 +11918,29 @@ "organizationalUnitPath": { "type": "string" }, - "pooledHostPool": { - "type": "bool" + "profile": { + "type": "string" }, - "enableRecoveryServices": { - "type": "bool" + "recoveryServicesVaultName": { + "type": "string" }, - "enableScalingTool": { - "type": "bool" + "resourceGroupManagement": { + "type": "string" }, - "recoveryServicesVaultName": { + "resourceGroupName": { "type": "string" }, - "resourceGroupControlPlane": { + "scalingWeekdaysOffPeakStartTime": { "type": "string" }, - "resourceGroupHosts": { + "scalingWeekdaysPeakStartTime": { "type": "string" }, - "resourceGroupManagement": { + "scalingWeekendsOffPeakStartTime": { "type": "string" }, - "roleDefinitions": { - "type": "object" + "scalingWeekendsPeakStartTime": { + "type": "string" }, "securityPrincipalObjectIds": { "type": "array" @@ -12798,6 +11975,9 @@ "tags": { "type": "object" }, + "timeZone": { + "type": "string" + }, "virtualMachinePassword": { "type": "securestring" }, @@ -12809,24 +11989,103 @@ } }, "variables": { - "$fxv#0": "# This file enables modules to be automatically managed by the Functions service.\r\n# See https://aka.ms/functionsmanageddependency for additional information.\r\n#\r\n@{\r\n # For latest supported version, go to 'https://www.powershellgallery.com/packages/Az'. \r\n # To use the Az module in your function app, please uncomment the line below.\r\n # 'Az' = '7.*'\r\n}", - "$fxv#1": "param($Timer)\r\n\r\ntry\r\n{\r\n\t[string]$BeginPeakTime = $env:BeginPeakTime\r\n\t[string]$EndPeakTime = $env:EndPeakTime\r\n\t[string]$EnvironmentName = $env:EnvironmentName\r\n\t[string]$HostPoolName = $env:HostPoolName\r\n\t[string]$HostPoolResourceGroupName = $env:HostPoolResourceGroupName\r\n\t[int]$LimitSecondsToForceLogOffUser = $env:LimitSecondsToForceLogOffUser\r\n\t[string]$LogOffMessageBody = $env:LogOffMessageBody\r\n\t[string]$LogOffMessageTitle = $env:LogOffMessageTitle\r\n\t[string]$MaintenanceTagName = $env:MaintenanceTagName\r\n\t[int]$MinimumNumberOfRDSH = $env:MinimumNumberOfRDSH\r\n\t[string]$ResourceManagerUrl = $env:ResourceManagerUrl\r\n\t[double]$SessionThresholdPerCPU = $env:SessionThresholdPerCPU\r\n\t[string]$SubscriptionId = $env:SubscriptionId\r\n\t[string]$TenantId = $env:TenantId\r\n\t[string]$TimeDifference = $env:TimeDifference\r\n\t[string[]]$DesiredRunningStates = @('Available', 'NeedsAssistance')\r\n\t[string[]]$TimeDiffHrsMin = \"$($TimeDifference):0\".Split(':')\r\n\r\n\r\n\t#region Functions\r\n\tfunction Get-LocalDateTime\r\n {\r\n\t\treturn (Get-Date).ToUniversalTime().AddHours($TimeDiffHrsMin[0]).AddMinutes($TimeDiffHrsMin[1])\r\n\t}\r\n\r\n\tfunction Write-Log \r\n {\r\n\t\t[CmdletBinding()]\r\n\t\tparam (\r\n\t\t\t[Parameter(Mandatory = $false)]\r\n\t\t\t[switch]$Err,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$HostPoolName,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$Message,\r\n\r\n\t\t\t[Parameter(Mandatory = $false)]\r\n\t\t\t[switch]$Warn\r\n\t\t)\r\n\r\n\t\t[string]$MessageTimeStamp = (Get-LocalDateTime).ToString('yyyy-MM-dd HH:mm:ss')\r\n\t\t$Message = \"[$($MyInvocation.ScriptLineNumber)] [$($HostPoolName)] $Message\"\r\n\t\t[string]$WriteMessage = \"[$($MessageTimeStamp)] $Message\"\r\n\r\n\t\tif ($Err)\r\n {\r\n\t\t\tWrite-Error $WriteMessage\r\n\t\t\t$Message = \"ERROR: $Message\"\r\n\t\t}\r\n\t\telseif ($Warn)\r\n {\r\n\t\t\tWrite-Warning $WriteMessage\r\n\t\t\t$Message = \"WARN: $Message\"\r\n\t\t}\r\n\t\telse \r\n {\r\n\t\t\tWrite-Output $WriteMessage\r\n\t\t}\r\n\t}\r\n\r\n\tfunction Set-nVMsToStartOrStop \r\n {\r\n\t\tparam (\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$HostPoolName,\r\n\r\n\t\t\t[Parameter(Mandatory = $false)]\r\n\t\t\t[switch]$InPeakHours,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[int]$MaxUserSessionsPerVM,\r\n\t\t\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[int]$nRunningCores,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[int]$nRunningVMs,\r\n\t\t\t\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[int]$nUserSessions,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[hashtable]$Res\r\n\t\t)\r\n\r\n\t\t# check if need to adjust min num of running session hosts required if the number of user sessions is close to the max allowed by the min num of running session hosts required\r\n\t\t[double]$MaxUserSessionsThreshold = 0.9\r\n\t\t[int]$MaxUserSessionsThresholdCapacity = [math]::Floor($MinimumNumberOfRDSH * $MaxUserSessionsPerVM * $MaxUserSessionsThreshold)\r\n\t\tif ($nUserSessions -gt $MaxUserSessionsThresholdCapacity)\r\n {\r\n\t\t\t$MinimumNumberOfRDSH = [math]::Ceiling($nUserSessions / ($MaxUserSessionsPerVM * $MaxUserSessionsThreshold))\r\n\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Number of user sessions is more than $($MaxUserSessionsThreshold * 100) % of the max number of sessions allowed with minimum number of running session hosts required ($MaxUserSessionsThresholdCapacity). Adjusted minimum number of running session hosts required to $MinimumNumberOfRDSH\"\r\n\t\t}\r\n\r\n\t\t# Check if minimum number of session hosts are running\r\n\t\tif ($nRunningVMs -lt $MinimumNumberOfRDSH)\r\n {\r\n\t\t\t$res.nVMsToStart = $MinimumNumberOfRDSH - $nRunningVMs\r\n\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Number of running session host is less than minimum required. Need to start $($res.nVMsToStart) VMs\"\r\n\t\t}\r\n\t\t\r\n\t\tif ($InPeakHours)\r\n {\r\n\t\t\t[double]$nUserSessionsPerCore = $nUserSessions / $nRunningCores\r\n\t\t\t# In peak hours: check if current capacity is meeting the user demands\r\n\t\t\tif ($nUserSessionsPerCore -gt $SessionThresholdPerCPU)\r\n {\r\n\t\t\t\t$res.nCoresToStart = [math]::Ceiling(($nUserSessions / $SessionThresholdPerCPU) - $nRunningCores)\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"[In peak hours] Number of user sessions per Core is more than the threshold. Need to start $($res.nCoresToStart) cores\"\r\n\t\t\t}\r\n\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\tif ($nRunningVMs -gt $MinimumNumberOfRDSH)\r\n {\r\n\t\t\t# Calculate the number of session hosts to stop\r\n\t\t\t$res.nVMsToStop = $nRunningVMs - $MinimumNumberOfRDSH\r\n\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"[Off peak hours] Number of running session host is greater than minimum required. Need to stop $($res.nVMsToStop) VMs\"\r\n\t\t}\r\n\t}\r\n\r\n\tfunction TryUpdateSessionHostDrainMode\r\n {\r\n\t\t[CmdletBinding()]\r\n\t\tparam (\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[bool]$AllowNewSession,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[hashtable]$Header,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$HostPoolName,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$HostPoolResourceGroupName,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$ResourceManagerUrl,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$SessionHostName,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$SubscriptionId\r\n\t\t)\r\n\t\tBegin { }\r\n\t\tProcess \r\n {\r\n\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Update session host '$SessionHostName' to set allow new sessions to $AllowNewSession\"\r\n\t\t\ttry \r\n\t\t\t{\r\n\t\t\t\t$Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '/sessionHosts/' + $SessionHostName + '?api-version=2022-02-10-preview'\r\n\t\t\t\tInvoke-RestMethod -Headers $Header -Body (@{properties = @{allowNewSession = $AllowNewSession}} | ConvertTo-Json) -Method 'Patch' -Uri $Uri | Out-Null\r\n\t\t\t}\r\n\t\t\tcatch\r\n\t\t\t{\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message \"Failed to update the session host '$SessionHostName' to set allow new sessions to $($AllowNewSession): $($PSItem | Format-List -Force | Out-String)\"\r\n\t\t\t}\r\n\t\t}\r\n\t\tEnd { }\r\n\t}\r\n\r\n\tfunction TryForceLogOffUser\r\n {\r\n\t\t[CmdletBinding()]\r\n\t\tparam (\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[hashtable]$Header,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$HostPoolName,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$HostPoolResourceGroupName,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$ResourceManagerUrl,\r\n\r\n\t\t\t[Parameter(Mandatory = $true, ValueFromPipeline = $true)]\r\n\t\t\t$Session,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$SubscriptionId\r\n\t\t)\r\n\t\tBegin { }\r\n\t\tProcess\r\n {\r\n [string[]]$Toks = $Session.Name.Split('/')\r\n [string]$SessionHostName = $Toks[1]\r\n [string]$SessionID = $Toks[-1]\r\n [string]$User = $Session.ActiveDirectoryUserName\r\n\r\n\t\t\ttry \r\n\t\t\t{\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Force log off user: '$User', session ID: $SessionID\"\r\n\r\n\t\t\t\t$Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '/sessionHosts/' + $SessionHostName + '/userSessions/' + $SessionID + '?api-version=2022-02-10-preview&force=True'\r\n\t\t\t\tInvoke-RestMethod -Headers $Header -Method 'Delete' -Uri $Uri | Out-Null\r\n\t\t\t}\r\n\t\t\tcatch \r\n\t\t\t{\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message \"Failed to force log off user: '$User', session ID: $SessionID $($PSItem | Format-List -Force | Out-String)\"\r\n\t\t\t}\r\n\t\t}\r\n\t\tEnd { }\r\n\t}\r\n\r\n\tfunction TryResetSessionHostDrainModeAndUserSessions\r\n {\r\n\t\t[CmdletBinding()]\r\n\t\tparam (\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[hashtable]$Header,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$HostPoolName,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$HostPoolResourceGroupName,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$ResourceManagerUrl,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$SessionHostName,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[int]$SessionHostSessions,\r\n\r\n\t\t\t[Parameter(Mandatory = $true)]\r\n\t\t\t[string]$SubscriptionId\r\n\t\t)\r\n\t\tBegin { }\r\n\t\tProcess \r\n {\r\n\t\t\tTryUpdateSessionHostDrainMode -AllowNewSession $true -Header $Header -HostPoolName $HostPoolName -HostPoolResourceGroupName $HostPoolResourceGroupName -ResourceManagerUrl $ResourceManagerUrl -SessionHostName $SessionHostName -SubscriptionId $SubscriptionId\r\n\r\n\t\t\tif ($SessionHostSessions -eq 0)\r\n {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message \"Session host '$SessionHostName' still has $SessionHostSessions) sessions left behind in broker DB\"\r\n\r\n\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Get all user sessions from session host '$SessionHostName'\"\r\n\t\t\ttry \r\n\t\t\t{\r\n\t\t\t\t$Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '/sessionHosts/' + $SessionHostName + '/userSessions?api-version=2022-02-10-preview'\r\n\t\t\t\t$UserSessions = Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri\r\n\t\t\t}\r\n\t\t\tcatch \r\n\t\t\t{\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message \"Failed to retrieve user sessions of session host '$SessionHostName': $($PSItem | Format-List -Force | Out-String)\"\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Force log off $($UserSessions.Count) users on session host: '$SessionHostName'\"\r\n\t\t\t$UserSessions | TryForceLogOffUser -Header $Header -HostPoolName $HostPoolName -HostPoolResourceGroupName $HostPoolResourceGroupName -ResourceManagerUrl $ResourceManagerUrl -SubscriptionId $SubscriptionId\r\n\t\t}\r\n\t\tEnd { }\r\n\t}\r\n\t#endregion Functions\r\n\r\n\r\n\t# Note: https://stackoverflow.com/questions/41674518/powershell-setting-security-protocol-to-tls-1-2\r\n\t[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\r\n\r\n\r\n\t#region Azure Authentication\r\n $AccessToken = $null\r\n try\r\n {\r\n\t\t$TokenAuthURI = $env:IDENTITY_ENDPOINT + '?resource=' + $ResourceManagerUrl + '&api-version=2019-08-01'\r\n\t\t$TokenResponse = Invoke-RestMethod -Method Get -Headers @{\"X-IDENTITY-HEADER\"=\"$env:IDENTITY_HEADER\"} -Uri $TokenAuthURI\r\n\t\t$AccessToken = $TokenResponse.access_token\r\n\t\t$Header = @{\r\n\t\t\t'Content-Type'='application/json'\r\n\t\t\t'Authorization'='Bearer ' + $AccessToken\r\n\t\t}\r\n }\r\n catch\r\n {\r\n throw [System.Exception]::new('Failed to authenticate Azure with application ID, tenant ID, subscription ID', $PSItem.Exception)\r\n }\r\n Write-Log -HostPoolName $HostPoolName -Message \"Successfully authenticated with Azure using a managed identity\"\r\n\t#endregion Azure Authentication\r\n\r\n\r\n\t#region validate host pool, validate / update HostPool load balancer type, ensure there is at least 1 session host, get num of user sessions\r\n\t# Validate and get host pool info\r\n\t$HostPool = $null\r\n\ttry \r\n\t{\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Get host pool information\"\r\n\t\t$Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '?api-version=2022-02-10-preview'\r\n\t\t$HostPool = Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri\r\n\r\n\t\tif (!$HostPool) \r\n\t\t{\r\n\t\t\tthrow $HostPool\r\n\t\t}\r\n\t}\r\n\tcatch \r\n\t{\r\n\t\tthrow [System.Exception]::new(\"Failed to get host pool info of '$HostPoolName' in resource group '$HostPoolResourceGroupName'. Ensure that you have entered the correct values\", $PSItem.Exception)\r\n\t}\r\n\r\n\t# Ensure HostPool load balancer type is not persistent\r\n\tif ($HostPool.properties.loadBalancerType -ieq 'Persistent')\r\n {\r\n\t\tthrow \"HostPool '$HostPoolName' is configured with 'Persistent' load balancer type. Scaling tool only supports these load balancer types: BreadthFirst, DepthFirst\"\r\n\t}\r\n\r\n\tWrite-Log -HostPoolName $HostPoolName -Message 'Get session hosts'\r\n\t$Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '/sessionHosts?api-version=2022-02-10-preview'\r\n\t$SessionHosts = (Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri).value\r\n\r\n\tif (!$SessionHosts)\r\n {\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Message \"There are no session hosts in the host pool '$HostPoolName'. Ensure that hostpool has session hosts\"\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Message 'End'\r\n\t\treturn\r\n\t}\r\n\r\n\tWrite-Log -HostPoolName $HostPoolName -Message 'Get number of user sessions in host pool'\r\n\t$Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '/userSessions?api-version=2022-02-10-preview'\r\n\t[int]$nUserSessions = (Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri).value.Count\r\n\r\n\t# Set up breadth 1st load balacing type\r\n\t# Note: breadth 1st is enforced on AND off peak hours to simplify the things with scaling in the start/end of peak hours\r\n\tif (!$SkipUpdateLoadBalancerType -and $HostPool.properties.loadBalancerType -ine 'BreadthFirst')\r\n {\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Update HostPool with 'BreadthFirst' load balancer type (current: '$($HostPool.properties.loadBalancerType)')\"\r\n\r\n\t\t$Body = @{\r\n\t\t\tproperties = @{\r\n\t\t\t\tloadBalancerType = 'BreadthFirst'\r\n\t\t\t}\r\n\t\t}\r\n\t\t$Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '?api-version=2022-02-10-preview'\r\n\t\t$HostPool = Invoke-RestMethod -Headers $Header -Body $($Body | ConvertTo-Json) -Method 'Patch' -Uri $Uri\r\n\t}\r\n\tWrite-Log -HostPoolName $HostPoolName -Message \"Number of session hosts in the HostPool: $($SessionHosts.Count)\"\r\n\t#endregion\r\n\t\r\n\r\n\t# region Peak Hours #\r\n\t# Convert local time, begin peak time & end peak time from UTC to local time\r\n\t$CurrentDateTime = Get-LocalDateTime\r\n\t$BeginPeakDateTime = [datetime]::Parse($CurrentDateTime.ToShortDateString() + ' ' + $BeginPeakTime)\r\n\t$EndPeakDateTime = [datetime]::Parse($CurrentDateTime.ToShortDateString() + ' ' + $EndPeakTime)\r\n\r\n\t# Adjust peak times to make sure begin peak time is always before end peak time\r\n\tif ($EndPeakDateTime -lt $BeginPeakDateTime)\r\n {\r\n\t\tif ($CurrentDateTime -lt $EndPeakDateTime)\r\n {\r\n\t\t\t$BeginPeakDateTime = $BeginPeakDateTime.AddDays(-1)\r\n\t\t}\r\n\t\telse\r\n {\r\n\t\t\t$EndPeakDateTime = $EndPeakDateTime.AddDays(1)\r\n\t\t}\r\n\t}\r\n\r\n\tWrite-Log -HostPoolName $HostPoolName -Message \"Using current time: $($CurrentDateTime.ToString('yyyy-MM-dd HH:mm:ss')), begin peak time: $($BeginPeakDateTime.ToString('yyyy-MM-dd HH:mm:ss')), end peak time: $($EndPeakDateTime.ToString('yyyy-MM-dd HH:mm:ss'))\"\r\n\r\n\t[bool]$InPeakHours = ($BeginPeakDateTime -le $CurrentDateTime -and $CurrentDateTime -le $EndPeakDateTime)\r\n\tif ($InPeakHours)\r\n {\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Message 'In peak hours'\r\n\t}\r\n\telse\r\n {\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Message 'Off peak hours'\r\n\t}\r\n\t# endregion Peak Hours #\r\n\r\n\r\n\t#region get session hosts, VMs & user sessions info and compute workload\r\n\t# Note: session host is considered \"running\" if its running AND is in desired states AND allowing new sessions\r\n\t# Number of session hosts that are running, are in desired states and allowing new sessions\r\n\t[int]$nRunningVMs = 0\r\n\t# Number of cores that are running, are in desired states and allowing new sessions\r\n\t[int]$nRunningCores = 0\r\n\t# Array that contains all the virtual machine objects that are session hosts except the ones that are tagged for maintenance\r\n\t$VMs = @()\r\n\t# Object that contains the number of cores for each VM size SKU\r\n\t$VMSizeCores = @{}\r\n\t# Number of user sessions reported by each session host that is running, is in desired state and allowing new sessions\r\n\t[int]$nUserSessionsFromAllRunningVMs = 0\r\n\r\n\t# Populate all session hosts objects\r\n\tforeach ($SessionHost in $SessionHosts)\r\n {\r\n\t\t[string]$VirtualMachineResourceId = $SessionHost.properties.resourceId\r\n\t\t[string]$VirtualMachineName = $VirtualMachineResourceId.Split('/')[8]\r\n\t\t[string]$VirtualMachineResourceGroupName = $VirtualMachineResourceId.Split('/')[4]\r\n\t\t$Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $VirtualMachineResourceGroupName + '/providers/Microsoft.Compute/virtualMachines/' + $VirtualMachineName + '?api-version=2024-03-01&$expand=instanceView'\r\n\t\t$VirtualMachine = Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri\r\n\r\n\t\t# Throw an error if the virtual machine for the session host does not exist\r\n\t\tif ($VirtualMachine.error)\r\n\t\t{\r\n\t\t\tthrow \"The virtual machine for session host '$VirtualMachineName' does not exist\"\r\n\t\t}\r\n\t\t# Ignore session hosts tagged for maintenance or missing virtual machine\r\n\t\telseif($VirtualMachine.tags.Keys -contains $MaintenanceTagName)\r\n {\r\n\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"VM '$VirtualMachineName' is in maintenance and will be ignored\"\r\n\t\t\tcontinue\r\n\t\t}\r\n\t\telse \r\n\t\t{\r\n\t\t\t$VMs += $VirtualMachine\r\n\t\t}\r\n\r\n\t\t$PowerState = $VirtualMachine.properties.instanceView.statuses[1].displayStatus\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Session host: '$($VirtualMachineName)', power state: '$PowerState', status: '$($SessionHost.properties.status)', update state: '$($SessionHost.properties.updateState)', sessions: $($SessionHost.properties.sessions), allow new session: $($SessionHost.properties.allowNewSession)\"\r\n\t\t\r\n\t\t# Get the number of cores for VM size SKU\r\n\t\tif (!$VMSizeCores.ContainsKey($VirtualMachine.properties.hardwareProfile.vmSize))\r\n {\r\n\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Get VM sizes in $($VirtualMachine.location)\"\r\n\r\n\t\t\t$Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/providers/Microsoft.Compute/locations/' + $VirtualMachine.location + '/vmSizes?api-version=2024-03-01'\r\n\t\t\t$VMSizes = (Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri).value\r\n\r\n\t\t\tforeach ($VMSize in $VMSizes)\r\n {\r\n\t\t\t\tif (!$VMSizeCores.ContainsKey($VMSize.name))\r\n {\r\n\t\t\t\t\t$VMSizeCores.Add($VMSize.name, $VMSize.numberOfCores)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ($PowerState -ieq 'VM running')\r\n {\r\n\t\t\tif ($SessionHost.properties.status -notin $DesiredRunningStates)\r\n {\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message 'VM is in running state but session host is not and so it will be ignored (this could be because the VM was just started and has not connected to broker yet)'\r\n\t\t\t}\r\n\t\t\tif (!$SessionHost.properties.allowNewSession)\r\n {\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message 'VM is in running state but session host is not allowing new sessions and so it will be ignored'\r\n\t\t\t}\r\n\r\n\t\t\tif ($SessionHost.properties.status -in $DesiredRunningStates -and $SessionHost.properties.allowNewSession)\r\n {\r\n\t\t\t\t++$nRunningVMs\r\n\t\t\t\t$nRunningCores += $VMSizeCores[$VirtualMachine.properties.hardwareProfile.vmSize]\r\n\t\t\t\t$nUserSessionsFromAllRunningVMs += $SessionHost.properties.sessions\r\n\t\t\t}\r\n\t\t}\r\n\t\telse \r\n {\r\n\t\t\tif ($SessionHost.properties.status -in $DesiredRunningStates)\r\n {\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message \"VM is not in running state but session host is (this could be because the VM was just stopped and broker doesn't know that yet)\"\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif ($nUserSessionsFromAllRunningVMs -ne $nUserSessions)\r\n {\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message \"Sum of user sessions reported by every running session host ($nUserSessionsFromAllRunningVMs) is not equal to the total number of user sessions reported by the host pool ($nUserSessions)\"\r\n\t}\r\n\r\n\r\n\tif (!$nRunningCores)\r\n {\r\n\t\t$nRunningCores = 1\r\n\t}\r\n\r\n\tWrite-Log -HostPoolName $HostPoolName -Message \"Number of running session hosts: $nRunningVMs of total $($VMs.Count)\"\r\n\tWrite-Log -HostPoolName $HostPoolName -Message \"Number of user sessions: $nUserSessions of total allowed $($nRunningVMs * $HostPool.properties.maxSessionLimit)\"\r\n\tWrite-Log -HostPoolName $HostPoolName -Message \"Number of user sessions per Core: $($nUserSessions / $nRunningCores), threshold: $SessionThresholdPerCPU\"\r\n\tWrite-Log -HostPoolName $HostPoolName -Message \"Minimum number of running session hosts required: $MinimumNumberOfRDSH\"\r\n\r\n\t# Check if minimum num of running session hosts required is higher than max allowed\r\n\tif ($VMs.Count -le $MinimumNumberOfRDSH)\r\n {\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message 'Minimum number of RDSH is set higher than or equal to total number of session hosts'\r\n\t}\r\n\t#endregion\r\n\r\n\r\n\t#region determine number of session hosts to start/stop if any\r\n\t# Now that we have all the info about the session hosts & their usage, figure how many session hosts to start/stop depending on in/off peak hours and the demand [Ops = operations to perform]\r\n\t$Ops = @{\r\n\t\tnVMsToStart = 0\r\n\t\tnCoresToStart = 0\r\n\t\tnVMsToStop = 0\r\n\t}\r\n\r\n\tSet-nVMsToStartOrStop -HostPoolName $HostPoolName -nRunningVMs $nRunningVMs -nRunningCores $nRunningCores -nUserSessions $nUserSessions -MaxUserSessionsPerVM $HostPool.properties.maxSessionLimit -InPeakHours:$InPeakHours -Res $Ops\r\n\t#endregion\r\n\r\n\r\n\t#region start any session hosts if need to\r\n\t# Check if we have any session hosts to start\r\n\tif ($Ops.nVMsToStart -or $Ops.nCoresToStart)\r\n {\r\n\t\tif ($nRunningVMs -eq $VMs.Count)\r\n {\r\n\t\t\tWrite-Log -HostPoolName $HostPoolName -Message 'All session hosts are running'\r\n\t\t\tWrite-Log -HostPoolName $HostPoolName -Message 'End'\r\n\t\t\treturn\r\n\t\t}\r\n\r\n\t\t# Object that contains names of session hosts that will be started\r\n\t\t# $StartSessionHostFullNames = @{ }\r\n\t\t# Array that contains jobs of starting the session hosts\r\n\t\t[array]$StartedVMs = @()\r\n\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Message 'Find session hosts that are stopped and allowing new sessions'\r\n\t\tforeach ($SessionHost in $SessionHosts)\r\n {\r\n\t\t\t$VM = $VMs | Where-Object { $_.id -ieq $SessionHost.properties.resourceId }\r\n\t\t\tif (!$Ops.nVMsToStart -and !$Ops.nCoresToStart)\r\n {\r\n\t\t\t\t# Done with starting session hosts that needed to be\r\n\t\t\t\tbreak\r\n\t\t\t}\r\n\t\t\tif ($VM.properties.instanceView.statuses[1].displayStatus -ieq 'VM running')\r\n {\r\n\t\t\t\tcontinue\r\n\t\t\t}\r\n\t\t\tif ($SessionHost.properties.updateState -ine 'Succeeded')\r\n {\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message \"Session host '$($VM.name)' may not be healthy\"\r\n\t\t\t}\r\n\r\n\t\t\tif (!$SessionHost.properties.allowNewSession)\r\n {\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message \"Session host '$($VM.name)' is not allowing new sessions and so it will not be started\"\r\n\t\t\t\tcontinue\r\n\t\t\t}\r\n\r\n\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Start session host '$($VM.name)'\"\r\n\r\n\t\t\t$Uri = $ResourceManagerUrl + $VM.id.TrimStart('/') + '/start?api-version=2023-09-01'\r\n\t\t\tInvoke-RestMethod -Headers $Header -Method 'Post' -Uri $Uri | Out-Null\r\n\t\t\t$StartedVMs += $VM\r\n\t\t\t\t\r\n\t\t\t--$Ops.nVMsToStart\r\n\t\t\tif ($Ops.nVMsToStart -lt 0)\r\n {\r\n\t\t\t\t$Ops.nVMsToStart = 0\r\n\t\t\t}\r\n\r\n\t\t\t$Ops.nCoresToStart -= $VMSizeCores[$VM.properties.hardwareProfile.vmSize]\r\n\t\t\tif ($Ops.nCoresToStart -lt 0)\r\n {\r\n\t\t\t\t$Ops.nCoresToStart = 0\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t# Check if there were enough number of session hosts to start\r\n\t\tif ($Ops.nVMsToStart -or $Ops.nCoresToStart)\r\n {\r\n\t\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message \"Not enough session hosts to start. Still need to start maximum of either $($Ops.nVMsToStart) VMs or $($Ops.nCoresToStart) cores\"\r\n\t\t}\r\n\r\n\t\t# Wait for session hosts to start\r\n\t\twhile($StartedVMs.Count -gt 0)\r\n\t\t{\r\n\t\t\tforeach($StartedVM in $StartedVMs)\r\n\t\t\t{\r\n\t\t\t\t$Uri = $ResourceManagerUrl + $StartedVM.id.TrimStart('/') + '?api-version=2024-03-01'\r\n\t\t\t\t$VMAgentStatus = (Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri).properties.instanceView.vmAgent\r\n\t\t\t\tif ($VMAgentStatus)\r\n\t\t\t\t{\r\n\t\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Session host '$($StartedVM.name)' is running\"\r\n\t\t\t\t\t$StartedVMs = $StartedVMs -ne $StartedVM\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tStart-Sleep -Seconds 30\r\n\t\t}\r\n\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Message 'All session hosts have started'\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Message 'End'\r\n\t\treturn\r\n\t}\r\n\t#endregion\r\n\r\n\r\n\t#region stop any session hosts if need to\r\n\tif (!$Ops.nVMsToStop)\r\n {\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Message 'No need to start/stop any session hosts'\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Message 'End'\r\n\t\treturn\r\n\t}\r\n\r\n\t# Object that contains names of session hosts that will be stopped\r\n\t$VMsToStop = @()\r\n\t[array]$VMsToStopAfterLogOffTimeOut = @()\r\n\r\n\tWrite-Log -HostPoolName $HostPoolName -Message 'Find session hosts that are running and allowing new sessions, sort them by number of user sessions'\r\n\tforeach ($SessionHost in ($SessionHosts | Where-Object { $_.properties.allowNewSession } | Sort-Object { $_.properties.sessions }))\r\n {\r\n\t\t$VM = $VMs | Where-Object { $_.id -ieq $SessionHost.properties.resourceId }\r\n\t\tif ($VM.properties.instanceView.statuses[1].displayStatus -ieq 'VM running')\r\n\t\t{\r\n\t\t\tif (!$Ops.nVMsToStop)\r\n\t\t\t{\r\n\t\t\t\t# Done with stopping session hosts that needed to be\r\n\t\t\t\tbreak\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif ($SessionHost.properties.sessions -gt 0 -and !$LimitSecondsToForceLogOffUser)\r\n\t\t\t{\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message \"Session host '$($VM.name)' has $($SessionHost.properties.sessions) sessions but limit seconds to force log off user is set to 0, so will not stop any more session hosts (https://aka.ms/wvdscale#how-the-scaling-tool-works)\"\r\n\t\t\t\t# Note: why break ? Because the list this loop iterates through is sorted by number of sessions, if it hits this, the rest of items in the loop will also hit this\r\n\t\t\t\tbreak\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tTryUpdateSessionHostDrainMode -AllowNewSession $false -Header $Header -HostPoolName $HostPoolName -HostPoolResourceGroupName $HostPoolResourceGroupName -ResourceManagerUrl $ResourceManagerUrl -SessionHostName $VM.name -SubscriptionId $SubscriptionId\r\n\r\n\t\t\t# Note: check if there were new user sessions since session host info was 1st fetched\r\n\t\t\tif ($SessionHost.properties.sessions -gt 0 -and !$LimitSecondsToForceLogOffUser)\r\n\t\t\t{\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message \"Session host '$($VM.name)' has $($SessionHost.properties.sessions) sessions but limit seconds to force log off user is set to 0, so will not stop any more session hosts (https://aka.ms/wvdscale#how-the-scaling-tool-works)\"\r\n\t\t\t\tTryUpdateSessionHostDrainMode -AllowNewSession $true -Header $Header -HostPoolName $HostPoolName -HostPoolResourceGroupName $HostPoolResourceGroupName -ResourceManagerUrl $ResourceManagerUrl -SessionHostName $VM.name -SubscriptionId $SubscriptionId \r\n\t\t\t\tcontinue\r\n\t\t\t}\r\n\r\n\t\t\tif ($SessionHost.properties.sessions -gt 0)\r\n\t\t\t{\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Get all user sessions from session host '$($VM.name)'\"\r\n\t\t\t\ttry \r\n\t\t\t\t{\r\n\t\t\t\t\t$Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '/sessionHosts/' + $VM.name + '/userSessions?api-version=2022-02-10-preview'\r\n\t\t\t\t\t$UserSessions = Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri\r\n\t\t\t\t}\r\n\t\t\t\tcatch\r\n\t\t\t\t{\r\n\t\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message \"Failed to retrieve user sessions of session host '$($VM.name)': $($PSItem | Format-List -Force | Out-String)\"\r\n\t\t\t\t}\r\n\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Send log off message to active user sessions on session host: '$($VM.name)'\"\r\n\t\t\t\tforeach ($UserSession in $UserSessions)\r\n\t\t\t\t{\r\n\t\t\t\t\tif($UserSession.properties.sessionState -ine 'Active')\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tcontinue\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t[string]$SessionID = $UserSession.name.Split('/')[-1]\r\n\t\t\t\t\t[string]$User = $UserSession.properties.activeDirectoryUserName\r\n\t\t\t\t\t\r\n\t\t\t\t\ttry \r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Send a log off message to user: '$User', session ID: $SessionID\"\r\n\r\n\t\t\t\t\t\t$Uri = $ResourceManagerUrl + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $HostPoolResourceGroupName + '/providers/Microsoft.DesktopVirtualization/hostPools/' + $HostPoolName + '/sessionHosts/' + $VM.name + '/userSessions/' + $SessionID + '/sendMessage?api-version=2022-02-10-preview'\r\n\t\t\t\t\t\tInvoke-RestMethod -Headers $Header -Method 'Post' -Uri $Uri -Body (@{ 'messageTitle' = $LogOffMessageTitle; 'messageBody' = \"$LogOffMessageBody You will be logged off in $LimitSecondsToForceLogOffUser seconds\" } | ConvertTo-Json) | Out-Null\r\n\t\t\t\t\t}\r\n\t\t\t\t\tcatch \r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message \"Failed to send a log off message to user: '$User', session ID: $SessionID $($PSItem | Format-List -Force | Out-String)\"\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t$VMsToStopAfterLogOffTimeOut += $VM\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Stop session host '$($VM.name)'\"\r\n\t\t\t\t$Uri = $ResourceManagerUrl + $VM.id.TrimStart('/') + '/deallocate?api-version=2023-09-01'\r\n\t\t\t\tInvoke-RestMethod -Headers $Header -Method 'Post' -Uri $Uri | Out-Null\r\n\t\t\t\t$VMsToStop += $VM\r\n\t\t\t}\r\n\r\n\t\t\t--$Ops.nVMsToStop\r\n\t\t\tif ($Ops.nVMsToStop -lt 0) {\r\n\t\t\t\t$Ops.nVMsToStop = 0\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif ($VMsToStopAfterLogOffTimeOut)\r\n {\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Wait $LimitSecondsToForceLogOffUser seconds for users to log off\"\r\n\r\n\t\tStart-Sleep -Seconds $LimitSecondsToForceLogOffUser\r\n\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Force log off users and stop remaining $($VMsToStopAfterLogOffTimeOut.Count) session hosts\"\r\n\t\tforeach ($VM in $VMsToStopAfterLogOffTimeOut)\r\n {\r\n\t\t\t$SessionHost = $SessionHosts | Where-Object { $_.properties.resourceId -ieq $VM.id }\r\n\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Force log off $($SessionHost.sessions) users on session host: '$($VM.name)'\"\r\n\t\t\t$VM.UserSessions | TryForceLogOffUser -Header $Header -HostPoolName $HostPoolName -HostPoolResourceGroupName $HostPoolResourceGroupName -ResourceManagerUrl $ResourceManagerUrl -SubscriptionId $SubscriptionId\r\n\t\t\t\r\n\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Stop session host '$($VM.name)'\"\r\n\t\t\t$Uri = $ResourceManagerUrl + $VM.id.TrimStart('/') + '/deallocate?api-version=2023-09-01'\r\n\t\t\tInvoke-RestMethod -Headers $Header -Method 'Post' -Uri $Uri | Out-Null\r\n\t\t\t$VMsToStop += $VM\r\n\t\t}\r\n\t}\r\n\r\n\t# Check if there were enough number of session hosts to stop\r\n\tif ($Ops.nVMsToStop)\r\n {\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Warn -Message \"Not enough session hosts to stop. Still need to stop $($Ops.nVMsToStop) VMs\"\r\n\t}\r\n\r\n\t# Wait for the session hosts to stop / deallocate\r\n\tWrite-Log -HostPoolName $HostPoolName -Message \"Wait for session hosts to stop / deallocate\"\r\n\twhile($VMsToStop.Count -gt 0)\r\n\t{\r\n\t\tforeach($VMToStop in $VMsToStop)\r\n\t\t{\r\n\t\t\t$Uri = $ResourceManagerUrl + $VMToStop.id.TrimStart('/') + '?$expand=instanceView&api-version=2024-03-01'\r\n\t\t\t$VMPowerState = (Invoke-RestMethod -Headers $Header -Method 'Get' -Uri $Uri).properties.instanceView.statuses[1].displayStatus\r\n\t\t\tif ($VMPowerState -eq 'VM deallocated')\r\n\t\t\t{\r\n\t\t\t\tWrite-Log -HostPoolName $HostPoolName -Message \"Session host '$($VMToStop.name)' is stopping\"\r\n\t\t\t\t$SessionHost = $SessionHosts | Where-Object { $_.properties.resourceId -ieq $VMToStop.id }\r\n\t\t\t\tTryResetSessionHostDrainModeAndUserSessions -Header $Header -HostPoolName $HostPoolName -HostPoolResourceGroupName $HostPoolResourceGroupName -ResourceManagerUrl $ResourceManagerUrl -SessionHostName $VMToStop.name -SessionHostSessions $SessionHost.properties.sessions -SubscriptionId $SubscriptionId\r\n\t\t\t\t$VMsToStop = $VMsToStop -ne $VMToStop\r\n\t\t\t}\r\n\t\t}\r\n\t\tStart-Sleep -Seconds 30\r\n\t}\r\n\r\n\tWrite-Log -HostPoolName $HostPoolName -Message 'All required session hosts have stopped.'\r\n\tWrite-Log -HostPoolName $HostPoolName -Message 'End'\r\n\treturn\r\n\t#endregion\r\n}\r\ncatch \r\n{\r\n\t$ErrContainer = $PSItem\r\n\t# $ErrContainer = $_\r\n\r\n\t[string]$ErrMsg = $ErrContainer | Format-List -Force | Out-String\r\n\t$ErrMsg += \"Version: $Version`n\"\r\n\r\n\tif (Get-Command 'Write-Log' -ErrorAction:SilentlyContinue)\r\n {\r\n\t\tWrite-Log -HostPoolName $HostPoolName -Err -Message $ErrMsg -ErrorAction:Continue\r\n\t}\r\n\telse\r\n {\r\n\t\tWrite-Error $ErrMsg -ErrorAction:Continue\r\n\t}\r\n\r\n\tthrow [System.Exception]::new($ErrMsg, $ErrContainer.Exception)\r\n}", - "$fxv#2": "# Authentication is provided in the script", + "$fxv#0": "Param(\r\n [string]$HostPoolResourceId,\r\n [string]$ResourceGroupName,\r\n [string]$ResourceManagerUri,\r\n [string]$ScalingPlanName,\r\n [string]$SubscriptionId,\r\n [string]$UserAssignedIdentityClientId\r\n)\r\n\r\n$ErrorActionPreference = 'Stop'\r\n$WarningPreference = 'SilentlyContinue'\r\n\r\n# Fix the resource manager URI since only AzureCloud contains a trailing slash\r\n$ResourceManagerUriFixed = if($ResourceManagerUri[-1] -eq '/'){$ResourceManagerUri} else {$ResourceManagerUri + '/'}\r\n\r\n# Get an access token for Azure resources\r\n$AzureManagementAccessToken = (Invoke-RestMethod `\r\n -Headers @{Metadata=\"true\"} `\r\n -Uri $('http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' + $ResourceManagerUriFixed + '&client_id=' + $UserAssignedIdentityClientId)).access_token\r\n\r\n# Set header for Azure Management API\r\n$AzureManagementHeader = @{\r\n 'Content-Type'='application/json'\r\n 'Authorization'='Bearer ' + $AzureManagementAccessToken\r\n}\r\n\r\n# Check if the scaling plan exists: https://learn.microsoft.com/rest/api/desktopvirtualization/scaling-plans/list-by-resource-group\r\n$ScalingPlanExists = (Invoke-RestMethod `\r\n -Headers $AzureManagementHeader `\r\n -Method 'GET' `\r\n -Uri $($ResourceManagerUriFixed + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $ResourceGroupName + '/providers/Microsoft.DesktopVirtualization/scalingPlans?api-version=2023-09-05')).value | Where-Object {$_.name -eq $ScalingPlanName}\r\n\r\n# Disable autoscale for the host pool: https://learn.microsoft.com/rest/api/desktopvirtualization/scaling-plans/update\r\nif ($ScalingPlanExists)\r\n{\r\n Invoke-RestMethod `\r\n -Body (@{properties = @{hostPoolReferences = @(@{hostPoolArmPath = $HostPoolResourceId; scalingPlanEnabled = $false})}} | ConvertTo-Json -Depth 3) `\r\n -Headers $AzureManagementHeader `\r\n -Method 'PATCH' `\r\n -Uri $($ResourceManagerUriFixed + 'subscriptions/' + $SubscriptionId + '/resourceGroups/' + $ResourceGroupName + '/providers/Microsoft.DesktopVirtualization/scalingPlans/' + $ScalingPlanName + '?api-version=2023-09-05') | Out-Null\r\n}", + "$fxv#1": "param (\r\n [string]$ImageOffer,\r\n [string]$ImagePublisher,\r\n [string]$ImageSku,\r\n [string]$ResourceManagerUri,\r\n [string]$SubscriptionId,\r\n [string]$UserAssignedIdentityClientId\r\n)\r\n\r\n# Fix the resource manager URI since only AzureCloud contains a trailing slash\r\n$ResourceManagerUriFixed = if($ResourceManagerUri[-1] -eq '/'){$ResourceManagerUri} else {$ResourceManagerUri + '/'}\r\n\r\n# Get an access token for Azure resources\r\n$AzureManagementAccessToken = (Invoke-RestMethod `\r\n -Headers @{Metadata=\"true\"} `\r\n -Uri $('http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' + $ResourceManagerUriFixed + '&client_id=' + $UserAssignedIdentityClientId)).access_token\r\n\r\n# Set header for Azure Management API\r\n$AzureManagementHeader = @{\r\n 'Content-Type'='application/json'\r\n 'Authorization'='Bearer ' + $AzureManagementAccessToken\r\n}\r\n\r\n# Use the access token to get the marketplace agreement\r\n$Terms = Invoke-RestMethod `\r\n -Headers $AzureManagementHeader `\r\n -Method 'GET' `\r\n -Uri $($ResourceManagerUriFixed + 'subscriptions/' + $SubscriptionId + '/providers/Microsoft.MarketplaceOrdering/agreements/' + $ImagePublisher + '/offers/' + $ImageOffer + '/plans/' + $ImageSku + '?api-version=2021-01-01')\r\n\r\n# Use the access token to set the marketplace agreement\r\nif($Terms.error)\r\n{\r\n Invoke-RestMethod `\r\n -Headers $AzureManagementHeader `\r\n -Method 'POST' `\r\n -Uri $($ResourceManagerUriFixed + 'subscriptions/' + $SubscriptionId + '/providers/Microsoft.MarketplaceOrdering/agreements/' + $ImagePublisher + '/offers/' + $ImageOffer + '/plans/' + $ImageSku + '/sign?api-version=2021-01-01') | Out-Null\r\n}", "availabilitySetNamePrefix": "[parameters('namingConvention').availabilitySet]", - "tagsAvailabilitySets": "[union(createObject('cm-resource-parent', format('{0}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupManagement'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.Compute/availabilitySets'), parameters('tags')['Microsoft.Compute/availabilitySets'], createObject()), parameters('mlzTags'))]", - "tagsNetworkInterfaces": "[union(createObject('cm-resource-parent', format('{0}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupManagement'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.Network/networkInterfaces'), parameters('tags')['Microsoft.Network/networkInterfaces'], createObject()), parameters('mlzTags'))]", - "tagsRecoveryServicesVault": "[union(createObject('cm-resource-parent', format('{0}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupManagement'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.recoveryServices/vaults'), parameters('tags')['Microsoft.recoveryServices/vaults'], createObject()), parameters('mlzTags'))]", - "tagsVirtualMachines": "[union(createObject('cm-resource-parent', format('{0}/resourceGroups/{1}/providers/Microsoft.DesktopVirtualization/hostpools/{2}', subscription().id, parameters('resourceGroupManagement'), parameters('hostPoolName'))), if(contains(parameters('tags'), 'Microsoft.Compute/virtualMachines'), parameters('tags')['Microsoft.Compute/virtualMachines'], createObject()), parameters('mlzTags'))]", + "tagsVirtualMachines": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Compute/virtualMachines'), createObject()), parameters('mlzTags'))]", "uniqueToken": "[uniqueString(parameters('identifier'), parameters('environmentAbbreviation'), subscription().subscriptionId)]", "virtualMachineNamePrefix": "[replace(parameters('namingConvention').virtualMachine, parameters('serviceToken'), '')]" }, "resources": [ { - "condition": "[and(parameters('pooledHostPool'), equals(parameters('availability'), 'AvailabilitySets'))]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2023-07-01", + "name": "[parameters('resourceGroupName')]", + "location": "[parameters('location')]", + "tags": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Resources/resourceGroups'), createObject()), parameters('mlzTags'))]" + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('assign-policy-diskAccess-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "diskAccessResourceId": { + "value": "[parameters('diskAccessResourceId')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "policyDefinitionId": { + "value": "[parameters('diskAccessPolicyDefinitionId')]" + }, + "policyDisplayName": { + "value": "[parameters('diskAccessPolicyDisplayName')]" + }, + "policyName": { + "value": "[parameters('diskAccessPolicyDisplayName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.92.45157", + "templateHash": "1275953978872597894" + } + }, + "parameters": { + "diskAccessResourceId": { + "type": "string" + }, + "location": { + "type": "string" + }, + "policyDefinitionId": { + "type": "string" + }, + "policyDisplayName": { + "type": "string" + }, + "policyName": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/policyAssignments", + "apiVersion": "2022-06-01", + "name": "[parameters('policyName')]", + "location": "[parameters('location')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "displayName": "[parameters('policyDisplayName')]", + "policyDefinitionId": "[parameters('policyDefinitionId')]", + "parameters": "[if(not(empty(parameters('diskAccessResourceId'))), createObject('diskAccessId', createObject('value', parameters('diskAccessResourceId'))), createObject())]" + } + } + ] + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" + ] + }, + { + "condition": "[and(equals(parameters('hostPoolType'), 'Pooled'), equals(parameters('availability'), 'AvailabilitySets'))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('deploy-avail-{0}', parameters('deploymentNameSuffix'))]", - "resourceGroup": "[parameters('resourceGroupHosts')]", + "name": "[format('deploy-avSets-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[parameters('resourceGroupName')]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -12846,7 +12105,7 @@ "value": "[parameters('location')]" }, "tagsAvailabilitySets": { - "value": "[variables('tagsAvailabilitySets')]" + "value": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Compute/availabilitySets'), createObject()), parameters('mlzTags'))]" } }, "template": { @@ -12855,8 +12114,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "4247819304130517352" + "version": "0.31.92.45157", + "templateHash": "12575584281240773385" } }, "parameters": { @@ -12897,32 +12156,244 @@ } ] } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" + ] + }, + { + "copy": { + "name": "roleAssignments", + "count": "[length(range(0, length(parameters('securityPrincipalObjectIds'))))]" + }, + "condition": "[contains(parameters('activeDirectorySolution'), 'EntraId')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('assign-role-{0}-{1}', range(0, length(parameters('securityPrincipalObjectIds')))[copyIndex()], parameters('deploymentNameSuffix'))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "principalId": { + "value": "[parameters('securityPrincipalObjectIds')[range(0, length(parameters('securityPrincipalObjectIds')))[copyIndex()]]]" + }, + "principalType": { + "value": "Group" + }, + "roleDefinitionId": { + "value": "fb879df8-f326-4884-b1cf-06f3ad86be52" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.92.45157", + "templateHash": "10174155731090308034" + } + }, + "parameters": { + "principalId": { + "type": "string" + }, + "principalType": { + "type": "string" + }, + "roleDefinitionId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('principalId'), parameters('roleDefinitionId'), resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionId'))]", + "principalId": "[parameters('principalId')]", + "principalType": "[parameters('principalType')]" + } + } + ] + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('deploy-disableAutoscale-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[parameters('resourceGroupManagement')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "name": { + "value": "Disable-Autoscale" + }, + "parameters": { + "value": [ + { + "name": "HostPoolResourceId", + "value": "[parameters('hostPoolResourceId')]" + }, + { + "name": "ResourceGroupName", + "value": "[parameters('resourceGroupManagement')]" + }, + { + "name": "ResourceManagerUri", + "value": "[environment().resourceManager]" + }, + { + "name": "ScalingPlanName", + "value": "[parameters('namingConvention').scalingPlan]" + }, + { + "name": "SubscriptionId", + "value": "[subscription().subscriptionId]" + }, + { + "name": "UserAssignedidentityClientId", + "value": "[parameters('deploymentUserAssignedIdentityClientId')]" + } + ] + }, + "script": { + "value": "[variables('$fxv#0')]" + }, + "tags": { + "value": "[variables('tagsVirtualMachines')]" + }, + "virtualMachineName": { + "value": "[parameters('managementVirtualMachineName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.92.45157", + "templateHash": "6896852651895359166" + } + }, + "parameters": { + "asyncExecution": { + "type": "bool", + "defaultValue": false + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parameters": { + "type": "array", + "defaultValue": [] + }, + "script": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "treatFailureAsDeploymentFailure": { + "type": "bool", + "defaultValue": true + }, + "virtualMachineName": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Compute/virtualMachines/runCommands", + "apiVersion": "2023-09-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "asyncExecution": "[parameters('asyncExecution')]", + "parameters": "[parameters('parameters')]", + "source": { + "script": "[parameters('script')]" + }, + "treatFailureAsDeploymentFailure": "[parameters('treatFailureAsDeploymentFailure')]" + } + } + ] + } } }, { - "copy": { - "name": "roleAssignments", - "count": "[length(range(0, length(parameters('securityPrincipalObjectIds'))))]" - }, - "condition": "[not(contains(parameters('activeDirectorySolution'), 'DomainServices'))]", + "condition": "[equals(parameters('profile'), 'ArcGISPro')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('deploy-role-assignments-{0}-{1}', range(0, length(parameters('securityPrincipalObjectIds')))[copyIndex()], parameters('deploymentNameSuffix'))]", - "resourceGroup": "[parameters('resourceGroupHosts')]", + "name": "[format('set-marketplaceTerms-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[parameters('resourceGroupManagement')]", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "principalId": { - "value": "[parameters('securityPrincipalObjectIds')[range(0, length(parameters('securityPrincipalObjectIds')))[copyIndex()]]]" + "location": { + "value": "[parameters('location')]" }, - "principalType": { - "value": "Group" + "name": { + "value": "Set-AzureMarketplaceTerms" }, - "roleDefinitionId": { - "value": "[parameters('roleDefinitions').VirtualMachineUserLogin]" + "parameters": { + "value": [ + { + "name": "ImageOffer", + "value": "[parameters('imageOffer')]" + }, + { + "name": "ImagePublisher", + "value": "[parameters('imagePublisher')]" + }, + { + "name": "ImageSku", + "value": "[parameters('imageSku')]" + }, + { + "name": "ResourceManagerUri", + "value": "[environment().resourceManager]" + }, + { + "name": "SubscriptionId", + "value": "[subscription().subscriptionId]" + }, + { + "name": "UserAssignedidentityClientId", + "value": "[parameters('deploymentUserAssignedIdentityClientId')]" + } + ] + }, + "script": { + "value": "[variables('$fxv#1')]" + }, + "tags": { + "value": "[variables('tagsVirtualMachines')]" + }, + "virtualMachineName": { + "value": "[parameters('managementVirtualMachineName')]" } }, "template": { @@ -12931,35 +12402,61 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "15359000832124286075" + "version": "0.31.92.45157", + "templateHash": "6896852651895359166" } }, "parameters": { - "principalId": { + "asyncExecution": { + "type": "bool", + "defaultValue": false + }, + "location": { "type": "string" }, - "principalType": { + "name": { "type": "string" }, - "roleDefinitionId": { + "parameters": { + "type": "array", + "defaultValue": [] + }, + "script": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "treatFailureAsDeploymentFailure": { + "type": "bool", + "defaultValue": true + }, + "virtualMachineName": { "type": "string" } }, "resources": [ { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(parameters('principalId'), parameters('roleDefinitionId'), resourceGroup().id)]", + "type": "Microsoft.Compute/virtualMachines/runCommands", + "apiVersion": "2023-09-01", + "name": "[format('{0}/{1}', parameters('virtualMachineName'), parameters('name'))]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roleDefinitionId'))]", - "principalId": "[parameters('principalId')]", - "principalType": "[parameters('principalType')]" + "asyncExecution": "[parameters('asyncExecution')]", + "parameters": "[parameters('parameters')]", + "source": { + "script": "[parameters('script')]" + }, + "treatFailureAsDeploymentFailure": "[parameters('treatFailureAsDeploymentFailure')]" } } ] } - } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-disableAutoscale-{0}', parameters('deploymentNameSuffix')))]" + ] }, { "copy": { @@ -12971,7 +12468,7 @@ "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "[format('deploy-vms-{0}-{1}', sub(range(1, parameters('sessionHostBatchCount'))[copyIndex()], 1), parameters('deploymentNameSuffix'))]", - "resourceGroup": "[parameters('resourceGroupHosts')]", + "resourceGroup": "[parameters('resourceGroupName')]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -12990,6 +12487,9 @@ "availabilityZones": { "value": "[parameters('availabilityZones')]" }, + "avdConfigurationZipFileName": { + "value": "[parameters('avdConfigurationZipFileName')]" + }, "batchCount": { "value": "[range(1, parameters('sessionHostBatchCount'))[copyIndex()]]" }, @@ -13041,9 +12541,6 @@ "hostPoolName": { "value": "[parameters('hostPoolName')]" }, - "hostPoolType": { - "value": "[parameters('hostPoolType')]" - }, "imageVersionResourceId": { "value": "[parameters('imageVersionResourceId')]" }, @@ -13065,8 +12562,8 @@ "organizationalUnitPath": { "value": "[parameters('organizationalUnitPath')]" }, - "resourceGroupControlPlane": { - "value": "[parameters('resourceGroupControlPlane')]" + "profile": { + "value": "[parameters('profile')]" }, "resourceGroupManagement": { "value": "[parameters('resourceGroupManagement')]" @@ -13095,7 +12592,7 @@ "value": "[parameters('subnetResourceId')]" }, "tagsNetworkInterfaces": { - "value": "[variables('tagsNetworkInterfaces')]" + "value": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.Network/networkInterfaces'), createObject()), parameters('mlzTags'))]" }, "tagsVirtualMachines": { "value": "[variables('tagsVirtualMachines')]" @@ -13122,8 +12619,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10616160910124347908" + "version": "0.31.92.45157", + "templateHash": "10645307132280074356" } }, "parameters": { @@ -13139,6 +12636,9 @@ "availabilityZones": { "type": "array" }, + "avdConfigurationZipFileName": { + "type": "string" + }, "batchCount": { "type": "int" }, @@ -13190,9 +12690,6 @@ "hostPoolName": { "type": "string" }, - "hostPoolType": { - "type": "string" - }, "imageOffer": { "type": "string" }, @@ -13220,7 +12717,7 @@ "organizationalUnitPath": { "type": "string" }, - "resourceGroupControlPlane": { + "profile": { "type": "string" }, "resourceGroupManagement": { @@ -13296,7 +12793,7 @@ "fslogixOfficeShare": "[format('\\\\{0}.file.{1}\\office-containers\\*\\*.VHDX', variables('storageAccountToken'), parameters('storageSuffix'))]", "fslogixProfileShare": "[format('\\\\{0}.file.{1}\\profile-containers\\*\\*.VHDX', variables('storageAccountToken'), parameters('storageSuffix'))]", "imageReference": "[if(empty(parameters('imageVersionResourceId')), createObject('publisher', parameters('imagePublisher'), 'offer', parameters('imageOffer'), 'sku', parameters('imageSku'), 'version', 'latest'), createObject('id', parameters('imageVersionResourceId')))]", - "intune": "[contains(parameters('activeDirectorySolution'), 'intuneEnrollment')]", + "intune": "[contains(parameters('activeDirectorySolution'), 'IntuneEnrollment')]", "nvidiaVmSize": "[contains(variables('nvidiaVmSizes'), parameters('virtualMachineSize'))]", "nvidiaVmSizes": [ "Standard_NV6", @@ -13316,9 +12813,8 @@ "Standard_NV36adms_A10_v5", "Standard_NV72ads_A10_v5" ], - "pooledHostPool": "[equals(split(parameters('hostPoolType'), ' ')[0], 'Pooled')]", "sessionHostNamePrefix": "[replace(parameters('virtualMachineNamePrefix'), parameters('serviceToken'), '')]", - "storageAccountToken": "[take(format('{0}??{1}', parameters('storageAccountPrefix'), parameters('uniqueToken')), 24)]" + "storageAccountToken": "[format('{0}??', parameters('storageAccountPrefix'))]" }, "resources": [ { @@ -13359,6 +12855,10 @@ "name": "[format('{0}{1}', variables('sessionHostNamePrefix'), padLeft(add(range(0, parameters('sessionHostCount'))[copyIndex()], parameters('sessionHostIndex')), 4, '0'))]", "location": "[parameters('location')]", "tags": "[parameters('tagsVirtualMachines')]", + "identity": { + "type": "SystemAssigned" + }, + "plan": "[if(equals(parameters('profile'), 'ArcGISPro'), createObject('name', parameters('imageSku'), 'publisher', parameters('imagePublisher'), 'product', parameters('imageOffer')), null())]", "zones": "[if(equals(parameters('availability'), 'AvailabilityZones'), createArray(parameters('availabilityZones')[mod(range(0, parameters('sessionHostCount'))[copyIndex()], length(parameters('availabilityZones')))]), null())]", "properties": { "availabilitySet": "[if(equals(parameters('availability'), 'AvailabilitySets'), createObject('id', resourceId('Microsoft.Compute/availabilitySets', format('{0}-{1}', parameters('availabilitySetNamePrefix'), padLeft(div(add(range(0, parameters('sessionHostCount'))[copyIndex()], parameters('sessionHostIndex')), 200), 2, '0')))), null())]", @@ -13543,7 +13043,7 @@ "typeHandlerVersion": "2.73", "autoUpgradeMinorVersion": true, "settings": { - "modulesUrl": "[format('https://wvdportalstorageblob.blob.{0}/galleryartifacts/Configuration_1.0.02721.349.zip', environment().suffixes.storage)]", + "modulesUrl": "[format('https://wvdportalstorageblob.blob.{0}/galleryartifacts/{1}', environment().suffixes.storage, parameters('avdConfigurationZipFileName'))]", "configurationFunction": "Configuration.ps1\\AddSessionHost", "properties": { "hostPoolName": "[parameters('hostPoolName')]", @@ -13551,14 +13051,14 @@ "UserName": "PLACEHOLDER_DO_NOT_USE", "Password": "PrivateSettingsRef:RegistrationInfoToken" }, - "aadJoin": "[not(contains(parameters('activeDirectorySolution'), 'DomainServices'))]", + "aadJoin": "[contains(parameters('activeDirectorySolution'), 'EntraId')]", "UseAgentDownloadEndpoint": false, "mdmId": "[if(variables('intune'), '0000000a-0000-0000-c000-000000000000', '')]" } }, "protectedSettings": { "Items": { - "RegistrationInfoToken": "[listRegistrationTokens(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupControlPlane')), 'Microsoft.DesktopVirtualization/hostPools', parameters('hostPoolName')), '2023-09-05').value[0].token]" + "RegistrationInfoToken": "[listRegistrationTokens(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.DesktopVirtualization/hostPools', parameters('hostPoolName')), '2023-09-05').value[0].token]" } } }, @@ -13605,7 +13105,7 @@ "name": "extension_AADLoginForWindows", "count": "[length(range(0, parameters('sessionHostCount')))]" }, - "condition": "[not(contains(parameters('activeDirectorySolution'), 'DomainServices'))]", + "condition": "[contains(parameters('activeDirectorySolution'), 'EntraId')]", "type": "Microsoft.Compute/virtualMachines/extensions", "apiVersion": "2021-03-01", "name": "[format('{0}/{1}', format('{0}{1}', variables('sessionHostNamePrefix'), padLeft(add(range(0, parameters('sessionHostCount'))[range(0, parameters('sessionHostCount'))[copyIndex()]], parameters('sessionHostIndex')), 4, '0')), 'AADLoginForWindows')]", @@ -13759,8 +13259,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17500708096438980911" + "version": "0.31.92.45157", + "templateHash": "6896852651895359166" } }, "parameters": { @@ -13817,7 +13317,7 @@ ] }, { - "condition": "[and(parameters('enableDrainMode'), variables('pooledHostPool'))]", + "condition": "[parameters('enableDrainMode')]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "[format('deploy-drain-mode-{0}-{1}', parameters('batchCount'), parameters('deploymentNameSuffix'))]", @@ -13846,7 +13346,7 @@ }, { "name": "HostPoolResourceGroupName", - "value": "[parameters('resourceGroupControlPlane')]" + "value": "[parameters('resourceGroupManagement')]" }, { "name": "ResourceManagerUri", @@ -13894,8 +13394,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17500708096438980911" + "version": "0.31.92.45157", + "templateHash": "6896852651895359166" } }, "parameters": { @@ -13954,14 +13454,17 @@ } }, "dependsOn": [ - "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupHosts')), 'Microsoft.Resources/deployments', format('deploy-avail-{0}', parameters('deploymentNameSuffix')))]" + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('deploy-avSets-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-disableAutoscale-{0}', parameters('deploymentNameSuffix')))]", + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('set-marketplaceTerms-{0}', parameters('deploymentNameSuffix')))]" ] }, { - "condition": "[and(parameters('enableRecoveryServices'), contains(parameters('hostPoolType'), 'Personal'))]", + "condition": "[and(parameters('enableRecoveryServices'), equals(parameters('hostPoolType'), 'Personal'))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('deploy-recovery-services-{0}', parameters('deploymentNameSuffix'))]", + "name": "[format('deploy-recoveryServices-{0}', parameters('deploymentNameSuffix'))]", "resourceGroup": "[parameters('resourceGroupManagement')]", "properties": { "expressionEvaluationOptions": { @@ -13988,7 +13491,7 @@ "value": "[parameters('recoveryServicesVaultName')]" }, "resourceGroupHosts": { - "value": "[parameters('resourceGroupHosts')]" + "value": "[parameters('resourceGroupName')]" }, "resourceGroupManagement": { "value": "[parameters('resourceGroupManagement')]" @@ -14000,7 +13503,7 @@ "value": "[parameters('sessionHostIndex')]" }, "tagsRecoveryServicesVault": { - "value": "[variables('tagsRecoveryServicesVault')]" + "value": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.recoveryServices/vaults'), createObject()), parameters('mlzTags'))]" }, "virtualMachineNamePrefix": { "value": "[variables('virtualMachineNamePrefix')]" @@ -14012,8 +13515,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "12693991481187214678" + "version": "0.31.92.45157", + "templateHash": "16414429800719346251" } }, "parameters": { @@ -14098,8 +13601,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10260725559165503719" + "version": "0.31.92.45157", + "templateHash": "2762553882428643570" } }, "parameters": { @@ -14157,14 +13660,14 @@ } }, "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]", "virtualMachines" ] }, { - "condition": "[and(parameters('enableScalingTool'), parameters('pooledHostPool'))]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", - "name": "[format('deploy-scaling-tool-{0}', parameters('deploymentNameSuffix'))]", + "name": "[format('deploy-scalingPlan-{0}', parameters('deploymentNameSuffix'))]", "resourceGroup": "[parameters('resourceGroupManagement')]", "properties": { "expressionEvaluationOptions": { @@ -14172,21 +13675,47 @@ }, "mode": "Incremental", "parameters": { - "files": { - "value": { - "requirements.psd1": "[variables('$fxv#0')]", - "run.ps1": "[variables('$fxv#1')]", - "../profile.ps1": "[variables('$fxv#2')]" - } + "deploymentUserAssignedIdentityPrincipalId": { + "value": "[parameters('deploymentUserAssignedIdentityPrincipalId')]" + }, + "enableAvdInsights": { + "value": "[parameters('enableAvdInsights')]" + }, + "hostPoolResourceId": { + "value": "[parameters('hostPoolResourceId')]" + }, + "hostPoolType": { + "value": "[parameters('hostPoolType')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "logAnalyticsWorkspaceResourceId": { + "value": "[parameters('logAnalyticsWorkspaceResourceId')]" + }, + "scalingPlanDiagnosticSettingName": { + "value": "[parameters('namingConvention').scalingPlanDiagnosticSetting]" + }, + "scalingPlanName": { + "value": "[parameters('namingConvention').scalingPlan]" + }, + "tags": { + "value": "[union(createObject('cm-resource-parent', parameters('hostPoolResourceId')), coalesce(tryGet(parameters('tags'), 'Microsoft.DesktopVirtualization/scalingPlans'), createObject()), parameters('mlzTags'))]" + }, + "timeZone": { + "value": "[parameters('timeZone')]" }, - "functionAppName": { - "value": "[parameters('functionAppName')]" + "weekdaysOffPeakStartTime": { + "value": "[parameters('scalingWeekdaysOffPeakStartTime')]" }, - "functionName": { - "value": "avd-scaling-tool" + "weekdaysPeakStartTime": { + "value": "[parameters('scalingWeekdaysPeakStartTime')]" }, - "schedule": { - "value": "0 */15 * * * *" + "weekendsOffPeakStartTime": { + "value": "[parameters('scalingWeekendsOffPeakStartTime')]" + }, + "weekendsPeakStartTime": { + "value": "[parameters('scalingWeekendsPeakStartTime')]" } }, "template": { @@ -14195,56 +13724,215 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3901504540519683161" + "version": "0.31.92.45157", + "templateHash": "12064357872576819259" } }, "parameters": { - "files": { - "type": "object" + "deploymentUserAssignedIdentityPrincipalId": { + "type": "string", + "metadata": { + "description": "Required. Principal ID of the user assigned identity." + } }, - "functionAppName": { - "type": "string" + "enableAvdInsights": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable AVD Insights." + } }, - "functionName": { - "type": "string" + "hostPoolResourceId": { + "type": "string", + "metadata": { + "description": "Required. Host pool resource ID for the Scaling Plan." + } }, - "schedule": { - "type": "string" + "location": { + "type": "string", + "metadata": { + "description": "Required. Location of the Scaling Plan. The location must match the location of the host pool." + } + }, + "logAnalyticsWorkspaceResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of the Log Analytics workspace for the Scaling Plan." + } + }, + "hostPoolType": { + "type": "string", + "allowedValues": [ + "Personal", + "Pooled" + ], + "metadata": { + "description": "Required. Host pool type of the Scaling Plan." + } + }, + "scalingPlanDiagnosticSettingName": { + "type": "string", + "metadata": { + "description": "Required. Name of the diagnostic setting for the Scaling Plan." + } + }, + "scalingPlanName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Scaling Plan." + } + }, + "supportedClouds": { + "type": "array", + "defaultValue": [ + "AzureCloud", + "AzureUSGovernment" + ], + "metadata": { + "description": "Optional. Supported clouds." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "timeZone": { + "type": "string", + "defaultValue": "UTC", + "metadata": { + "description": "Optional. Time zone of the Scaling Plan. Defaults to UTC." + } + }, + "weekdaysOffPeakStartTime": { + "type": "string", + "metadata": { + "description": "Required. Off peak start time for weekdays in HH:mm format." + } + }, + "weekendsOffPeakStartTime": { + "type": "string", + "metadata": { + "description": "Required. Off peak start time for weekends in HH:mm format." + } + }, + "weekdaysPeakStartTime": { + "type": "string", + "metadata": { + "description": "Required. Peak start time for weekdays in HH:mm format." + } + }, + "weekendsPeakStartTime": { + "type": "string", + "metadata": { + "description": "Required. Peak start time for weekends in HH:mm format." + } } }, + "variables": { + "schedules": "[if(equals(parameters('hostPoolType'), 'Pooled'), createArray(createObject('daysOfWeek', createArray('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'), 'offPeakLoadBalancingAlgorithm', 'DepthFirst', 'offPeakStartTime', createObject('hour', split(parameters('weekdaysOffPeakStartTime'), ':')[0], 'minute', split(parameters('weekdaysOffPeakStartTime'), ':')[1]), 'peakLoadBalancingAlgorithm', 'BreadthFirst', 'peakStartTime', createObject('hour', split(parameters('weekdaysPeakStartTime'), ':')[0], 'minute', split(parameters('weekdaysPeakStartTime'), ':')[1]), 'rampDownCapacityThresholdPct', 90, 'rampDownForceLogoffUsers', false(), 'rampDownLoadBalancingAlgorithm', 'DepthFirst', 'rampDownMinimumHostsPct', 0, 'rampDownNotificationMessage', 'Ramping down the AVD session hosts to support low demand.', 'rampDownStartTime', createObject('hour', sub(int(split(parameters('weekdaysOffPeakStartTime'), ':')[0]), 1), 'minute', split(parameters('weekdaysOffPeakStartTime'), ':')[1]), 'rampDownStopHostsWhen', 'ZeroSessions', 'rampDownWaitTimeMinutes', 0, 'rampUpCapacityThresholdPct', 70, 'rampUpLoadBalancingAlgorithm', 'BreadthFirst', 'rampUpMinimumHostsPct', 25, 'rampUpStartTime', createObject('hour', sub(int(split(parameters('weekdaysPeakStartTime'), ':')[0]), 1), 'minute', split(parameters('weekdaysPeakStartTime'), ':')[1])), createObject('daysOfWeek', createArray('Saturday', 'Sunday'), 'offPeakLoadBalancingAlgorithm', 'DepthFirst', 'offPeakStartTime', createObject('hour', split(parameters('weekendsOffPeakStartTime'), ':')[0], 'minute', split(parameters('weekendsOffPeakStartTime'), ':')[1]), 'peakLoadBalancingAlgorithm', 'BreadthFirst', 'peakStartTime', createObject('hour', split(parameters('weekendsPeakStartTime'), ':')[0], 'minute', split(parameters('weekendsPeakStartTime'), ':')[1]), 'rampDownCapacityThresholdPct', 90, 'rampDownForceLogoffUsers', false(), 'rampDownLoadBalancingAlgorithm', 'DepthFirst', 'rampDownMinimumHostsPct', 0, 'rampDownNotificationMessage', 'Ramping down the AVD session hosts to support low demand.', 'rampDownStartTime', createObject('hour', sub(int(split(parameters('weekendsOffPeakStartTime'), ':')[0]), 1), 'minute', split(parameters('weekendsOffPeakStartTime'), ':')[1]), 'rampDownStopHostsWhen', 'ZeroSessions', 'rampDownWaitTimeMinutes', 0, 'rampUpCapacityThresholdPct', 90, 'rampUpLoadBalancingAlgorithm', 'BreadthFirst', 'rampUpMinimumHostsPct', 25, 'rampUpStartTime', createObject('hour', sub(int(split(parameters('weekendsPeakStartTime'), ':')[0]), 1), 'minute', split(parameters('weekendsPeakStartTime'), ':')[1]))), createArray(createObject('daysOfWeek', createArray('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'), 'offPeakActionOnDisconnect', 'None', 'offPeakActionOnLogoff', 'Deallocate', 'offPeakMinutesToWaitOnDisconnect', 0, 'offPeakMinutesToWaitOnLogoff', 0, 'offPeakStartTime', createObject('hour', split(parameters('weekdaysOffPeakStartTime'), ':')[0], 'minute', split(parameters('weekdaysOffPeakStartTime'), ':')[1]), 'offPeakStartVMOnConnect', 'Enable', 'peakActionOnDisconnect', 'None', 'peakActionOnLogoff', 'Deallocate', 'peakMinutesToWaitOnDisconnect', 0, 'peakMinutesToWaitOnLogoff', 0, 'peakStartTime', createObject('hour', split(parameters('weekdaysPeakStartTime'), ':')[0], 'minute', split(parameters('weekdaysPeakStartTime'), ':')[1]), 'peakStartVMOnConnect', 'Enable', 'rampDownActionOnDisconnect', 'None', 'rampDownActionOnLogoff', 'Deallocate', 'rampDownMinutesToWaitOnDisconnect', 0, 'rampDownMinutesToWaitOnLogoff', 0, 'rampDownStartTime', createObject('hour', sub(int(split(parameters('weekdaysOffPeakStartTime'), ':')[0]), 1), 'minute', split(parameters('weekdaysOffPeakStartTime'), ':')[1]), 'rampDownStartVMOnConnect', 'Enable', 'rampUpActionOnDisconnect', 'None', 'rampUpActionOnLogoff', 'None', 'rampUpAutoStartHosts', 'WithAssignedUser', 'rampUpMinutesToWaitOnDisconnect', 0, 'rampUpMinutesToWaitOnLogoff', 0, 'rampUpStartTime', createObject('hour', sub(int(split(parameters('weekdaysPeakStartTime'), ':')[0]), 1), 'minute', split(parameters('weekdaysPeakStartTime'), ':')[1]), 'rampUpStartVMOnConnect', 'Enable'), createObject('daysOfWeek', createArray('Saturday', 'Sunday'), 'rampUpStartTime', createObject('hour', sub(int(split(parameters('weekendsPeakStartTime'), ':')[0]), 1), 'minute', split(parameters('weekendsPeakStartTime'), ':')[1]), 'peakStartTime', createObject('hour', split(parameters('weekendsPeakStartTime'), ':')[0], 'minute', split(parameters('weekendsPeakStartTime'), ':')[1]), 'peakMinutesToWaitOnDisconnect', 0, 'peakActionOnDisconnect', 'None', 'peakMinutesToWaitOnLogoff', 0, 'peakActionOnLogoff', 'Deallocate', 'peakStartVMOnConnect', 'Enable', 'rampDownStartTime', createObject('hour', sub(int(split(parameters('weekendsOffPeakStartTime'), ':')[0]), 1), 'minute', split(parameters('weekendsOffPeakStartTime'), ':')[1]), 'rampDownMinutesToWaitOnDisconnect', 0, 'rampDownActionOnDisconnect', 'None', 'rampDownMinutesToWaitOnLogoff', 0, 'rampDownActionOnLogoff', 'Deallocate', 'rampDownStartVMOnConnect', 'Enable', 'rampUpAutoStartHosts', 'WithAssignedUser', 'rampUpStartVMOnConnect', 'Enable', 'rampUpMinutesToWaitOnDisconnect', 0, 'rampUpActionOnDisconnect', 'None', 'rampUpMinutesToWaitOnLogoff', 0, 'rampUpActionOnLogoff', 'None', 'offPeakStartTime', createObject('hour', split(parameters('weekendsOffPeakStartTime'), ':')[0], 'minute', split(parameters('weekendsOffPeakStartTime'), ':')[1]), 'offPeakMinutesToWaitOnDisconnect', 0, 'offPeakActionOnDisconnect', 'None', 'offPeakMinutesToWaitOnLogoff', 0, 'offPeakActionOnLogoff', 'Deallocate', 'offPeakStartVMOnConnect', 'Enable')))]" + }, "resources": [ { - "type": "Microsoft.Web/sites/functions", - "apiVersion": "2020-12-01", - "name": "[format('{0}/{1}', parameters('functionAppName'), parameters('functionName'))]", + "type": "Microsoft.DesktopVirtualization/scalingPlans", + "apiVersion": "2023-09-05", + "name": "[parameters('scalingPlanName')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", "properties": { - "config": { - "disabled": false, - "bindings": [ - { - "name": "Timer", - "type": "timerTrigger", - "direction": "in", - "schedule": "[parameters('schedule')]" - } - ] - }, - "files": "[parameters('files')]" + "timeZone": "[parameters('timeZone')]", + "hostPoolType": "[parameters('hostPoolType')]", + "exclusionTag": "excludeFromAutoscale", + "schedules": [], + "hostPoolReferences": [ + { + "hostPoolArmPath": "[parameters('hostPoolResourceId')]", + "scalingPlanEnabled": true + } + ] } + }, + { + "copy": { + "name": "schedules_Pooled", + "count": "[length(range(0, length(variables('schedules'))))]" + }, + "condition": "[equals(parameters('hostPoolType'), 'Pooled')]", + "type": "Microsoft.DesktopVirtualization/scalingPlans/pooledSchedules", + "apiVersion": "2023-09-05", + "name": "[format('{0}/{1}', parameters('scalingPlanName'), if(equals(range(0, length(variables('schedules')))[copyIndex()], 0), 'Weekdays', 'Weekends'))]", + "properties": "[variables('schedules')[range(0, length(variables('schedules')))[copyIndex()]]]", + "dependsOn": [ + "[resourceId('Microsoft.DesktopVirtualization/scalingPlans', parameters('scalingPlanName'))]" + ] + }, + { + "copy": { + "name": "schedule_Personal", + "count": "[length(range(0, length(variables('schedules'))))]" + }, + "condition": "[equals(parameters('hostPoolType'), 'Personal')]", + "type": "Microsoft.DesktopVirtualization/scalingPlans/personalSchedules", + "apiVersion": "2023-09-05", + "name": "[format('{0}/{1}', parameters('scalingPlanName'), if(equals(range(0, length(variables('schedules')))[copyIndex()], 0), 'Weekdays', 'Weekends'))]", + "properties": "[variables('schedules')[range(0, length(variables('schedules')))[copyIndex()]]]", + "dependsOn": [ + "[resourceId('Microsoft.DesktopVirtualization/scalingPlans', parameters('scalingPlanName'))]" + ] + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DesktopVirtualization/scalingPlans/{0}', parameters('scalingPlanName'))]", + "name": "[guid(parameters('deploymentUserAssignedIdentityPrincipalId'), '082f0a83-3be5-4ba1-904c-961cca79b387', resourceId('Microsoft.DesktopVirtualization/scalingPlans', parameters('scalingPlanName')))]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '082f0a83-3be5-4ba1-904c-961cca79b387')]", + "principalId": "[parameters('deploymentUserAssignedIdentityPrincipalId')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.DesktopVirtualization/scalingPlans', parameters('scalingPlanName'))]", + "schedule_Personal", + "schedules_Pooled" + ] + }, + { + "condition": "[and(parameters('enableAvdInsights'), contains(parameters('supportedClouds'), environment().name))]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.DesktopVirtualization/scalingPlans/{0}', parameters('scalingPlanName'))]", + "name": "[parameters('scalingPlanDiagnosticSettingName')]", + "properties": { + "logs": [ + { + "categoryGroup": "allLogs", + "enabled": true + } + ], + "workspaceId": "[parameters('logAnalyticsWorkspaceResourceId')]" + }, + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.DesktopVirtualization/scalingPlans', parameters('scalingPlanName')), 'Microsoft.Authorization/roleAssignments', guid(parameters('deploymentUserAssignedIdentityPrincipalId'), '082f0a83-3be5-4ba1-904c-961cca79b387', resourceId('Microsoft.DesktopVirtualization/scalingPlans', parameters('scalingPlanName'))))]", + "[resourceId('Microsoft.DesktopVirtualization/scalingPlans', parameters('scalingPlanName'))]" + ] } ] } - } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupManagement')), 'Microsoft.Resources/deployments', format('deploy-recoveryServices-{0}', parameters('deploymentNameSuffix')))]", + "virtualMachines" + ] } ] } }, "dependsOn": [ - "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-control-plane-{0}', parameters('deploymentNameSuffix')))]", "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-fslogix-{0}', parameters('deploymentNameSuffix')))]", "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix')))]", - "rgs", "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-tier3-avd-{0}', parameters('deploymentNameSuffix')))]" ] }, @@ -14266,7 +13954,7 @@ "value": "[parameters('locationVirtualMachines')]" }, "resourceGroupManagement": { - "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[2], parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.resourceGroupName.value]" }, "userAssignedIdentityClientId": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.deploymentUserAssignedIdentityClientId.value]" @@ -14281,8 +13969,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3323446775924256571" + "version": "0.31.92.45157", + "templateHash": "297418666680418295" } }, "parameters": { @@ -14365,8 +14053,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17500708096438980911" + "version": "0.31.92.45157", + "templateHash": "6896852651895359166" } }, "parameters": { @@ -14424,7 +14112,6 @@ "dependsOn": [ "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-fslogix-{0}', parameters('deploymentNameSuffix')))]", "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-management-{0}', parameters('deploymentNameSuffix')))]", - "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', variables('resourceGroupServices')[2], parameters('deploymentNameSuffix')))]", "[subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-session-hosts-{0}', parameters('deploymentNameSuffix')))]" ] } diff --git a/src/bicep/add-ons/azure-virtual-desktop/solution.parameters.json b/src/bicep/add-ons/azure-virtual-desktop/solution.parameters.json new file mode 100644 index 000000000..187a39a0d --- /dev/null +++ b/src/bicep/add-ons/azure-virtual-desktop/solution.parameters.json @@ -0,0 +1,207 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "activeDirectorySolution": { + "value": "ActiveDirectoryDomainServices" + }, + "availability": { + "value": "AvailabilityZones" + }, + "availabilityZones": { + "value": ["1","2","3"] + }, + "avdConfigurationZipFileName": { + "value": "Configuration_1.0.02790.438.zip" + }, + "avdObjectId": { + "value": "cdcfb416-e2fe-41e2-be12-33813c1cd427" + }, + "azureNetAppFilesSubnetAddressPrefix": { + "value": "" + }, + "customRdpProperty": { + "value": "audiocapturemode:i:1;camerastoredirect:s:*;use multimon:i:0;drivestoredirect:s:;encode redirected video capture:i:1;redirected video capture encoding quality:i:1;audiomode:i:0;devicestoredirect:s:;redirectclipboard:i:0;redirectcomports:i:0;redirectlocation:i:1;redirectprinters:i:0;redirectsmartcards:i:1;redirectwebauthn:i:1;usbdevicestoredirect:s:;keyboardhook:i:2;" + }, + "deployActivityLogDiagnosticSetting": { + "value": true + }, + "deployDefender": { + "value": true + }, + "deployNetworkWatcher": { + "value": true + }, + "deployPolicy": { + "value": true + }, + "desktopFriendlyName": { + "value": "" + }, + "diskSku": { + "value": "Premium_LRS" + }, + "domainJoinUserPrincipalName": { + "value": "xadmin" + }, + "domainName": { + "value": "contoso.com" + }, + "drainMode": { + "value": true + }, + "emailSecurityContact": { + "value": "email@contoso.com" + }, + "enableAcceleratedNetworking": { + "value": true + }, + "enableApplicationInsights": { + "value": true + }, + "enableAvdInsights": { + "value": true + }, + "enableTelemetry": { + "value": false + }, + "environmentAbbreviation": { + "value": "dev" + }, + "existingSharedActiveDirectoryConnection": { + "value": false + }, + "existingFeedWorkspaceResourceId": { + "value": "" + }, + "fslogixShareSizeInGB": { + "value": 100 + }, + "fslogixContainerType": { + "value": "ProfileContainer" + }, + "fslogixStorageService": { + "value": "AzureFiles Premium" + }, + "functionAppSubnetAddressPrefix": { + "value": "10.0.141.128/26" + }, + "hostPoolPublicNetworkAccess": { + "value": "Enabled" + }, + "hostPoolType": { + "value": "Pooled" + }, + "hubAzureFirewallResourceId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mlz-rg-network-hub-dev-use2/providers/Microsoft.Network/azureFirewalls/mlz-afw-hub-dev-use2" + }, + "hubVirtualNetworkResourceId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mlz-rg-network-hub-dev-use2/providers/Microsoft.Network/virtualNetworks/mlz-vnet-hub-dev-use2" + }, + "identifier": { + "value": "mlz" + }, + "imageOffer": { + "value": "windows-11" + }, + "imagePublisher": { + "value": "MicrosoftWindowsDesktop" + }, + "imageSku": { + "value": "win11-23h2-avd" + }, + "imageVersionResourceId": { + "value": "" + }, + "locationVirtualMachines": { + "value": "eastus2" + }, + "logAnalyticsWorkspaceRetention": { + "value": 30 + }, + "logAnalyticsWorkspaceSku": { + "value": "PerGB2018" + }, + "operationsLogAnalyticsWorkspaceResourceId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mlz-rg-network-operations-dev-use2/providers/Microsoft.OperationalInsights/workspaces/mlz-log-operations-dev-use2" + }, + "organizationalUnitPath": { + "value": "OU=Computers,OU=AVD,DC=contoso,DC=com" + }, + "policy": { + "value": "NISTRev4" + }, + "privateLinkScopeResourceId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mlz-rg-network-operations-dev-use2/providers/microsoft.insights/privatelinkscopes/mlz-pls-operations-dev-use2" + }, + "profile": { + "value": "Generic" + }, + "recoveryServices": { + "value": true + }, + "scalingWeekdaysOffPeakStartTime": { + "value": "17:00" + }, + "scalingWeekdaysPeakStartTime": { + "value": "09:00" + }, + "scalingWeekendsOffPeakStartTime": { + "value": "17:00" + }, + "scalingWeekendsPeakStartTime": { + "value": "09:00" + }, + "securityPrincipals": { + "value": [{"name":"AVD","objectId":"00000000-0000-0000-0000-000000000000"}] + }, + "sessionHostCount": { + "value": 1 + }, + "sessionHostIndex": { + "value": 0 + }, + "sharedServicesSubnetResourceId": { + "value": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mlz-rg-network-sharedServices-dev-use2/providers/Microsoft.Network/virtualNetworks/mlz-vnet-sharedServices-dev-use2/subnets/mlz-snet-sharedServices-dev-use2" + }, + "stampIndex": { + "value": 0 + }, + "storageCount": { + "value": 1 + }, + "storageIndex": { + "value": 0 + }, + "subnetAddressPrefixes": { + "value": ["10.0.140.0/24","10.0.141.0/26"] + }, + "tags": { + "value": {} + }, + "usersPerCore": { + "value": 2 + }, + "validationEnvironment": { + "value": false + }, + "virtualMachineSize": { + "value": "Standard_D4ads_v5" + }, + "virtualMachineUsername": { + "value": "xadmin" + }, + "virtualMachineVirtualCpuCount": { + "value": 4 + }, + "virtualNetworkAddressPrefixes": { + "value": ["10.0.140.0/23"] + }, + "workspaceFriendlyName": { + "value": "mlz-vdws-avd-use2-dev" + }, + "workspacePublicNetworkAccess": { + "value": "Enabled" + } + } +} \ No newline at end of file diff --git a/src/bicep/add-ons/azure-virtual-desktop/uiDefinition.json b/src/bicep/add-ons/azure-virtual-desktop/uiDefinition.json index 7e6cd692e..7ae63ad60 100644 --- a/src/bicep/add-ons/azure-virtual-desktop/uiDefinition.json +++ b/src/bicep/add-ons/azure-virtual-desktop/uiDefinition.json @@ -314,253 +314,6 @@ } ] }, - { - "name": "controlPlane", - "label": "Control Plane", - "elements": [ - { - "name": "hostPool", - "type": "Microsoft.Common.Section", - "visible": true, - "label": "Host Pool", - "elements": [ - { - "name": "validation", - "type": "Microsoft.Common.CheckBox", - "label": "Validation environment", - "visible": "[not(equals(steps('basics').scenario.profile, 'arcGisPro'))]", - "toolTip": "Choose whether to deploy the host pool as a validation environment. This allows you test preview features for AVD before they are released to production.", - "constraints": { - "required": false - } - }, - { - "name": "hostPoolType", - "type": "Microsoft.Common.DropDown", - "visible": true, - "label": "Host Pool Type", - "defaultValue": "Pooled", - "multiLine": true, - "toolTip": "", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "Pooled", - "value": "Pooled" - }, - { - "label": "Personal", - "value": "Personal" - } - ] - } - }, - { - "name": "workloadType", - "type": "Microsoft.Common.DropDown", - "label": "Workload Type", - "visible": "[or(equals(steps('basics').scenario.profile, 'arcGisPro'), and(equals(steps('basics').scenario.profile, 'generic'), equals(steps('controlPlane').hostPool.hostPoolType, 'Pooled')))]", - "filter": true, - "defaultValue": "[if(equals(steps('basics').scenario.profile, 'arcGisPro'), 'Medium', 'Heavy (2 users per core)')]", - "toolTip": "Select the type of image to deploy on the session hosts.", - "multiLine": true, - "constraints": { - "required": true, - "allowedValues": "[if(equals(steps('basics').scenario.profile, 'arcGisPro'), parse('[{\"label\": \"Light\",\"description\": \"Simple 2-D map visualizations, basic analyses, and reporting.\",\"value\": 6},{\"label\": \"Medium\",\"description\": \"Advanced 2-D or 3-D map visualizations, multi-source data analyses leveraging geoprocessing tools, reporting, and editing.\",\"value\": 4},{\"label\": \"Heavy\",\"description\": \"Complex 2-D or 3-D map visualizations including advanced symbology, analyses with visability and line of site, reporting, and editing.\",\"value\": 3}]'), parse('[{\"label\": \"Light (6 users per core)\",\"description\": \"Basic data entry tasks\",\"value\": \"6.0\"},{\"label\": \"Medium (4 users per core)\",\"description\": \"Consultants and market researchers\",\"value\": \"4.0\"},{\"label\": \"Heavy (2 users per core)\",\"description\": \"Software engineers and content creators\",\"value\": \"2.0\"},{\"label\": \"Power (1 user per core)\",\"description\": \"Graphic designers, 3D model makers, and machine learning researchers\",\"value\": \"1.0\"}]'))]" - } - }, - { - "name": "customRdpProperties", - "type": "Microsoft.Common.TextBox", - "visible": true, - "label": "Custom RDP properties", - "defaultValue": "[if(equals(steps('basics').scenario.profile, 'arcGisPro'), 'use multimon:i:1;drivestoredirect:s:;encode redirected video capture:i:1;redirected video capture encoding quality:i:2;audiomode:i:0;devicestoredirect:s:;redirectclipboard:i:0;redirectcomports:i:0;redirectlocation:i:1;redirectprinters:i:0;redirectsmartcards:i:1;redirectwebauthn:i:1;usbdevicestoredirect:s:;keyboardhook:i:2;', 'audiocapturemode:i:1;camerastoredirect:s:*;use multimon:i:0;drivestoredirect:s:;encode redirected video capture:i:1;redirected video capture encoding quality:i:1;audiomode:i:0;devicestoredirect:s:;redirectclipboard:i:0;redirectcomports:i:0;redirectlocation:i:1;redirectprinters:i:0;redirectsmartcards:i:1;redirectwebauthn:i:1;usbdevicestoredirect:s:;keyboardhook:i:2;')]", - "toolTip": "Specify the configuration for the RDP properties on the AVD host pool.", - "constraints": { - "required": true - } - }, - { - "name": "publicNetworkAccess", - "type": "Microsoft.Common.DropDown", - "visible": true, - "label": "Public network access", - "defaultValue": "Enabled", - "multiLine": true, - "toolTip": "Enabled: allows the host pool to be accessed from both public and private networks. Disabled: allows the host pool to only be accessed via private endpoints.", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "Disabled", - "value": "Disabled" - }, - { - "label": "Enabled", - "value": "Enabled" - }, - { - "label": "Enabled For Clients Only", - "value": "EnabledForClientsOnly" - }, - { - "label": "Enabled For Session Hosts Only", - "value": "EnabledForSessionHostsOnly" - } - ] - } - } - ] - }, - { - "name": "workspacesApi", - "type": "Microsoft.Solutions.ArmApiControl", - "request": { - "method": "GET", - "path": "[concat(steps('basics').scope.subscription.id, '/providers/Microsoft.DesktopVirtualization/workspaces?api-version=2022-02-10-preview')]" - } - }, - { - "name": "workspace", - "type": "Microsoft.Common.Section", - "visible": "[empty(first(filter(steps('controlPlane').workspacesApi.value, (item) => and(contains(item.name, steps('basics').naming.identifier), contains(item.name, 'feed'), contains(item.name, steps('basics').naming.environment), equals(item.location, steps('basics').hub.azureFirewallApi.location)))))]", - "label": "Workspace", - "elements": [ - { - "name": "publicNetworkAccess", - "type": "Microsoft.Common.DropDown", - "visible": true, - "label": "Public network access (feed)", - "defaultValue": "Enabled", - "multiLine": true, - "toolTip": "Enabled: allows the host pool to be accessed from both public and private networks. Disabled: allows the host pool to only be accessed via private endpoints.", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "Disabled", - "value": "Disabled" - }, - { - "label": "Enabled", - "value": "Enabled" - } - ] - } - } - ] - }, - { - "name": "assignment", - "type": "Microsoft.Common.Section", - "label": "Assignment", - "visible": true, - "elements": [ - { - "name": "description", - "type": "Microsoft.Common.TextBlock", - "visible": true, - "options": { - "text": "To access Azure Virtual Desktop, your end users will need to be assigned to the Desktop Applicaiton Group and if applicable, be given permissions on the storage service for FSLogix. Select the desired security groups below to give access to this AVD stamp. If deploying FSLogix, storage will be deployed for each group to support sharding. Be sure your groups are sized appropriately for each shard.", - "link": { - "label": "Learn more about FSLogix storage options for sizing your groups.", - "uri": "https://learn.microsoft.com/azure/virtual-desktop/store-fslogix-profile" - } - } - }, - { - "name": "groups", - "type": "Microsoft.Common.EditableGrid", - "ariaLabel": "Input the security groups for access to AVD and if applicable, FSLogix. The object ID is a property on the group and can be found in Entra ID.", - "label": "Security Groups", - "visible": true, - "constraints": { - "width": "Full", - "rows": { - "count": { - "min": 1, - "max": 100 - } - }, - "columns": [ - { - "id": "name", - "header": "Name", - "width": "1fr", - "element": { - "type": "Microsoft.Common.TextBox", - "placeholder": "Security Group Name", - "constraints": { - "required": true, - "validations": [] - } - } - }, - { - "id": "objectId", - "header": "Object ID", - "width": "1fr", - "element": { - "type": "Microsoft.Common.TextBox", - "placeholder": "Security Group Object ID", - "constraints": { - "required": true, - "validations": [] - } - } - } - ] - } - } - ] - }, - { - "name": "friendlyNames", - "type": "Microsoft.Common.Section", - "label": "Friendly Names for AVD Client", - "visible": true, - "elements": [ - { - "name": "custom", - "type": "Microsoft.Common.CheckBox", - "visible": true, - "label": "Custom friendly names?", - "defaultValue": false, - "toolTip": "Choose whether to add custom names to the AVD feed workspace and the desktop for the AVD client." - }, - { - "name": "feedWorkspace", - "type": "Microsoft.Common.TextBox", - "label": "Feed Workspace", - "defaultValue": "", - "placeholder": "Example: Information Technology", - "toolTip": "Input the friendly name for the AVD workspace that will be displayed in the end user's client. This value should apply to all the stamp indexes within the same identifier and would mostly likely represent your business unit or project.", - "visible": "[and(steps('controlPlane').friendlyNames.custom, empty(first(filter(steps('controlPlane').workspacesApi.value, (item) => and(contains(item.name, steps('basics').naming.identifier), contains(item.name, 'feed'), contains(item.name, steps('basics').naming.environment), equals(item.location, steps('basics').hub.azureFirewallApi.location))))))]", - "constraints": { - "required": false, - "regex": "^.{1,64}$", - "validationMessage": "The value must be between 1 and 64 characters in length." - } - }, - { - "name": "sessionDesktop", - "type": "Microsoft.Common.TextBox", - "label": "Desktop", - "defaultValue": "", - "placeholder": "Example: Help Desk", - "toolTip": "Input the friendly name for the AVD Session Desktop application that will be displayed in the end user's client. This value should represent the workload (eg., Help Desk, Development, or Administration) that will be supported on the host pool.", - "visible": "[steps('controlPlane').friendlyNames.custom]", - "constraints": { - "required": false, - "regex": "^.{1,64}$", - "validationMessage": "The value must be between 1 and 64 characters in length." - } - } - ] - } - ] - }, { "name": "hosts", "label": "Session Hosts", @@ -771,7 +524,7 @@ }, "osPlatform": "Windows", "count": "[steps('hosts').virtualMachines.count]", - "visible": "[and(equals(steps('basics').scenario.profile, 'arcGisPro'), and(equals(steps('controlPlane').hostPool.hostPoolType, 'Personal'), equals(steps('controlPlane').hostPool.workloadType, 6)))]" + "visible": "[and(equals(steps('basics').scenario.profile, 'arcGisPro'), and(equals(steps('management').hostPool.hostPoolType, 'Personal'), equals(steps('management').hostPool.workloadType, 6)))]" }, { "name": "sizeArcGisProSingleMedium", @@ -798,7 +551,7 @@ }, "osPlatform": "Windows", "count": "[steps('hosts').virtualMachines.count]", - "visible": "[and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Personal'), equals(steps('controlPlane').hostPool.workloadType, 4))]" + "visible": "[and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Personal'), equals(steps('management').hostPool.workloadType, 4))]" }, { "name": "sizeArcGisProSingleHeavy", @@ -823,7 +576,7 @@ }, "osPlatform": "Windows", "count": "[steps('hosts').virtualMachines.count]", - "visible": "[and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Personal'), equals(steps('controlPlane').hostPool.workloadType, 3))]" + "visible": "[and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Personal'), equals(steps('management').hostPool.workloadType, 3))]" }, { "name": "sizeArcGisProMulti", @@ -846,7 +599,7 @@ }, "osPlatform": "Windows", "count": "[steps('hosts').virtualMachines.count]", - "visible": "[and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Pooled'))]" + "visible": "[and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Pooled'))]" }, { "name": "resourceSkusApi", @@ -1020,28 +773,19 @@ "visible": "[equals(steps('hosts').identity.solution, 'MicrosoftEntraId')]", "options": { "style": "Info", - "text": "This solution currently does not support FSLogix with Entra joined session hosts." + "text": "Only local profiles may be selected with Entra joined session hosts due to the security risks." } }, { "name": "profileSolution", "type": "Microsoft.Common.DropDown", "label": "Profile Solution", - "visible": "[contains(steps('hosts').identity.solution, 'DomainServices')]", - "defaultValue": "Local", + "visible": true, + "defaultValue": "Local Profiles", "toolTip": "Select the user profile solution for your end users.", "constraints": { "required": true, - "allowedValues": [ - { - "label": "FSLogix", - "value": "fslogix" - }, - { - "label": "Local", - "value": "local" - } - ] + "allowedValues": "[if(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'), parse('[{\"label\":\"Local Profiles\",\"value\":\"local\"}]'), parse('[{\"label\":\"Local Profiles\",\"value\":\"local\"},{\"label\":\"FSLogix\",\"value\":\"fslogix\"}]'))]" } }, { @@ -1124,33 +868,132 @@ "label": "Management", "elements": [ { - "name": "servicePrincipalsApi", - "type": "Microsoft.Solutions.GraphApiControl", + "name": "hostPool", + "type": "Microsoft.Common.Section", + "visible": true, + "label": "Host Pool", + "elements": [ + { + "name": "validation", + "type": "Microsoft.Common.CheckBox", + "label": "Validation environment", + "visible": "[not(equals(steps('basics').scenario.profile, 'arcGisPro'))]", + "toolTip": "Choose whether to deploy the host pool as a validation environment. This allows you test preview features for AVD before they are released to production.", + "constraints": { + "required": false + } + }, + { + "name": "hostPoolType", + "type": "Microsoft.Common.DropDown", + "visible": true, + "label": "Host Pool Type", + "defaultValue": "Pooled", + "multiLine": true, + "toolTip": "", + "constraints": { + "required": true, + "allowedValues": [ + { + "label": "Pooled", + "value": "Pooled" + }, + { + "label": "Personal", + "value": "Personal" + } + ] + } + }, + { + "name": "workloadType", + "type": "Microsoft.Common.DropDown", + "label": "Workload Type", + "visible": "[or(equals(steps('basics').scenario.profile, 'arcGisPro'), and(equals(steps('basics').scenario.profile, 'generic'), equals(steps('management').hostPool.hostPoolType, 'Pooled')))]", + "filter": true, + "defaultValue": "[if(equals(steps('basics').scenario.profile, 'arcGisPro'), 'Medium', 'Heavy (2 users per core)')]", + "toolTip": "Select the type of image to deploy on the session hosts.", + "multiLine": true, + "constraints": { + "required": true, + "allowedValues": "[if(equals(steps('basics').scenario.profile, 'arcGisPro'), parse('[{\"label\": \"Light\",\"description\": \"Simple 2-D map visualizations, basic analyses, and reporting.\",\"value\": 6},{\"label\": \"Medium\",\"description\": \"Advanced 2-D or 3-D map visualizations, multi-source data analyses leveraging geoprocessing tools, reporting, and editing.\",\"value\": 4},{\"label\": \"Heavy\",\"description\": \"Complex 2-D or 3-D map visualizations including advanced symbology, analyses with visability and line of site, reporting, and editing.\",\"value\": 3}]'), parse('[{\"label\": \"Light (6 users per core)\",\"description\": \"Basic data entry tasks\",\"value\": \"6.0\"},{\"label\": \"Medium (4 users per core)\",\"description\": \"Consultants and market researchers\",\"value\": \"4.0\"},{\"label\": \"Heavy (2 users per core)\",\"description\": \"Software engineers and content creators\",\"value\": \"2.0\"},{\"label\": \"Power (1 user per core)\",\"description\": \"Graphic designers, 3D model makers, and machine learning researchers\",\"value\": \"1.0\"}]'))]" + } + }, + { + "name": "customRdpProperties", + "type": "Microsoft.Common.TextBox", + "visible": true, + "label": "Custom RDP properties", + "defaultValue": "[if(equals(steps('basics').scenario.profile, 'arcGisPro'), 'use multimon:i:1;drivestoredirect:s:;encode redirected video capture:i:1;redirected video capture encoding quality:i:2;audiomode:i:0;devicestoredirect:s:;redirectclipboard:i:0;redirectcomports:i:0;redirectlocation:i:1;redirectprinters:i:0;redirectsmartcards:i:1;redirectwebauthn:i:1;usbdevicestoredirect:s:;keyboardhook:i:2;', 'audiocapturemode:i:1;camerastoredirect:s:*;use multimon:i:0;drivestoredirect:s:;encode redirected video capture:i:1;redirected video capture encoding quality:i:1;audiomode:i:0;devicestoredirect:s:;redirectclipboard:i:0;redirectcomports:i:0;redirectlocation:i:1;redirectprinters:i:0;redirectsmartcards:i:1;redirectwebauthn:i:1;usbdevicestoredirect:s:;keyboardhook:i:2;')]", + "toolTip": "Specify the configuration for the RDP properties on the AVD host pool.", + "constraints": { + "required": true + } + }, + { + "name": "publicNetworkAccess", + "type": "Microsoft.Common.DropDown", + "visible": true, + "label": "Public network access", + "defaultValue": "Enabled", + "multiLine": true, + "toolTip": "Enabled: allows the host pool to be accessed from both public and private networks. Disabled: allows the host pool to only be accessed via private endpoints.", + "constraints": { + "required": true, + "allowedValues": [ + { + "label": "Disabled", + "value": "Disabled" + }, + { + "label": "Enabled", + "value": "Enabled" + }, + { + "label": "Enabled For Clients Only", + "value": "EnabledForClientsOnly" + }, + { + "label": "Enabled For Session Hosts Only", + "value": "EnabledForSessionHostsOnly" + } + ] + } + } + ] + }, + { + "name": "workspacesApi", + "type": "Microsoft.Solutions.ArmApiControl", "request": { "method": "GET", - "path": "/v1.0/serviceprincipals?$filter=appId eq '9cdead84-a844-4324-93f2-b2e6bb768d07'" + "path": "[concat(steps('basics').scope.subscription.id, '/providers/Microsoft.DesktopVirtualization/workspaces?api-version=2022-02-10-preview')]" } }, { - "name": "startVmOnConnect", + "name": "workspace", "type": "Microsoft.Common.Section", - "label": "Start VM On Connect", - "visible": "[empty(steps('management').servicePrincipalsApi)]", + "visible": "[empty(first(filter(steps('management').workspacesApi.value, (item) => and(contains(item.name, steps('basics').naming.identifier), contains(item.name, 'feed'), contains(item.name, steps('basics').naming.environment), equals(item.location, steps('basics').hub.azureFirewallApi.location)))))]", + "label": "Workspace", "elements": [ { - "name": "objectId", - "type": "Microsoft.Common.TextBox", - "label": "AVD Object ID", + "name": "publicNetworkAccess", + "type": "Microsoft.Common.DropDown", "visible": true, - "defaultValue": "", - "placeholder": "", - "toolTip": "Input the object ID for the Azure Virtual Desktop enterprise application in Entra ID. The application ID for the principal is 9cdead84-a844-4324-93f2-b2e6bb768d07.", + "label": "Public network access (feed)", + "defaultValue": "Enabled", + "multiLine": true, + "toolTip": "Enabled: allows the host pool to be accessed from both public and private networks. Disabled: allows the host pool to only be accessed via private endpoints.", "constraints": { "required": true, - "validations": [ + "allowedValues": [ { - "regex": "^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$", - "message": "The value must be a globally unique ID." + "label": "Disabled", + "value": "Disabled" + }, + { + "label": "Enabled", + "value": "Enabled" } ] } @@ -1158,123 +1001,213 @@ ] }, { - "name": "drainMode", + "name": "assignment", "type": "Microsoft.Common.Section", - "label": "Drain Mode", + "label": "Assignment", "visible": true, "elements": [ { - "name": "enableDrainMode", - "type": "Microsoft.Common.CheckBox", - "label": "Enable drain mode?", - "defaultValue": false, - "toolTip": "Enables drain mode on the AVD session hosts so the virtual machines cannot be accessed until they have been validated." + "name": "description", + "type": "Microsoft.Common.TextBlock", + "visible": true, + "options": { + "text": "To access Azure Virtual Desktop, your end users will need to be assigned to the Desktop Applicaiton Group and if applicable, be given permissions on the storage service for FSLogix. Select the desired security groups below to give access to this AVD stamp. If deploying FSLogix, storage will be deployed for each group to support sharding. Be sure your groups are sized appropriately for each shard.", + "link": { + "label": "Learn more about FSLogix storage options for sizing your groups.", + "uri": "https://learn.microsoft.com/azure/virtual-desktop/store-fslogix-profile" + } + } + }, + { + "name": "groupPickerBlade", + "type": "Microsoft.Solutions.BladeInvokeControl", + "openBladeStatus": "[steps('management').assignment.groupSelector.changing]", + "bladeReference": { + "name": "ObjectPickerBlade", + "extension": "Microsoft_AAD_IAM", + "parameters": { + "queries": 32, + "disablers": 4, + "bladeSubtitle": "Pick the groups to assign", + "additionalQueriesOnSearch": 0, + "advancedQueryOptions": { + "suggestedObjectsOptions": {} + }, + "selectionMaximum": 100, + "selectionMinimum": 1, + "bladeTitle": "Select Groups", + "informationHeader": { + "informationText": "Select the groups that require access to the AVD stamp", + "informationLink": "" + }, + "inviteEnabled": true, + "searchBoxLabel": "Search for a group", + "searchBoxPlaceHolderText": "Enter a string in the group name", + "searchBoxTooltip": "This is the tooltip", + "searchGridNoRowsMessage": "No groups found", + "selectButtonText": "Select Groups", + "selectedGridLabel": "Selected Groups", + "selectedGridNoRowsMessage": "You must select at least one group" + }, + "inFullScreen": false + }, + "transforms": { + "selection": "selectedObjects|[*].{displayName:displayName, objectId:id}" + } + }, + { + "name": "groupSelector", + "type": "Microsoft.Common.Selector", + "label": "Select Groups", + "keyPath": "displayName", + "descriptionKeyPath": "id", + "value": "[steps('management').assignment.groupPickerBlade.transformed.selection]", + "visible": true, + "barColor": "[if(empty(steps('management').assignment.groupPickerBlade), '#FF0000', '#7fba00')]", + "constraints": { + "required": true + }, + "link": "[if(empty(steps('management').assignment.groupPickerBlade), 'Select groups', 'Re-select groups')]" } ] }, { - "name": "scalingTool", - "label": "Scaling Tool", + "name": "friendlyNames", "type": "Microsoft.Common.Section", - "visible": "[equals(steps('controlPlane').hostPool.hostPoolType, 'Pooled')]", + "label": "Friendly Names for AVD Client", + "visible": true, "elements": [ { - "name": "deployScalingTool", + "name": "custom", "type": "Microsoft.Common.CheckBox", - "label": "Deploy scaling tool?", "visible": true, + "label": "Custom friendly names?", "defaultValue": false, - "toolTip": "Choose whether to deploy the required resources to enable the Scaling Tool. This tool should only be used when scaling plans are not available. It has been enhanced for zero-trust compliance and does not rely on AZ modules." + "toolTip": "Choose whether to add custom names to the AVD feed workspace and the desktop for the AVD client." }, { - "name": "beginPeakTime", + "name": "feedWorkspace", "type": "Microsoft.Common.TextBox", - "label": "Begin Peak Time", - "visible": "[steps('management').scalingTool.deployScalingTool]", - "defaultValue": "8:00", - "placeholder": "", - "toolTip": "Input the time when peak hours begin for your end users.", + "label": "Feed Workspace", + "defaultValue": "", + "placeholder": "Example: Information Technology", + "toolTip": "Input the friendly name for the AVD workspace that will be displayed in the end user's client. This value should apply to all the stamp indexes within the same identifier and would mostly likely represent your business unit or project.", + "visible": "[and(steps('management').friendlyNames.custom, empty(first(filter(steps('management').workspacesApi.value, (item) => and(contains(item.name, steps('basics').naming.identifier), contains(item.name, 'feed'), contains(item.name, steps('basics').naming.environment), equals(item.location, steps('basics').hub.azureFirewallApi.location))))))]", "constraints": { - "required": true, - "validations": [ - { - "regex": "^[012]?[0-9]:[0-5][0-9]$", - "message": "The value must be in a proper time format with a two digit hour and two digit minutes, e.g. 12am should be input as 00:00." - } - ] + "required": false, + "regex": "^.{1,64}$", + "validationMessage": "The value must be between 1 and 64 characters in length." } }, { - "name": "endPeakTime", + "name": "sessionDesktop", "type": "Microsoft.Common.TextBox", - "label": "End Peak Time", - "visible": "[steps('management').scalingTool.deployScalingTool]", - "defaultValue": "17:00", - "placeholder": "", + "label": "Desktop", + "defaultValue": "", + "placeholder": "Example: Help Desk", + "toolTip": "Input the friendly name for the AVD Session Desktop application that will be displayed in the end user's client. This value should represent the workload (eg., Help Desk, Development, or Administration) that will be supported on the host pool.", + "visible": "[steps('management').friendlyNames.custom]", + "constraints": { + "required": false, + "regex": "^.{1,64}$", + "validationMessage": "The value must be between 1 and 64 characters in length." + } + } + ] + }, + { + "name": "servicePrincipalsApi", + "type": "Microsoft.Solutions.GraphApiControl", + "request": { + "method": "GET", + "path": "/v1.0/serviceprincipals?$filter=appId eq '9cdead84-a844-4324-93f2-b2e6bb768d07'" + } + }, + { + "name": "drainMode", + "type": "Microsoft.Common.Section", + "label": "Drain Mode", + "visible": true, + "elements": [ + { + "name": "enableDrainMode", + "type": "Microsoft.Common.CheckBox", + "label": "Enable drain mode?", + "defaultValue": true, + "toolTip": "Enables drain mode on the AVD session hosts so the virtual machines cannot be accessed until they have been validated." + } + ] + }, + { + "name": "scalingPlan", + "label": "Scaling Plan", + "type": "Microsoft.Common.Section", + "visible": true, + "elements": [ + { + "name": "weekdaysPeakStartTime", + "type": "Microsoft.Common.TextBox", + "label": "Weekdays - Peak Start Time", + "visible": true, + "defaultValue": "09:00", "toolTip": "Input the time when peak hours end for your end users.", "constraints": { "required": true, "validations": [ { - "regex": "^[012]?[0-9]:[0-5][0-9]$", + "regex": "^[012][0-9]:[0-5][0-9]$", "message": "The value must be in a proper time format with a two digit hour and two digit minutes, e.g. 8pm should be input as 20:00." } ] } }, { - "name": "forceLogOff", + "name": "weekdaysOffPeakStartTime", "type": "Microsoft.Common.TextBox", - "label": "Force Log Off (Seconds)", - "visible": "[steps('management').scalingTool.deployScalingTool]", - "defaultValue": "0", - "toolTip": "Use this setting to force logoff users if session time limit settings cannot be used.", - "placeholder": "", - "multiLine": false, + "label": "Weekdays - Off Peak Start Time", + "visible": true, + "defaultValue": "17:00", + "toolTip": "Input the time when peak hours begin for your end users during the work week, Monday - Friday.", "constraints": { "required": true, "validations": [ { - "regex": "^[0-9]{1,3}$", - "message": "The value must be between 1 and 3 digits." + "regex": "^[012][0-9]:[0-5][0-9]$", + "message": "The value must be in a proper time format with a two digit hour and two digit minutes, e.g. 12am should be input as 00:00." } ] } }, { - "name": "minimumHosts", + "name": "weekendsPeakStartTime", "type": "Microsoft.Common.TextBox", - "label": "Minimum Number of Session Hosts", - "visible": "[steps('management').scalingTool.deployScalingTool]", - "defaultValue": "0", - "toolTip": "Use this setting to determine the minimum number of sessions hosts to keep online.", - "placeholder": "", - "multiLine": false, + "label": "Weekends - Peak Start Time", + "visible": true, + "defaultValue": "09:00", + "toolTip": "Input the time when peak hours end for your end users.", "constraints": { "required": true, "validations": [ { - "regex": "^[0-9]{1,4}$", - "message": "The value must be between 1 and 4 digits." + "regex": "^[012][0-9]:[0-5][0-9]$", + "message": "The value must be in a proper time format with a two digit hour and two digit minutes, e.g. 8pm should be input as 20:00." } ] } }, { - "name": "cpuThreshold", + "name": "weekendsOffPeakStartTime", "type": "Microsoft.Common.TextBox", - "label": "Session Threshold Per CPU", - "visible": "[steps('management').scalingTool.deployScalingTool]", - "defaultValue": "1", - "toolTip": "Use this setting to determine the number of sessions per CPU before turning on another host.", - "placeholder": "", - "multiLine": false, + "label": "Weekends - Off Peak Start Time", + "visible": true, + "defaultValue": "17:00", + "toolTip": "Input the time when peak hours begin for your end users during the work week, Monday - Friday.", "constraints": { "required": true, "validations": [ { - "regex": "^[0-9]{0,1}(?:\\.[0-9]{0,2})?$", - "message": "The value must be a number with 1 whole number and, if desired, a single or two digit decimal number." + "regex": "^[012][0-9]:[0-5][0-9]$", + "message": "The value must be in a proper time format with a two digit hour and two digit minutes, e.g. 12am should be input as 00:00." } ] } @@ -1314,10 +1247,10 @@ { "name": "enableApplicationInsights", "type": "Microsoft.Common.CheckBox", - "visible": "[or(steps('management').scalingTool.deployScalingTool, equals(steps('profiles').storage.service, 'AzureFiles Premium'))]", + "visible": "[equals(steps('profiles').storage.service, 'AzureFiles Premium')]", "label": "Enable Application Insights?", "defaultValue": true, - "toolTip": "Deploy the required resources to monitor the Scaling Tool and / or the Auto Increase File Share Solution." + "toolTip": "Deploy the required resources to monitor the Auto Increase File Share Solution." } ] } @@ -1383,7 +1316,7 @@ } }, { - "name": "controlPlaneSubnetAddressCidrRange", + "name": "managementSubnetAddressCidrRange", "label": "Control Plane Subnet CIDR range", "type": "Microsoft.Common.TextBox", "defaultValue": "[concat('10.0.1', string(add(41, mul(2, steps('basics').naming.stampIndex))), '.0/26')]", @@ -1396,19 +1329,19 @@ "message": "Invalid CIDR range. The address prefix must be in the range [24,28]." }, { - "isValid": "[if(greaterOrEquals(last(split(steps('network').vnet.virtualNetworkAddressCidrRange, '/')), 8), equals(last(take(split(first(split(steps('network').vnet.virtualNetworkAddressCidrRange, '/')), '.'), 1)), last(take(split(first(split(steps('network').vnet.controlPlaneSubnetAddressCidrRange, '/')), '.'), 1))), true)]", + "isValid": "[if(greaterOrEquals(last(split(steps('network').vnet.virtualNetworkAddressCidrRange, '/')), 8), equals(last(take(split(first(split(steps('network').vnet.virtualNetworkAddressCidrRange, '/')), '.'), 1)), last(take(split(first(split(steps('network').vnet.managementSubnetAddressCidrRange, '/')), '.'), 1))), true)]", "message": "CIDR range not within virtual network CIDR range (first octet)." }, { - "isValid": "[if(greaterOrEquals(last(split(steps('network').vnet.virtualNetworkAddressCidrRange, '/')), 16), equals(last(take(split(first(split(steps('network').vnet.virtualNetworkAddressCidrRange, '/')), '.'), 2)), last(take(split(first(split(steps('network').vnet.controlPlaneSubnetAddressCidrRange, '/')), '.'), 2))), true)]", + "isValid": "[if(greaterOrEquals(last(split(steps('network').vnet.virtualNetworkAddressCidrRange, '/')), 16), equals(last(take(split(first(split(steps('network').vnet.virtualNetworkAddressCidrRange, '/')), '.'), 2)), last(take(split(first(split(steps('network').vnet.managementSubnetAddressCidrRange, '/')), '.'), 2))), true)]", "message": "CIDR range not within virtual network CIDR range (second octet)." }, { - "isValid": "[if(greaterOrEquals(last(split(steps('network').vnet.virtualNetworkAddressCidrRange, '/')), 24), equals(last(take(split(first(split(steps('network').vnet.virtualNetworkAddressCidrRange, '/')), '.'), 3)), last(take(split(first(split(steps('network').vnet.controlPlaneSubnetAddressCidrRange, '/')), '.'), 3))), true)]", + "isValid": "[if(greaterOrEquals(last(split(steps('network').vnet.virtualNetworkAddressCidrRange, '/')), 24), equals(last(take(split(first(split(steps('network').vnet.virtualNetworkAddressCidrRange, '/')), '.'), 3)), last(take(split(first(split(steps('network').vnet.managementSubnetAddressCidrRange, '/')), '.'), 3))), true)]", "message": "CIDR range not within virtual network CIDR range (third octet)." }, { - "isValid": "[lessOrEquals(last(split(steps('network').vnet.virtualNetworkAddressCidrRange, '/')), last(split(steps('network').vnet.controlPlaneSubnetAddressCidrRange, '/')))]", + "isValid": "[lessOrEquals(last(split(steps('network').vnet.virtualNetworkAddressCidrRange, '/')), last(split(steps('network').vnet.managementSubnetAddressCidrRange, '/')))]", "message": "CIDR range not within virtual network CIDR range (subnet mask)." } ] @@ -1451,7 +1384,7 @@ "name": "functionAppSubnetAddressCidrRange", "label": "Function App Subnet CIDR range", "type": "Microsoft.Common.TextBox", - "visible": "[or(steps('management').scalingTool.deployScalingTool, equals(steps('profiles').storage.service, 'AzureFiles Premium'))]", + "visible": "[equals(steps('profiles').storage.service, 'AzureFiles Premium')]", "defaultValue": "[concat('10.0.1', string(add(41, mul(2, steps('basics').naming.stampIndex))), '.128/26')]", "toolTip": "Specify a CIDR range for the Function App subnet within the AVD spoke virtual network range [24,28] for the Scaling Tool and / or the Auto Increase Premium File Share Qutoa tool.", "constraints": { @@ -1640,6 +1573,8 @@ "Microsoft.Compute/virtualMachines", "Microsoft.DesktopVirtualization/applicationGroups", "Microsoft.DesktopVirtualization/hostPools", + "Microsoft.DesktopVirtualization/scalingPlans", + "Microsoft.DesktopVirtualization/workspaces", "Microsoft.Insights/components", "Microsoft.Insights/dataCollectionEndpoints", "Microsoft.Insights/dataCollectionRules", @@ -1668,34 +1603,34 @@ "parameters": { "activeDirectorySolution": "[if(and(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'), steps('hosts').identity.intune), 'MicrosoftEntraIdIntuneEnrollment', steps('hosts').identity.solution)]", "availability": "[steps('hosts').virtualMachines.availability]", - "availabilityZones": "[first(map(first(map(filter(steps('hosts').virtualMachines.resourceSkusApi.value, (item) => contains(item.name, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Pooled')), steps('hosts').virtualMachines.sizeArcGisProMulti, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Personal'), equals(steps('controlPlane').hostPool.workloadType, 6)), steps('hosts').virtualMachines.sizeArcGisProSingleLight, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Personal'), equals(steps('controlPlane').hostPool.workloadType, 4)), steps('hosts').virtualMachines.sizeArcGisProSingleMedium, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Personal'), equals(steps('controlPlane').hostPool.workloadType, 3)), steps('hosts').virtualMachines.sizeArcGisProSingleHeavy, steps('hosts').virtualMachines.sizeGeneric)))))), (item) => item.locationInfo)), (item) => item.zones))]", - "avdObjectId": "[if(empty(steps('management').servicePrincipalsApi), steps('management').startVmOnConnect.objectId, first(map(steps('management').servicePrincipalsApi.value, (item) => item.id)))]", + "availabilityZones": "[first(map(first(map(filter(steps('hosts').virtualMachines.resourceSkusApi.value, (item) => contains(item.name, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Pooled')), steps('hosts').virtualMachines.sizeArcGisProMulti, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Personal'), equals(steps('management').hostPool.workloadType, 6)), steps('hosts').virtualMachines.sizeArcGisProSingleLight, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Personal'), equals(steps('management').hostPool.workloadType, 4)), steps('hosts').virtualMachines.sizeArcGisProSingleMedium, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Personal'), equals(steps('management').hostPool.workloadType, 3)), steps('hosts').virtualMachines.sizeArcGisProSingleHeavy, steps('hosts').virtualMachines.sizeGeneric)))))), (item) => item.locationInfo)), (item) => item.zones))]", + "avdObjectId": "[first(map(steps('management').servicePrincipalsApi.value, (item) => item.id))]", "azureNetAppFilesSubnetAddressPrefix": "[steps('network').vnet.azureNetAppFilesSubnetAddressCidrRange]", - "customRdpProperty": "[steps('controlPlane').hostPool.customRdpProperties]", + "customRdpProperty": "[steps('management').hostPool.customRdpProperties]", "deployActivityLogDiagnosticSetting": "[empty(steps('compliance').diagnosticSettingsApi.value)]", "deployDefender": "[and(steps('compliance').defenderForCloud.deployDefender, empty(steps('compliance').defenderForCloud.workspaceSettingsApi.value))]", "deployNetworkWatcher": "[empty(filter(steps('basics').networkWatchersApi.value, (item) => equals(item.location, steps('basics').scope.location.name)))]", "deployPolicy": "[steps('compliance').policySection.deployPolicy]", - "desktopFriendlyName": "[steps('controlPlane').friendlyNames.sessionDesktop]", + "desktopFriendlyName": "[steps('management').friendlyNames.sessionDesktop]", "diskSku": "[if(equals(steps('basics').scenario.profile, 'arcGisPro'), 'Premium_LRS', steps('hosts').virtualMachines.diskSku)]", "domainJoinPassword": "[steps('hosts').domainJoinCredentials.domainPassword]", "domainJoinUserPrincipalName": "[steps('hosts').domainJoinCredentials.domainUserPrincipalName]", "domainName": "[steps('hosts').identity.domainName]", "drainMode": "[steps('management').drainMode.enableDrainMode]", "emailSecurityContact": "[if(and(steps('compliance').defenderForCloud.deployDefender, empty(steps('compliance').defenderForCloud.workspaceSettingsApi.value)), steps('compliance').defenderForCloud.emailSecurityContact, '')]", - "enableAcceleratedNetworking": "[bool(first(map(filter(first(map(filter(steps('hosts').virtualMachines.resourceSkusApi.value, (item) => contains(item.name, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Pooled')), steps('hosts').virtualMachines.sizeArcGisProMulti, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Personal'), equals(steps('controlPlane').hostPool.workloadType, 6)), steps('hosts').virtualMachines.sizeArcGisProSingleLight, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Personal'), equals(steps('controlPlane').hostPool.workloadType, 4)), steps('hosts').virtualMachines.sizeArcGisProSingleMedium, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Personal'), equals(steps('controlPlane').hostPool.workloadType, 3)), steps('hosts').virtualMachines.sizeArcGisProSingleHeavy, steps('hosts').virtualMachines.sizeGeneric)))))), (item) => item.capabilities)), (item) => equals(item.name, 'AcceleratedNetworkingEnabled')), (item) => item.value)))]", + "enableAcceleratedNetworking": "[bool(first(map(filter(first(map(filter(steps('hosts').virtualMachines.resourceSkusApi.value, (item) => contains(item.name, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Pooled')), steps('hosts').virtualMachines.sizeArcGisProMulti, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Personal'), equals(steps('management').hostPool.workloadType, 6)), steps('hosts').virtualMachines.sizeArcGisProSingleLight, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Personal'), equals(steps('management').hostPool.workloadType, 4)), steps('hosts').virtualMachines.sizeArcGisProSingleMedium, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Personal'), equals(steps('management').hostPool.workloadType, 3)), steps('hosts').virtualMachines.sizeArcGisProSingleHeavy, steps('hosts').virtualMachines.sizeGeneric)))))), (item) => item.capabilities)), (item) => equals(item.name, 'AcceleratedNetworkingEnabled')), (item) => item.value)))]", "enableApplicationInsights": "[steps('management').monitoring.enableApplicationInsights]", "enableAvdInsights": "[steps('management').monitoring.enableAvdInsights]", "enableTelemetry": "[steps('basics').scenario.enablePartnerTelemetry]", "environmentAbbreviation": "[steps('basics').naming.environment]", "existingSharedActiveDirectoryConnection": "[steps('profiles').storage.existingSharedActiveDirectoryConnection]", - "existingFeedWorkspaceResourceId": "[if(empty(steps('controlPlane').workspacesApi.value), '', first(map(filter(steps('controlPlane').workspacesApi.value, (item) => and(contains(item.name, steps('basics').naming.identifier), contains(item.name, 'feed'), contains(item.name, steps('basics').naming.environment), equals(item.location, steps('basics').hub.azureFirewallApi.location))), (item) => item.id)))]", + "existingFeedWorkspaceResourceId": "[if(empty(steps('management').workspacesApi.value), '', first(map(filter(steps('management').workspacesApi.value, (item) => and(contains(item.name, steps('basics').naming.identifier), contains(item.name, 'feed'), contains(item.name, steps('basics').naming.environment), equals(item.location, steps('basics').hub.azureFirewallApi.location))), (item) => item.id)))]", "fslogixShareSizeInGB": "[if(equals(steps('profiles').profileSolution, 'local'), 100, steps('profiles').storage.fileShareSize)]", "fslogixContainerType": "[steps('profiles').storage.fslogixContainerType]", "fslogixStorageService": "[if(equals(steps('profiles').profileSolution, 'local'), 'None', steps('profiles').storage.service)]", "functionAppSubnetAddressPrefix": "[steps('network').vnet.functionAppSubnetAddressCidrRange]", - "hostPoolPublicNetworkAccess": "[steps('controlPlane').hostPool.publicNetworkAccess]", - "hostPoolType": "[if(equals(steps('controlPlane').hostPool.hostPoolType, 'Pooled'), 'Pooled DepthFirst', 'Personal Automatic')]", + "hostPoolPublicNetworkAccess": "[steps('management').hostPool.publicNetworkAccess]", + "hostPoolType": "[steps('management').hostPool.hostPoolType]", "hubAzureFirewallResourceId": "[steps('basics').hub.azureFirewall]", "hubVirtualNetworkResourceId": "[steps('basics').hub.virtualNetwork]", "identifier": "[steps('basics').naming.identifier]", @@ -1712,30 +1647,28 @@ "privateLinkScopeResourceId": "[steps('basics').operations.privateLinkScope]", "profile": "[steps('basics').scenario.profile]", "recoveryServices": "[steps('management').backup.recoveryServices]", - "scalingBeginPeakTime": "[steps('management').scalingTool.beginPeakTime]", - "scalingEndPeakTime": "[steps('management').scalingTool.endPeakTime]", - "scalingLimitSecondsToForceLogOffUser": "[steps('management').scalingTool.forceLogOff]", - "scalingMinimumNumberOfRdsh": "[steps('management').scalingTool.minimumHosts]", - "scalingSessionThresholdPerCPU": "[steps('management').scalingTool.cpuThreshold]", - "scalingTool": "[steps('management').scalingTool.deployScalingTool]", - "securityPrincipals": "[steps('controlPlane').assignment.groups]", + "scalingWeekdaysOffPeakStartTime": "[steps('management').scalingPlan.weekdaysOffPeakStartTime]", + "scalingWeekdaysPeakStartTime": "[steps('management').scalingPlan.weekdaysPeakStartTime]", + "scalingWeekendsOffPeakStartTime": "[steps('management').scalingPlan.weekendsOffPeakStartTime]", + "scalingWeekendsPeakStartTime": "[steps('management').scalingPlan.weekendsPeakStartTime]", + "securityPrincipals": "[steps('management').assignment.groupPickerBlade.transformed.selection]", "sessionHostCount": "[steps('hosts').virtualMachines.count]", "sessionHostIndex": 0, "sharedServicesSubnetResourceId": "[first(map(steps('basics').sharedServicesSubnetsApi.value, (item) => item.id))]", "stampIndex": "[steps('basics').naming.stampIndex]", - "storageCount": "[if(equals(steps('profiles').profileSolution, 'local'), 0, length(steps('controlPlane').assignment.groups))]", + "storageCount": "[if(equals(steps('profiles').profileSolution, 'local'), 0, length(steps('management').assignment.groupPickerBlade.transformed.selection))]", "storageIndex": 0, - "subnetAddressPrefixes": "[parse(concat('[\"', steps('network').vnet.sessionHostsSubnetAddressCidrRange, '\",\"', steps('network').vnet.controlPlaneSubnetAddressCidrRange, '\"]'))]", + "subnetAddressPrefixes": "[parse(concat('[\"', steps('network').vnet.sessionHostsSubnetAddressCidrRange, '\",\"', steps('network').vnet.managementSubnetAddressCidrRange, '\"]'))]", "tags": "[steps('tags').tags]", - "usersPerCore": "[if(equals(steps('controlPlane').hostPool.hostPoolType, 'Pooled'), steps('controlPlane').hostPool.workloadType, 1)]", - "validationEnvironment": "[steps('controlPlane').hostPool.validation]", + "usersPerCore": "[if(equals(steps('management').hostPool.hostPoolType, 'Pooled'), steps('management').hostPool.workloadType, 1)]", + "validationEnvironment": "[steps('management').hostPool.validation]", "virtualMachinePassword": "[steps('hosts').localAdminCredentials.localAdminPassword]", - "virtualMachineSize": "[if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Pooled')), steps('hosts').virtualMachines.sizeArcGisProMulti, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Personal'), equals(steps('controlPlane').hostPool.workloadType, 6)), steps('hosts').virtualMachines.sizeArcGisProSingleLight, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Personal'), equals(steps('controlPlane').hostPool.workloadType, 4)), steps('hosts').virtualMachines.sizeArcGisProSingleMedium, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Personal'), equals(steps('controlPlane').hostPool.workloadType, 3)), steps('hosts').virtualMachines.sizeArcGisProSingleHeavy, steps('hosts').virtualMachines.sizeGeneric))))]", + "virtualMachineSize": "[if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Pooled')), steps('hosts').virtualMachines.sizeArcGisProMulti, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Personal'), equals(steps('management').hostPool.workloadType, 6)), steps('hosts').virtualMachines.sizeArcGisProSingleLight, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Personal'), equals(steps('management').hostPool.workloadType, 4)), steps('hosts').virtualMachines.sizeArcGisProSingleMedium, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Personal'), equals(steps('management').hostPool.workloadType, 3)), steps('hosts').virtualMachines.sizeArcGisProSingleHeavy, steps('hosts').virtualMachines.sizeGeneric))))]", "virtualMachineUsername": "[steps('hosts').localAdminCredentials.localAdminUsername]", - "virtualMachineVirtualCpuCount": "[first(map(filter(first(map(filter(steps('basics').scenario.virtualMachineSkusApi.value, (item) => equals(item.name, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Pooled')), steps('hosts').virtualMachines.sizeArcGisProMulti, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Personal'), equals(steps('controlPlane').hostPool.workloadType, 6)), steps('hosts').virtualMachines.sizeArcGisProSingleLight, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Personal'), equals(steps('controlPlane').hostPool.workloadType, 4)), steps('hosts').virtualMachines.sizeArcGisProSingleMedium, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('controlPlane').hostPool.hostPoolType, 'Personal'), equals(steps('controlPlane').hostPool.workloadType, 3)), steps('hosts').virtualMachines.sizeArcGisProSingleHeavy, steps('hosts').virtualMachines.sizeGeneric)))))), (item) => item.capabilities)), (item) => equals(item.name, 'vCPUs')), (item) => item.value))]", + "virtualMachineVirtualCpuCount": "[first(map(filter(first(map(filter(steps('basics').scenario.virtualMachineSkusApi.value, (item) => equals(item.name, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Pooled')), steps('hosts').virtualMachines.sizeArcGisProMulti, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Personal'), equals(steps('management').hostPool.workloadType, 6)), steps('hosts').virtualMachines.sizeArcGisProSingleLight, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Personal'), equals(steps('management').hostPool.workloadType, 4)), steps('hosts').virtualMachines.sizeArcGisProSingleMedium, if(and(equals(steps('basics').scenario.profile, 'arcGisPro'), equals(steps('management').hostPool.hostPoolType, 'Personal'), equals(steps('management').hostPool.workloadType, 3)), steps('hosts').virtualMachines.sizeArcGisProSingleHeavy, steps('hosts').virtualMachines.sizeGeneric)))))), (item) => item.capabilities)), (item) => equals(item.name, 'vCPUs')), (item) => item.value))]", "virtualNetworkAddressPrefixes": "[parse(concat('[\"', steps('network').vnet.virtualNetworkAddressCidrRange, '\"]'))]", - "workspaceFriendlyName": "[if(empty(first(map(filter(steps('controlPlane').workspacesApi.value, (item) => and(contains(item.name, steps('basics').naming.identifier), contains(item.name, 'feed'), contains(item.name, steps('basics').naming.environment), equals(item.location, steps('basics').hub.azureFirewallApi.location))), (item) => item.id))), steps('controlPlane').friendlyNames.feedWorkspace, first(map(filter(steps('controlPlane').workspacesApi.value, (item) => and(contains(item.name, steps('basics').naming.identifier), contains(item.name, 'feed'), contains(item.name, steps('basics').naming.environment), equals(item.location, steps('basics').hub.azureFirewallApi.location))), (item) => item.properties.friendlyName)))]", - "workspacePublicNetworkAccess": "[if(empty(first(map(filter(steps('controlPlane').workspacesApi.value, (item) => and(contains(item.name, steps('basics').naming.identifier), contains(item.name, 'feed'), contains(item.name, steps('basics').naming.environment), equals(item.location, steps('basics').hub.azureFirewallApi.location))), (item) => item.id))), steps('controlPlane').workspace.publicNetworkAccess, first(map(filter(steps('controlPlane').workspacesApi.value, (item) => and(contains(item.name, steps('basics').naming.identifier), contains(item.name, 'feed'), contains(item.name, steps('basics').naming.environment), equals(item.location, steps('basics').hub.azureFirewallApi.location))), (item) => item.properties.publicNetworkAccess)))]" + "workspaceFriendlyName": "[if(empty(first(map(filter(steps('management').workspacesApi.value, (item) => and(contains(item.name, steps('basics').naming.identifier), contains(item.name, 'feed'), contains(item.name, steps('basics').naming.environment), equals(item.location, steps('basics').hub.azureFirewallApi.location))), (item) => item.id))), steps('management').friendlyNames.feedWorkspace, first(map(filter(steps('management').workspacesApi.value, (item) => and(contains(item.name, steps('basics').naming.identifier), contains(item.name, 'feed'), contains(item.name, steps('basics').naming.environment), equals(item.location, steps('basics').hub.azureFirewallApi.location))), (item) => item.properties.friendlyName)))]", + "workspacePublicNetworkAccess": "[if(empty(first(map(filter(steps('management').workspacesApi.value, (item) => and(contains(item.name, steps('basics').naming.identifier), contains(item.name, 'feed'), contains(item.name, steps('basics').naming.environment), equals(item.location, steps('basics').hub.azureFirewallApi.location))), (item) => item.id))), steps('management').workspace.publicNetworkAccess, first(map(filter(steps('management').workspacesApi.value, (item) => and(contains(item.name, steps('basics').naming.identifier), contains(item.name, 'feed'), contains(item.name, steps('basics').naming.environment), equals(item.location, steps('basics').hub.azureFirewallApi.location))), (item) => item.properties.publicNetworkAccess)))]" }, "kind": "Subscription", "location": "[steps('basics').scope.location.name]", diff --git a/src/bicep/add-ons/imaging/solution.json b/src/bicep/add-ons/imaging/solution.json index 70b71996e..bccc542d8 100644 --- a/src/bicep/add-ons/imaging/solution.json +++ b/src/bicep/add-ons/imaging/solution.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7574127122781673801" + "version": "0.31.92.45157", + "templateHash": "1176843216741362451" } }, "parameters": { @@ -600,8 +600,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "5332647717093931418" + "version": "0.31.92.45157", + "templateHash": "14266827690996546891" } }, "parameters": { @@ -877,8 +877,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "11424932506966599764" + "version": "0.31.92.45157", + "templateHash": "15810721730485220824" } }, "parameters": { @@ -957,8 +957,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7616623366375139473" + "version": "0.31.92.45157", + "templateHash": "11125044402666498605" } }, "parameters": { @@ -984,6 +984,7 @@ "tokens": { "type": "object", "defaultValue": { + "purpose": "purpose_token", "resource": "resource_token", "service": "service_token" } @@ -1409,6 +1410,7 @@ "remoteApplicationGroups": "vdag", "resourceGroups": "rg", "routeTables": "rt", + "scalingPlans": "vdscaling", "storageAccounts": "st", "subnets": "snet", "userAssignedIdentities": "id", @@ -1419,11 +1421,11 @@ "locations": "[variables('$fxv#0')[environment().name]]", "locationAbbreviation": "[variables('locations')[parameters('location')].abbreviation]", "resourceAbbreviations": "[variables('$fxv#1')]", - "namingConvention": "[format('{0}-{1}{2}-{3}-{4}-{5}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), parameters('environmentAbbreviation'), variables('locationAbbreviation'))]", - "namingConvention_Service": "[format('{0}-{1}{2}-{3}-{4}-{5}-{6}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('tokens').service, parameters('networkName'), parameters('environmentAbbreviation'), variables('locationAbbreviation'))]", + "namingConvention": "[format('{0}-{1}{2}-{3}-{4}-{5}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), variables('locationAbbreviation'), parameters('environmentAbbreviation'))]", + "namingConvention_Service": "[format('{0}-{1}{2}-{3}-{4}-{5}-{6}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), parameters('tokens').service, variables('locationAbbreviation'), parameters('environmentAbbreviation'))]", "names": { "actionGroup": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').actionGroups)]", - "applicationGroup": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').applicationGroups)]", + "applicationGroup": "[replace(variables('namingConvention'), parameters('tokens').resource, format('{0}-desktop', variables('resourceAbbreviations').applicationGroups))]", "applicationInsights": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').applicationInsights)]", "appServicePlan": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').appServicePlans)]", "automationAccount": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').automationAccounts)]", @@ -1477,10 +1479,18 @@ "recoveryServicesVaultPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, variables('resourceAbbreviations').recoveryServicesVaults)]", "resourceGroup": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').resourceGroups)]", "routeTable": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').routeTables)]", + "scalingPlan": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').scalingPlans)]", + "scalingPlanDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').scalingPlans)]", "storageAccount": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').storageAccounts), parameters('networkName'), parameters('networkShortName'))]", "storageAccountDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", - "storageAccountNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", - "storageAccountPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", + "storageAccountBlobNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-blob', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountFileNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-file', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountQueueNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-queue', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountTableNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-table', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountBlobPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-blob', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountFilePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-file', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountQueuePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-queue', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountTablePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-table', variables('resourceAbbreviations').storageAccounts))]", "subnet": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').subnets)]", "userAssignedIdentity": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').userAssignedIdentities)]", "virtualMachine": "[replace(replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').virtualMachines), parameters('environmentAbbreviation'), first(parameters('environmentAbbreviation'))), parameters('networkName'), ''), '-', '')]", @@ -1488,14 +1498,14 @@ "virtualMachineNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').virtualMachines))]", "virtualNetwork": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').virtualNetworks)]", "virtualNetworkDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').virtualNetworks)]", - "workspaceFeed": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').workspaces), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobal": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').workspaces), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]" + "workspaceFeed": "[replace(replace(variables('namingConvention'), parameters('tokens').resource, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceFeedDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceFeedNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceFeedPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobal": "[replace(replace(variables('namingConvention'), parameters('tokens').resource, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobalDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobalNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobalPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]" } }, "resources": [], @@ -1541,8 +1551,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "4151572700986885014" + "version": "0.31.92.45157", + "templateHash": "17619606927129129347" } }, "parameters": { @@ -1681,8 +1691,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10109660736443081073" + "version": "0.31.92.45157", + "templateHash": "9371138343009436350" } }, "parameters": { @@ -1810,8 +1820,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2001402118470594055" + "version": "0.31.92.45157", + "templateHash": "12095518102496352105" } }, "parameters": { @@ -1946,8 +1956,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3535186238457701125" + "version": "0.31.92.45157", + "templateHash": "11818136489056939588" } }, "parameters": { @@ -2064,8 +2074,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "6722359274420487391" + "version": "0.31.92.45157", + "templateHash": "3681943409502537301" } }, "parameters": { @@ -2147,8 +2157,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "12652296496577802490" + "version": "0.31.92.45157", + "templateHash": "18425321023142226965" } }, "parameters": { @@ -2250,8 +2260,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17592952825859536181" + "version": "0.31.92.45157", + "templateHash": "15002544166394392504" } }, "parameters": { @@ -2327,8 +2337,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7007835755326231171" + "version": "0.31.92.45157", + "templateHash": "16817025486402215719" } }, "parameters": { @@ -2468,8 +2478,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3832300165614083813" + "version": "0.31.92.45157", + "templateHash": "10267893616110384815" } }, "parameters": { @@ -2521,8 +2531,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2015499370398293300" + "version": "0.31.92.45157", + "templateHash": "5574526676512163672" } }, "parameters": { @@ -2595,8 +2605,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "9502319494004310539" + "version": "0.31.92.45157", + "templateHash": "11553909803736438916" } }, "parameters": { @@ -2648,8 +2658,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2015499370398293300" + "version": "0.31.92.45157", + "templateHash": "5574526676512163672" } }, "parameters": { @@ -2748,8 +2758,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10999644342250296299" + "version": "0.31.92.45157", + "templateHash": "4304275711041823961" } }, "parameters": { @@ -2806,8 +2816,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2757774880390840506" + "version": "0.31.92.45157", + "templateHash": "16077950968688123011" } }, "parameters": { @@ -2909,8 +2919,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "11152278840466293351" + "version": "0.31.92.45157", + "templateHash": "12787329163785242553" } }, "parameters": { @@ -2998,8 +3008,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "1874197755006172394" + "version": "0.31.92.45157", + "templateHash": "11761568940379970751" } }, "parameters": { @@ -3256,8 +3266,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17613382135787640077" + "version": "0.31.92.45157", + "templateHash": "4207798980384159491" } }, "parameters": { @@ -3336,8 +3346,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8372265102618018066" + "version": "0.31.92.45157", + "templateHash": "1966035938992047983" } }, "parameters": { @@ -3431,8 +3441,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "6675708379514380442" + "version": "0.31.92.45157", + "templateHash": "7930493629995578222" } }, "parameters": { @@ -3537,6 +3547,9 @@ "blobsPrivateDnsZoneResourceId": { "value": "[resourceId(variables('hubSubscriptionId'), variables('hubResourceGroupName'), 'Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" }, + "filesPrivateDnsZoneResourceId": { + "value": "[resourceId(variables('hubSubscriptionId'), variables('hubResourceGroupName'), 'Microsoft.Network/privateDnsZones', format('privatelink.file.{0}', environment().suffixes.storage))]" + }, "keyVaultUri": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-cmk-{0}-{1}', parameters('workloadShortName'), parameters('deploymentNameSuffix'))), '2022-09-01').outputs.keyVaultUri.value]" }, @@ -3552,6 +3565,9 @@ "network": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-logic-{0}-{1}', parameters('workloadShortName'), parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tiers.value[0]]" }, + "queuesPrivateDnsZoneResourceId": { + "value": "[resourceId(variables('hubSubscriptionId'), variables('hubResourceGroupName'), 'Microsoft.Network/privateDnsZones', format('privatelink.queue.{0}', environment().suffixes.storage))]" + }, "resourceGroupName": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', parameters('workloadShortName'), parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" }, @@ -3583,14 +3599,17 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17995428208905585384" + "version": "0.31.92.45157", + "templateHash": "17358288344184718166" } }, "parameters": { "blobsPrivateDnsZoneResourceId": { "type": "string" }, + "filesPrivateDnsZoneResourceId": { + "type": "string" + }, "keyVaultUri": { "type": "string" }, @@ -3606,6 +3625,9 @@ "network": { "type": "object" }, + "queuesPrivateDnsZoneResourceId": { + "type": "string" + }, "resourceGroupName": { "type": "string" }, @@ -3647,6 +3669,9 @@ "blobsPrivateDnsZoneResourceId": { "value": "[parameters('blobsPrivateDnsZoneResourceId')]" }, + "filesPrivateDnsZoneResourceId": { + "value": "[parameters('filesPrivateDnsZoneResourceId')]" + }, "keyVaultUri": { "value": "[parameters('keyVaultUri')]" }, @@ -3656,6 +3681,9 @@ "mlzTags": { "value": "[parameters('mlzTags')]" }, + "queuesPrivateDnsZoneResourceId": { + "value": "[parameters('queuesPrivateDnsZoneResourceId')]" + }, "serviceToken": { "value": "[parameters('serviceToken')]" }, @@ -3687,14 +3715,17 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10231061264498799420" + "version": "0.31.92.45157", + "templateHash": "1625826941635729014" } }, "parameters": { "blobsPrivateDnsZoneResourceId": { "type": "string" }, + "filesPrivateDnsZoneResourceId": { + "type": "string" + }, "keyVaultUri": { "type": "string" }, @@ -3704,6 +3735,9 @@ "mlzTags": { "type": "object" }, + "queuesPrivateDnsZoneResourceId": { + "type": "string" + }, "serviceToken": { "type": "string" }, @@ -3730,9 +3764,27 @@ } }, "variables": { - "zones": [ - "[parameters('blobsPrivateDnsZoneResourceId')]", - "[parameters('tablesPrivateDnsZoneResourceId')]" + "subResources": [ + { + "id": "[parameters('blobsPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountBlobNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountBlobPrivateEndpoint]" + }, + { + "id": "[parameters('filesPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountFileNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountFilePrivateEndpoint]" + }, + { + "id": "[parameters('queuesPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountQueueNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountQueuePrivateEndpoint]" + }, + { + "id": "[parameters('tablesPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountTableNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountTablePrivateEndpoint]" + } ] }, "resources": [ @@ -3803,22 +3855,22 @@ { "copy": { "name": "privateEndpoints", - "count": "[length(variables('zones'))]" + "count": "[length(variables('subResources'))]" }, "type": "Microsoft.Network/privateEndpoints", "apiVersion": "2023-04-01", - "name": "[replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]))]", + "name": "[variables('subResources')[copyIndex()].pe]", "location": "[parameters('location')]", "tags": "[union(if(contains(parameters('tags'), 'Microsoft.Network/privateEndpoints'), parameters('tags')['Microsoft.Network/privateEndpoints'], createObject()), parameters('mlzTags'))]", "properties": { - "customNetworkInterfaceName": "[replace(parameters('tier').namingConvention.storageAccountNetworkInterface, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]))]", + "customNetworkInterfaceName": "[variables('subResources')[copyIndex()].nic]", "privateLinkServiceConnections": [ { - "name": "[replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]))]", + "name": "[variables('subResources')[copyIndex()].pe]", "properties": { "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]", "groupIds": [ - "[split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]]" + "[split(split(variables('subResources')[copyIndex()].id, '/')[8], '.')[1]]" ] } } @@ -3834,23 +3886,23 @@ { "copy": { "name": "privateDnsZoneGroups", - "count": "[length(variables('zones'))]" + "count": "[length(variables('subResources'))]" }, "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", "apiVersion": "2021-08-01", - "name": "[format('{0}/{1}', replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1])), uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]", + "name": "[format('{0}/{1}', variables('subResources')[copyIndex()].pe, uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]", "properties": { "privateDnsZoneConfigs": [ { "name": "ipconfig1", "properties": { - "privateDnsZoneId": "[variables('zones')[copyIndex()]]" + "privateDnsZoneId": "[variables('subResources')[copyIndex()].id]" } } ] }, "dependsOn": [ - "[resourceId('Microsoft.Network/privateEndpoints', replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1])))]", + "[resourceId('Microsoft.Network/privateEndpoints', variables('subResources')[copyIndex()].pe)]", "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]" ] } @@ -3944,8 +3996,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8480968961555779332" + "version": "0.31.92.45157", + "templateHash": "4345251511078445463" } }, "parameters": { @@ -4019,8 +4071,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8389358797157027271" + "version": "0.31.92.45157", + "templateHash": "4687229436121899773" } }, "parameters": { @@ -4109,8 +4161,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3320497816733398371" + "version": "0.31.92.45157", + "templateHash": "1721966359516622278" } }, "parameters": { @@ -4183,8 +4235,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "9851134383266019486" + "version": "0.31.92.45157", + "templateHash": "2073766618455932098" } }, "parameters": { @@ -4261,8 +4313,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7358671180047253284" + "version": "0.31.92.45157", + "templateHash": "9546260853018527046" } }, "parameters": { @@ -4352,8 +4404,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7046499602359448652" + "version": "0.31.92.45157", + "templateHash": "15014526386353172066" } }, "parameters": { @@ -4409,8 +4461,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "16614224552992880134" + "version": "0.31.92.45157", + "templateHash": "8545903679924437739" } }, "parameters": { @@ -4585,8 +4637,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8372265102618018066" + "version": "0.31.92.45157", + "templateHash": "1966035938992047983" } }, "parameters": { @@ -4670,8 +4722,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3588347827815061814" + "version": "0.31.92.45157", + "templateHash": "17047820191891552534" } }, "parameters": { @@ -5020,8 +5072,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "5226016669112266321" + "version": "0.31.92.45157", + "templateHash": "10919933768401588181" } }, "parameters": { @@ -5094,8 +5146,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "13440676993724521939" + "version": "0.31.92.45157", + "templateHash": "18352816396611024893" } }, "parameters": { @@ -5160,8 +5212,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17072503057563414192" + "version": "0.31.92.45157", + "templateHash": "6200502377293455151" } }, "parameters": { @@ -5224,8 +5276,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "15644482179055938365" + "version": "0.31.92.45157", + "templateHash": "7660308816591621829" } }, "parameters": { @@ -5283,8 +5335,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "14434555555821069254" + "version": "0.31.92.45157", + "templateHash": "4828751436439954614" } }, "parameters": { @@ -5354,8 +5406,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "18308303526064452173" + "version": "0.31.92.45157", + "templateHash": "505668065736432808" } }, "parameters": { @@ -5444,8 +5496,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17898789400078556340" + "version": "0.31.92.45157", + "templateHash": "15938438919628281730" } }, "parameters": { @@ -5722,8 +5774,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "14434361731662776378" + "version": "0.31.92.45157", + "templateHash": "3864397971513159924" } }, "parameters": { @@ -5977,8 +6029,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7158084538460059744" + "version": "0.31.92.45157", + "templateHash": "10151591372092483611" } }, "parameters": { @@ -6063,8 +6115,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "1996155124645705619" + "version": "0.31.92.45157", + "templateHash": "16335756069146472371" } }, "parameters": { @@ -6273,8 +6325,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2306888083293626312" + "version": "0.31.92.45157", + "templateHash": "17205876022497810397" } }, "parameters": { @@ -8361,8 +8413,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10068687414279329406" + "version": "0.31.92.45157", + "templateHash": "8601742688955831915" } }, "parameters": { @@ -8728,8 +8780,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "15496706649353968351" + "version": "0.31.92.45157", + "templateHash": "4852229183946608444" } }, "parameters": { @@ -9264,8 +9316,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2877200692627316157" + "version": "0.31.92.45157", + "templateHash": "8637536946355167151" } }, "parameters": { @@ -9638,8 +9690,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10741782051085334434" + "version": "0.31.92.45157", + "templateHash": "8072924941165181371" } }, "parameters": { @@ -9912,8 +9964,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10068687414279329406" + "version": "0.31.92.45157", + "templateHash": "8601742688955831915" } }, "parameters": { @@ -10122,8 +10174,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8116252798517351972" + "version": "0.31.92.45157", + "templateHash": "6378421340828498206" } }, "parameters": { @@ -10373,8 +10425,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "885927068946767612" + "version": "0.31.92.45157", + "templateHash": "14159413360692546906" } }, "parameters": { @@ -10796,8 +10848,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "13463113052745382608" + "version": "0.31.92.45157", + "templateHash": "3743593020967196481" } }, "parameters": { @@ -10899,8 +10951,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "1633398337190030443" + "version": "0.31.92.45157", + "templateHash": "7677543733842229678" } }, "parameters": { @@ -10986,8 +11038,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "13463113052745382608" + "version": "0.31.92.45157", + "templateHash": "3743593020967196481" } }, "parameters": { @@ -11082,8 +11134,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "15079303238971201589" + "version": "0.31.92.45157", + "templateHash": "1029512189778888686" } }, "parameters": { @@ -11163,8 +11215,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "6420082476378279690" + "version": "0.31.92.45157", + "templateHash": "8843400045516737228" } }, "parameters": { @@ -11284,8 +11336,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "9737618372857567001" + "version": "0.31.92.45157", + "templateHash": "5465461266607898562" } }, "parameters": { @@ -11440,8 +11492,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "13012724632844552518" + "version": "0.31.92.45157", + "templateHash": "4834529006346309280" } }, "parameters": { diff --git a/src/bicep/add-ons/imaging/uiDefinition.json b/src/bicep/add-ons/imaging/uiDefinition.json index 4c3039e9a..16a18b38a 100644 --- a/src/bicep/add-ons/imaging/uiDefinition.json +++ b/src/bicep/add-ons/imaging/uiDefinition.json @@ -13,9 +13,9 @@ "name": "prerequisites", "type": "Microsoft.Common.InfoBox", "options": { - "text": "Prior to deployment, make sure you meet the prerequisites outlined in the resource pre-reqs section in Zero Trust Image solution documentation.", - "uri": "https://github.com/mikedzikowski/ZTAImage#prequisites", - "icon": "Warning" + "style": "Warning", + "text": "Prior to deployment, make sure you meet the prerequisites outlined in the Zero Trust Image solution documentation.", + "uri": "https://github.com/Azure/missionlz/blob/main/src/bicep/add-ons/imaging/README.md#prequisites" } }, { @@ -546,10 +546,10 @@ "constraints": { "required": true, "validations": [ - { - "isValid": "[endsWith(steps('customizations').office, '.exe')]", - "message": "The file name must end with '.exe'." - } + { + "regex": ".*\\.exe$", + "message": "The file name must end with '.exe'." + } ] }, "visible": "[steps('customizations').office.installOffice]" @@ -817,10 +817,10 @@ "constraints": { "required": true, "validations": [ - { - "isValid": "[endsWith(steps('customizations').office.teamsBlob, '.msi')]", - "message": "The file name must end with '.msi'." - } + { + "regex": ".*\\.msi$", + "message": "The file name must end with '.msi'." + } ] }, "visible": "[steps('customizations').office.installTeams]" @@ -836,10 +836,10 @@ "constraints": { "required": true, "validations": [ - { - "isValid": "[endsWith(steps('customizations').office.msrdcwebrtcsvcInstaller, '.msi')]", - "message": "The file name must end with '.msi'." - } + { + "regex": ".*\\.msi$", + "message": "The file name must end with '.msi'." + } ] }, "visible": "[steps('customizations').office.installTeams]" @@ -854,11 +854,11 @@ "multiLine": false, "constraints": { "required": true, - "validations": [ - { - "isValid": "[endsWith(steps('customizations').office.vcRedistInstaller, '.exe')]", - "message": "The file name must end with '.exe'." - } + "validations": [ + { + "regex": ".*\\.exe$", + "message": "The file name must end with '.exe'." + } ] }, "visible": "[steps('customizations').office.installTeams]" @@ -969,7 +969,7 @@ { "name": "vDotBlob", "type": "Microsoft.Common.TextBox", - "label": "Microsoft Visual C++ Redistributable Installer (.zip)", + "label": "Virtual Desktop Optimization Tool File Name (.zip)", "defaultValue": "Virtual-Desktop-Optimization-Tool-main.zip", "toolTip": "Input the file / blob name for the VDOT installer zip.", "placeholder": "", @@ -977,10 +977,10 @@ "constraints": { "required": true, "validations": [ - { - "isValid": "[endsWith(steps('customizations').office.vDotBlob, '.zip')]", - "message": "The file name must end with '.zip'." - } + { + "regex": ".*\\.zip$", + "message": "The file name must end with '.zip'." + } ] }, "visible": "[steps('customizations').specialitySoftware.installVirtualDesktopOptimizationTool]" @@ -1016,10 +1016,10 @@ "constraints": { "required": true, "validations": [ - { - "isValid": "[endsWith(steps('customizations').specialitySoftware.arcGisBlob, '.zip')]", - "message": "The file name must end with '.zip'." - } + { + "regex": ".*\\.zip$", + "message": "The file name must end with '.zip'." + } ] }, "visible": "[steps('customizations').specialitySoftware.installArcGisPro]" @@ -1048,10 +1048,6 @@ "recommendedSizes": [ "Standard_D4ads_v5" ], - "constraints": { - "allowedSizes": [], - "numAvailabilityZonesRequired": 1 - }, "options": { "hideDiskTypeFilter": false }, @@ -1266,6 +1262,23 @@ "name": "networking", "label": "Networking", "elements": [ + { + "name": "prerequisitesNetworking", + "type": "Microsoft.Common.InfoBox", + "options": { + "style":"Warning", + "text": "Prior to deployment, make sure you meet the prerequisites outlined in the Zero Trust Image solution documentation.", + "uri": "https://github.com/Azure/missionlz/blob/main/src/bicep/add-ons/imaging/README.md#prequisites" + } + }, + { + "name": "prerequisitesNetworking2", + "type": "Microsoft.Common.InfoBox", + "options": { + "style":"Info", + "text": "This section applies to the deployment of a new Spoke or Tier 3 environment specifically for imaging which will be created for you. Choose a network and subnet range that doesn't conflict with the Hub or any other VNets in your MLZ." + } + }, { "name": "networking", "label": "Networking", @@ -1449,26 +1462,13 @@ "path": "[concat(steps('basics').hub.virtualNetwork, '?api-version=2023-09-01')]" } }, - { - "name": "logAnalyticsWorkspacesApi", - "type": "Microsoft.Solutions.ArmApiControl", - "request": { - "method": "GET", - "path": "[concat('/subscriptions/', first(skip(split(first(map(filter(steps('compliance').virtualNetworkApi.properties.virtualNetworkPeerings, (item) => contains(item.properties.remoteVirtualNetwork.id, 'operations')), (item) => item.properties.remoteVirtualNetwork.id)), '/'), 2)), '/resourcegroups/', first(skip(split(first(map(filter(steps('compliance').virtualNetworkApi.properties.virtualNetworkPeerings, (item) => contains(item.properties.remoteVirtualNetwork.id, 'operations')), (item) => item.properties.remoteVirtualNetwork.id)), '/'), 4)), '/providers/Microsoft.OperationalInsights/workspaces?api-version=2023-09-01')]" - } - }, - { - "name": "logAnalyticsWorkspace", - "type": "Microsoft.Common.DropDown", - "visible": true, - "label": "Existing Log Analytics Workspace for Central Logging", - "defaultValue": "[first(map(steps('compliance').logAnalyticsWorkspacesApi.value, (item) => item.name))]", - "filter": true, - "toolTip": "Select the existing Hub Azure firewall.", - "constraints": { - "required": true, - "allowedValues": "[map(steps('compliance').logAnalyticsWorkspacesApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.id, '\"}')))]" - } + { + "name": "logAnalyticsWorkspace", + "type": "Microsoft.Solutions.ResourceSelector", + "label": "Existing Log Analytics Workspace for Central Logging (Spoke)", + "visible": true, + "resourceType": "Microsoft.OperationalInsights/workspaces", + "toolTip": "Select the log analytics workspace to capture runbook log output." }, { "name": "diagnosticSettingsApi", @@ -1559,7 +1559,7 @@ "policy": "[if(steps('compliance').policySection.deployPolicy, steps('compliance').policySection.policy, '')]", "replicaCount": "[steps('image').destination.replicaCount]", "sourceImageType": "[steps('image').source.type]", - "spokelogAnalyticsWorkspaceResourceId": "[steps('compliance').logAnalyticsWorkspace]", + "spokelogAnalyticsWorkspaceResourceId": "[steps('compliance').logAnalyticsWorkspace.id]", "storageAccountResourceId": "[steps('basics').artifacts.storageSelector.id]", "subnetAddressPrefix": "[steps('networking').networking.subnetAddressCidrRange]", "tags": "[steps('tags').tags]", @@ -1576,4 +1576,4 @@ "subscriptionId": "[steps('basics').scope.subscription.id]" } } -} \ No newline at end of file +} diff --git a/src/bicep/add-ons/tier3/modules/storage.bicep b/src/bicep/add-ons/tier3/modules/storage.bicep index b78d37d41..8580e034d 100644 --- a/src/bicep/add-ons/tier3/modules/storage.bicep +++ b/src/bicep/add-ons/tier3/modules/storage.bicep @@ -6,11 +6,13 @@ Licensed under the MIT License. targetScope = 'subscription' param blobsPrivateDnsZoneResourceId string +param filesPrivateDnsZoneResourceId string param keyVaultUri string param logStorageSkuName string param location string param mlzTags object param network object +param queuesPrivateDnsZoneResourceId string param resourceGroupName string param serviceToken string param storageEncryptionKeyName string @@ -25,9 +27,11 @@ module storageAccount '../../../modules/storage-account.bicep' = { scope: resourceGroup(network.subscriptionId, resourceGroupName) params: { blobsPrivateDnsZoneResourceId: blobsPrivateDnsZoneResourceId + filesPrivateDnsZoneResourceId: filesPrivateDnsZoneResourceId keyVaultUri: keyVaultUri location: location mlzTags: mlzTags + queuesPrivateDnsZoneResourceId: queuesPrivateDnsZoneResourceId serviceToken: serviceToken skuName: logStorageSkuName storageEncryptionKeyName: storageEncryptionKeyName diff --git a/src/bicep/add-ons/tier3/solution.bicep b/src/bicep/add-ons/tier3/solution.bicep index 7e75f3fec..b30644948 100644 --- a/src/bicep/add-ons/tier3/solution.bicep +++ b/src/bicep/add-ons/tier3/solution.bicep @@ -237,27 +237,19 @@ module customerManagedKeys '../../modules/customer-managed-keys.bicep' = if (!(e module storage 'modules/storage.bicep' = if (!(empty(virtualNetworkAddressPrefix))) { name: 'deploy-storage-${workloadShortName}-${deploymentNameSuffix}' params: { - blobsPrivateDnsZoneResourceId: resourceId( - hubSubscriptionId, - hubResourceGroupName, - 'Microsoft.Network/privateDnsZones', - 'privatelink.blob.${environment().suffixes.storage}' - ) + blobsPrivateDnsZoneResourceId: resourceId(hubSubscriptionId, hubResourceGroupName, 'Microsoft.Network/privateDnsZones', 'privatelink.blob.${environment().suffixes.storage}') + filesPrivateDnsZoneResourceId: resourceId(hubSubscriptionId, hubResourceGroupName, 'Microsoft.Network/privateDnsZones', 'privatelink.file.${environment().suffixes.storage}') keyVaultUri: customerManagedKeys.outputs.keyVaultUri location: location logStorageSkuName: logStorageSkuName mlzTags: logic.outputs.mlzTags network: logic.outputs.tiers[0] + queuesPrivateDnsZoneResourceId: resourceId(hubSubscriptionId, hubResourceGroupName, 'Microsoft.Network/privateDnsZones', 'privatelink.queue.${environment().suffixes.storage}') resourceGroupName: rg.outputs.name serviceToken: logic.outputs.tokens.service storageEncryptionKeyName: customerManagedKeys.outputs.storageKeyName subnetResourceId: networking.outputs.subnets[0].id - tablesPrivateDnsZoneResourceId: resourceId( - hubSubscriptionId, - hubResourceGroupName, - 'Microsoft.Network/privateDnsZones', - 'privatelink.table.${environment().suffixes.storage}' - ) + tablesPrivateDnsZoneResourceId: resourceId(hubSubscriptionId, hubResourceGroupName, 'Microsoft.Network/privateDnsZones', 'privatelink.table.${environment().suffixes.storage}') tags: tags tier: logic.outputs.tiers[0] userAssignedIdentityResourceId: customerManagedKeys.outputs.userAssignedIdentityResourceId diff --git a/src/bicep/add-ons/tier3/solution.json b/src/bicep/add-ons/tier3/solution.json index 3dfe2b817..d155fc311 100644 --- a/src/bicep/add-ons/tier3/solution.json +++ b/src/bicep/add-ons/tier3/solution.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "5332647717093931418" + "version": "0.31.92.45157", + "templateHash": "14266827690996546891" } }, "parameters": { @@ -281,8 +281,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "11424932506966599764" + "version": "0.31.92.45157", + "templateHash": "15810721730485220824" } }, "parameters": { @@ -361,8 +361,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7616623366375139473" + "version": "0.31.92.45157", + "templateHash": "11125044402666498605" } }, "parameters": { @@ -388,6 +388,7 @@ "tokens": { "type": "object", "defaultValue": { + "purpose": "purpose_token", "resource": "resource_token", "service": "service_token" } @@ -813,6 +814,7 @@ "remoteApplicationGroups": "vdag", "resourceGroups": "rg", "routeTables": "rt", + "scalingPlans": "vdscaling", "storageAccounts": "st", "subnets": "snet", "userAssignedIdentities": "id", @@ -823,11 +825,11 @@ "locations": "[variables('$fxv#0')[environment().name]]", "locationAbbreviation": "[variables('locations')[parameters('location')].abbreviation]", "resourceAbbreviations": "[variables('$fxv#1')]", - "namingConvention": "[format('{0}-{1}{2}-{3}-{4}-{5}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), parameters('environmentAbbreviation'), variables('locationAbbreviation'))]", - "namingConvention_Service": "[format('{0}-{1}{2}-{3}-{4}-{5}-{6}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('tokens').service, parameters('networkName'), parameters('environmentAbbreviation'), variables('locationAbbreviation'))]", + "namingConvention": "[format('{0}-{1}{2}-{3}-{4}-{5}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), variables('locationAbbreviation'), parameters('environmentAbbreviation'))]", + "namingConvention_Service": "[format('{0}-{1}{2}-{3}-{4}-{5}-{6}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), parameters('tokens').service, variables('locationAbbreviation'), parameters('environmentAbbreviation'))]", "names": { "actionGroup": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').actionGroups)]", - "applicationGroup": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').applicationGroups)]", + "applicationGroup": "[replace(variables('namingConvention'), parameters('tokens').resource, format('{0}-desktop', variables('resourceAbbreviations').applicationGroups))]", "applicationInsights": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').applicationInsights)]", "appServicePlan": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').appServicePlans)]", "automationAccount": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').automationAccounts)]", @@ -881,10 +883,18 @@ "recoveryServicesVaultPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, variables('resourceAbbreviations').recoveryServicesVaults)]", "resourceGroup": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').resourceGroups)]", "routeTable": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').routeTables)]", + "scalingPlan": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').scalingPlans)]", + "scalingPlanDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').scalingPlans)]", "storageAccount": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').storageAccounts), parameters('networkName'), parameters('networkShortName'))]", "storageAccountDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", - "storageAccountNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", - "storageAccountPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", + "storageAccountBlobNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-blob', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountFileNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-file', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountQueueNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-queue', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountTableNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-table', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountBlobPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-blob', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountFilePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-file', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountQueuePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-queue', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountTablePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-table', variables('resourceAbbreviations').storageAccounts))]", "subnet": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').subnets)]", "userAssignedIdentity": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').userAssignedIdentities)]", "virtualMachine": "[replace(replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').virtualMachines), parameters('environmentAbbreviation'), first(parameters('environmentAbbreviation'))), parameters('networkName'), ''), '-', '')]", @@ -892,14 +902,14 @@ "virtualMachineNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').virtualMachines))]", "virtualNetwork": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').virtualNetworks)]", "virtualNetworkDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').virtualNetworks)]", - "workspaceFeed": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').workspaces), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobal": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').workspaces), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]" + "workspaceFeed": "[replace(replace(variables('namingConvention'), parameters('tokens').resource, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceFeedDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceFeedNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceFeedPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobal": "[replace(replace(variables('namingConvention'), parameters('tokens').resource, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobalDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobalNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobalPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]" } }, "resources": [], @@ -945,8 +955,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "4151572700986885014" + "version": "0.31.92.45157", + "templateHash": "17619606927129129347" } }, "parameters": { @@ -1085,8 +1095,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10109660736443081073" + "version": "0.31.92.45157", + "templateHash": "9371138343009436350" } }, "parameters": { @@ -1214,8 +1224,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2001402118470594055" + "version": "0.31.92.45157", + "templateHash": "12095518102496352105" } }, "parameters": { @@ -1350,8 +1360,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3535186238457701125" + "version": "0.31.92.45157", + "templateHash": "11818136489056939588" } }, "parameters": { @@ -1468,8 +1478,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "6722359274420487391" + "version": "0.31.92.45157", + "templateHash": "3681943409502537301" } }, "parameters": { @@ -1551,8 +1561,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "12652296496577802490" + "version": "0.31.92.45157", + "templateHash": "18425321023142226965" } }, "parameters": { @@ -1654,8 +1664,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17592952825859536181" + "version": "0.31.92.45157", + "templateHash": "15002544166394392504" } }, "parameters": { @@ -1731,8 +1741,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7007835755326231171" + "version": "0.31.92.45157", + "templateHash": "16817025486402215719" } }, "parameters": { @@ -1872,8 +1882,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3832300165614083813" + "version": "0.31.92.45157", + "templateHash": "10267893616110384815" } }, "parameters": { @@ -1925,8 +1935,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2015499370398293300" + "version": "0.31.92.45157", + "templateHash": "5574526676512163672" } }, "parameters": { @@ -1999,8 +2009,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "9502319494004310539" + "version": "0.31.92.45157", + "templateHash": "11553909803736438916" } }, "parameters": { @@ -2052,8 +2062,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2015499370398293300" + "version": "0.31.92.45157", + "templateHash": "5574526676512163672" } }, "parameters": { @@ -2152,8 +2162,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10999644342250296299" + "version": "0.31.92.45157", + "templateHash": "4304275711041823961" } }, "parameters": { @@ -2210,8 +2220,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2757774880390840506" + "version": "0.31.92.45157", + "templateHash": "16077950968688123011" } }, "parameters": { @@ -2313,8 +2323,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "11152278840466293351" + "version": "0.31.92.45157", + "templateHash": "12787329163785242553" } }, "parameters": { @@ -2402,8 +2412,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "1874197755006172394" + "version": "0.31.92.45157", + "templateHash": "11761568940379970751" } }, "parameters": { @@ -2660,8 +2670,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17613382135787640077" + "version": "0.31.92.45157", + "templateHash": "4207798980384159491" } }, "parameters": { @@ -2740,8 +2750,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8372265102618018066" + "version": "0.31.92.45157", + "templateHash": "1966035938992047983" } }, "parameters": { @@ -2835,8 +2845,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "6675708379514380442" + "version": "0.31.92.45157", + "templateHash": "7930493629995578222" } }, "parameters": { @@ -2941,6 +2951,9 @@ "blobsPrivateDnsZoneResourceId": { "value": "[resourceId(variables('hubSubscriptionId'), variables('hubResourceGroupName'), 'Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" }, + "filesPrivateDnsZoneResourceId": { + "value": "[resourceId(variables('hubSubscriptionId'), variables('hubResourceGroupName'), 'Microsoft.Network/privateDnsZones', format('privatelink.file.{0}', environment().suffixes.storage))]" + }, "keyVaultUri": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-cmk-{0}-{1}', parameters('workloadShortName'), parameters('deploymentNameSuffix'))), '2022-09-01').outputs.keyVaultUri.value]" }, @@ -2956,6 +2969,9 @@ "network": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-logic-{0}-{1}', parameters('workloadShortName'), parameters('deploymentNameSuffix'))), '2022-09-01').outputs.tiers.value[0]]" }, + "queuesPrivateDnsZoneResourceId": { + "value": "[resourceId(variables('hubSubscriptionId'), variables('hubResourceGroupName'), 'Microsoft.Network/privateDnsZones', format('privatelink.queue.{0}', environment().suffixes.storage))]" + }, "resourceGroupName": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-rg-{0}-{1}', parameters('workloadShortName'), parameters('deploymentNameSuffix'))), '2022-09-01').outputs.name.value]" }, @@ -2987,14 +3003,17 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17995428208905585384" + "version": "0.31.92.45157", + "templateHash": "17358288344184718166" } }, "parameters": { "blobsPrivateDnsZoneResourceId": { "type": "string" }, + "filesPrivateDnsZoneResourceId": { + "type": "string" + }, "keyVaultUri": { "type": "string" }, @@ -3010,6 +3029,9 @@ "network": { "type": "object" }, + "queuesPrivateDnsZoneResourceId": { + "type": "string" + }, "resourceGroupName": { "type": "string" }, @@ -3051,6 +3073,9 @@ "blobsPrivateDnsZoneResourceId": { "value": "[parameters('blobsPrivateDnsZoneResourceId')]" }, + "filesPrivateDnsZoneResourceId": { + "value": "[parameters('filesPrivateDnsZoneResourceId')]" + }, "keyVaultUri": { "value": "[parameters('keyVaultUri')]" }, @@ -3060,6 +3085,9 @@ "mlzTags": { "value": "[parameters('mlzTags')]" }, + "queuesPrivateDnsZoneResourceId": { + "value": "[parameters('queuesPrivateDnsZoneResourceId')]" + }, "serviceToken": { "value": "[parameters('serviceToken')]" }, @@ -3091,14 +3119,17 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10231061264498799420" + "version": "0.31.92.45157", + "templateHash": "1625826941635729014" } }, "parameters": { "blobsPrivateDnsZoneResourceId": { "type": "string" }, + "filesPrivateDnsZoneResourceId": { + "type": "string" + }, "keyVaultUri": { "type": "string" }, @@ -3108,6 +3139,9 @@ "mlzTags": { "type": "object" }, + "queuesPrivateDnsZoneResourceId": { + "type": "string" + }, "serviceToken": { "type": "string" }, @@ -3134,9 +3168,27 @@ } }, "variables": { - "zones": [ - "[parameters('blobsPrivateDnsZoneResourceId')]", - "[parameters('tablesPrivateDnsZoneResourceId')]" + "subResources": [ + { + "id": "[parameters('blobsPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountBlobNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountBlobPrivateEndpoint]" + }, + { + "id": "[parameters('filesPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountFileNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountFilePrivateEndpoint]" + }, + { + "id": "[parameters('queuesPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountQueueNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountQueuePrivateEndpoint]" + }, + { + "id": "[parameters('tablesPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountTableNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountTablePrivateEndpoint]" + } ] }, "resources": [ @@ -3207,22 +3259,22 @@ { "copy": { "name": "privateEndpoints", - "count": "[length(variables('zones'))]" + "count": "[length(variables('subResources'))]" }, "type": "Microsoft.Network/privateEndpoints", "apiVersion": "2023-04-01", - "name": "[replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]))]", + "name": "[variables('subResources')[copyIndex()].pe]", "location": "[parameters('location')]", "tags": "[union(if(contains(parameters('tags'), 'Microsoft.Network/privateEndpoints'), parameters('tags')['Microsoft.Network/privateEndpoints'], createObject()), parameters('mlzTags'))]", "properties": { - "customNetworkInterfaceName": "[replace(parameters('tier').namingConvention.storageAccountNetworkInterface, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]))]", + "customNetworkInterfaceName": "[variables('subResources')[copyIndex()].nic]", "privateLinkServiceConnections": [ { - "name": "[replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]))]", + "name": "[variables('subResources')[copyIndex()].pe]", "properties": { "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]", "groupIds": [ - "[split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]]" + "[split(split(variables('subResources')[copyIndex()].id, '/')[8], '.')[1]]" ] } } @@ -3238,23 +3290,23 @@ { "copy": { "name": "privateDnsZoneGroups", - "count": "[length(variables('zones'))]" + "count": "[length(variables('subResources'))]" }, "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", "apiVersion": "2021-08-01", - "name": "[format('{0}/{1}', replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1])), uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]", + "name": "[format('{0}/{1}', variables('subResources')[copyIndex()].pe, uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]", "properties": { "privateDnsZoneConfigs": [ { "name": "ipconfig1", "properties": { - "privateDnsZoneId": "[variables('zones')[copyIndex()]]" + "privateDnsZoneId": "[variables('subResources')[copyIndex()].id]" } } ] }, "dependsOn": [ - "[resourceId('Microsoft.Network/privateEndpoints', replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1])))]", + "[resourceId('Microsoft.Network/privateEndpoints', variables('subResources')[copyIndex()].pe)]", "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]" ] } @@ -3348,8 +3400,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8480968961555779332" + "version": "0.31.92.45157", + "templateHash": "4345251511078445463" } }, "parameters": { @@ -3423,8 +3475,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8389358797157027271" + "version": "0.31.92.45157", + "templateHash": "4687229436121899773" } }, "parameters": { @@ -3513,8 +3565,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3320497816733398371" + "version": "0.31.92.45157", + "templateHash": "1721966359516622278" } }, "parameters": { @@ -3587,8 +3639,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "9851134383266019486" + "version": "0.31.92.45157", + "templateHash": "2073766618455932098" } }, "parameters": { @@ -3665,8 +3717,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7358671180047253284" + "version": "0.31.92.45157", + "templateHash": "9546260853018527046" } }, "parameters": { @@ -3756,8 +3808,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7046499602359448652" + "version": "0.31.92.45157", + "templateHash": "15014526386353172066" } }, "parameters": { @@ -3813,8 +3865,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "16614224552992880134" + "version": "0.31.92.45157", + "templateHash": "8545903679924437739" } }, "parameters": { @@ -3989,8 +4041,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8372265102618018066" + "version": "0.31.92.45157", + "templateHash": "1966035938992047983" } }, "parameters": { @@ -4074,8 +4126,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3588347827815061814" + "version": "0.31.92.45157", + "templateHash": "17047820191891552534" } }, "parameters": { diff --git a/src/bicep/add-ons/virtual-network-gateway/README.md b/src/bicep/add-ons/virtual-network-gateway/README.md new file mode 100644 index 000000000..bb8105ce6 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/README.md @@ -0,0 +1,199 @@ +# VPN Gateway MLZ Add-On + +## Introduction + +This document provides details on a Bicep script that deploys a VPN Gateway, Local Network Gateway, VPN connection, and related resources in Azure, integrating into an existing MLZ network deployment. It includes descriptions of all parameters, required parameters, instructions on building and deploying the ARM template, and steps to create a template specification from the Bicep script. + +The deployment is intended to provide an on-prem VPN gateway to connect into the MLZ network through the Hub vNet and route to all spokes. A route table is created for the vpn gateway and attached to the GatewaySubnet in the Hub network, and firewall rules added to allow connectivity. + +This allows for the firewall to serve as a protection between the on-prem internal network and the Azure spoke networks. + +Additionally, it covers the modules used within the script and their roles in the deployment process. + +--- + +## Parameters + +### 1. **vgwName** (string) - Required + +- **Description:** The name of the VPN Gateway. Provided as an input parameter to the solution when deployed. + +### 2. **vgwLocation** (string) - Optional (default: location of the resource group) + +- **Description:** The Azure region for deploying the VPN Gateway. If not provided, it defaults to the location of the resource group. Provided as an input parameter to the solution when deployed. + +### 3. **vgwPublicIpAddressNames** (array) - Required + +- **Description:** The names of the public IP addresses associated with the VPN Gateway. Requires two for redundancy. Provided as an input parameter to the solution when deployed. + +### 4. **vgwSku** (string) - Optional (default: `'VpnGw2'`) + +- **Description:** The SKU (size) of the VPN Gateway. Allowed values: `VpnGw2`, `VpnGw3`, `VpnGw4`, `VpnGw5`. The default can be changed in the "solution.bicep" file. + +### 5. **localNetworkGatewayName** (string) - Required + +- **Description:** The name of the Local Network Gateway. Provided as an input parameter to the solution when deployed. + +### 6. **localGatewayIpAddress** (string) - Required + +- **Description:** The IP address of the Local Network Gateway. This must be a public IP address or a reachable IP from the Azure environment. Provided as an input parameter to the solution when deployed. + +### 7. **allowedAzureAddressPrefixes** (array) - Required + +- **Description:** A list of address prefixes of the peered spoke networks that will be allowed to access the networks through the VPN gateway. This is used in an Azure firewall rule. Provided as an input parameter to the solution when deployed. + +### 8. **localAddressPrefixes** (array) - Required + +- **Description:** A list of address prefixes of the local network routable through the VPN Gateway. This controls what networks can be accessed from Azure through the VPN Gateway. This is also used in an Azure firewall rule. Provided as an input parameter to the solution when deployed. + +### 9. **useSharedKey** (bool) - Required + +- **Description:** Indicates whether to use a shared key or a Key Vault certificate URI for the VPN connection. If false, a URL to a pre-existing keyvault stored certificate must be used instead. Provided as an input parameter to the solution when deployed. + +### 10. **sharedKey** (string) - Required if `useSharedKey = true` + +- **Description:** The shared key for the VPN connection. This parameter is secured. A "true" value uses shared key which is provided in the portal or command prompt at deployment. A "false" value requires that a keyVaultCertificateUri is provided. Remove this from the parameters file before deployment to ensure the deployment will prompt for the value to avoid storing the secret in the file. + +### 11. **keyVaultCertificateUri** (string) - Optional (default: `''`) + +- **Description:** The URI of the Key Vault certificate for the VPN connection. Only used if `useSharedKey = false`. Must be a valid URI starting with `https://` and containing `/secrets/`. Provided as an input parameter to the solution when deployed. + +### 12. **hubVirtualNetworkResourceId** (string) - Required + +- **Description:** The resource ID of the hub virtual network. Can be found on the "Properties" blade on the vNet in the Azure portal. Provided as an input parameter to the solution when deployed. + +### 13. **vnetResourceIdList** (array) - Required + +- **Description:** A list of peered virtual networks that will use the VPN Gateway. The peerings will be updated to allow gateway transit and use. Can be found on the "Properties" blade on the vNet in the Azure portal. Provided as an input parameter to the solution when deployed. + +### 14. **azureFirewallName** (string) - Required + +- **Description:** The name of the Azure firewall in the hub network used to control all traffic through the VPN gateway and all spoke networks. Provided as an input parameter to the solution when deployed. + +### 14. **routeTableName** (string) - Required + +- **Description:** The name of the VPN Gateway route table that is used to control the gateway subnet routing overrides necessary to push all traffic through the Azure firewall. Provided as an input parameter to the solution when deployed. + +--- + +## Modules Used in the Script + +This Bicep script calls several external modules to deploy resources efficiently and modularly. Here's an overview of each module and what it does: + +### 1. **VPN Gateway Module** + +- **File:** `modules/vpn-gateway.bicep` +- **Description:** This module deploys the Virtual Network Gateway (VPN Gateway) in a specified resource group. The VPN Gateway enables secure cross-premises connectivity. +- **Parameters:** + - `vgwName`: The name of the VPN Gateway. Provided as an input parameter to the solution when deployed. + - `vgwLocation`: The location where the VPN Gateway will be deployed. Provided as an input parameter to the solution when deployed. + - `publicIpAddressNames`: The names of the public IP addresses associated with the VPN Gateway. Provided as an input parameter to the solution when deployed. + - `vgwsku`: The SKU of the VPN to be deployed. Provided as an input parameter to the solution when deployed. + - `vnetName`: The name of the hub virtual network to which the VPN Gateway will be connected. Derived from the hub virtual network resource id provided as an input parameter when deployed. + +### 2. **Local Network Gateway Module** + +- **File:** `modules/local-network-gateway.bicep` +- **Description:** This module deploys the Local Network Gateway, which defines the on-premises network's configuration and connectivity to Azure. It includes the on-premises gateway's public IP address and the network address ranges to route through the VPN connection. +- **Parameters:** + - `vgwlocation`: The location of the Local Network Gateway. Provided as an input parameter to the solution when deployed. + - `localNetworkGatewayName`: The name of the Local Network Gateway. Provided as an input parameter to the solution when deployed. + - `gatewayIpAddress`: The public IP address of the Local Network Gateway. Provided as an input parameter to the solution when deployed. + - `addressPrefixes`: The local address prefixes (network ranges) of the on-premises network. Provided as an input parameter to the solution when deployed. + +### 3. **VPN Connection Module** + +The VPN connection module contains these most commonly used IPSEC configuration settings: +`` + saLifeTimeSeconds: 3600 + saDataSizeKilobytes: 102400000 + ipsecEncryption: 'AES256' + ipsecIntegrity: 'SHA256' + ikeEncryption: 'AES256' + ikeIntegrity: 'SHA256' + dhGroup: 'DHGroup2' + pfsGroup: 'PFS2' +`` +Change these in the module file directly to modify connection settings for deployment. + +- **File:** `modules/vpn-connection.bicep` +- **Description:** This module creates the VPN connection between the VPN Gateway in Azure and the Local Network Gateway (on-premises network). It can use either a shared key or a Key Vault certificate for secure authentication. +- **Parameters:** + - `vpnConnectionName`: The name of the VPN connection. Provided as an input parameter to the solution when deployed. + - `vgwlocation`: The location of the VPN Gateway. Provided as an input parameter to the solution when deployed. + - `vpnGatewayName`: The name of the VPN Gateway. Provided as an input parameter to the solution when deployed. + - `vpnGatewayResourceGroupName`: The resource group where the VPN Gateway is deployed. Gateway is placed in the hub virtual network resource group, the name is extracted from the hub virtual network resource group id provided in the parameters when deployed. + - `sharedKey`: The shared key for the VPN connection (if using shared key authentication). Provided as an input parameter to the solution when deployed. Ensure the shared key and value are not provided in the parameters file before deployment to ensure prompting for the value at deployment time. + - `keyVaultCertificateUri`: The URI of the Key Vault certificate (if using certificate-based authentication). Provided as an input parameter to the solution when deployed, if shared key is not used. + - `localNetworkGatewayName`: The name of the Local Network Gateway. Provided as an input parameter to the solution when deployed. + +### 4. **Retrieve Existing Module** + +- **File:** `modules/retrieve-existing.bicep` +- **Description:** This module retrieves the list of virtual network peerings associated with a virtual network. The peerings allow networks to communicate securely with each other within the same Azure region or across regions. This module is also used to retrieve information from other existing resources depending on the parameters used. +- **Parameters:** + - `vnetResourceId`: The resource ID of the virtual network for which peerings are being retrieved. Provided as an input parameter to the solution when deployed. + +### 5. **VNet Peerings Module** + +- **File:** `modules/vnet-peerings.bicep` +- **Description:** After retrieving the peerings for a virtual network, this module updates the peerings to reflect the new VPN Gateway configuration. This allows peered networks to utilize the VPN Gateway for cross-premises connectivity. +- **Parameters:** + - `vnetResourceId`: The resource ID of the virtual network. Provided as an input parameter to the solution when deployed. + - `peeringsList`: The list of virtual network peerings to be updated. Returned values from the retrieve-existing.bicep module. + +### 6. **Route Table Module** + +- **File:** `modules/route-table.bicep` +- **Description:** This module creates the route table for the VPN gateway. +- **Parameters:** + - `routeTableName`: The route table name. Provided as an input parameter to the solution when deployed. + +### 7. **Route Definition** + +- **File:** `modules/route-definition.bicep` +- **Description:** This module builds the route construct to be used when adding the route, as multiple routes need to be added. Virtual appliance is hard coded as the next hop type. +- **Parameters:** + - `firewallIpAddress`: The IP address of the firewall, used as the next hop IP address. Returned value from the retrieve-existing.bicep module. + - `addressPrefixes`: The address prefixes used in the route being built. Provided as an input parameter to the solution when deployed. + +### 8. **Routes Module** + +- **File:** `modules/routes.bicep` +- **Description:** This module creates the routes in a route table. +- **Parameters:** + - `routeTableName`: The route table name. Provided as an input parameter to the solution when deployed. + - `routeName`: The name of the route. Value defaulted in the solution.bicep file, the value is: "route-#" with an increment number depending on the number of routes being added. + - `addressSpace`: The CIDR address prefix being routed. Provided as an input parameter to the solution when deployed. + - `nextHopType`: The type of next hop, defaulted to appliance in the solution.bicep file. + - `nextHopIpAddress`: The IP address of the next hop. In this implementation, the firewall IP address. Provided as an input parameter to the solution when deployed. + +### 8. **Firewall Rules Module** + +- **File:** `modules/firewall-rules.bicep ` +- **Description:** This module creates the firewall rules to allow spoke and vpn address prefixes access to eachother. +- **Parameters:** + - `allowVnetAddressSpaces`: The CIDR address prefixes of peered Azure spoke vnets. Provided as an input parameter to the solution when deployed. + - `onPremAddressSpaces`: The CIDR address prefixes of the onprem networks to be allowed. Provided as an input parameter to the solution when deployed. + - `firewallPolicyId`: The firewall policy attached to the hub firewall. The solution will dynamically retrieve this value. + - `priorityValue`: The priority value for the firewall rule. The solution defines this at a value of 300 in the main bicep named solution.bicep. + + The rule is hardcoded to allow any protocol to any address in the rule. Customize firewall-rules.bicep to change behavior. + +## Removal of VPN Gateway + +1. Delete the VPN Connection in the MLZ Hub resource group. +2. Delete the Local Network Gateway in the MLZ Hub resource group. +3. Delete the VPN Gateway in the MLZ Hub resource group. +4. Navigate to the Hub vNet, and go to Peerings: + a. Open each peering in the list. + b. Uncheck "Allow gateway or route server in '' to forward traffic to ''. + c. Click "save". +5. Navigate to each spoke vNet represented in the peerings list. + a. Open the peering to the Hub network. + b. Uncheck "Enable '' to use '' remote gateway. + c. Click "save". +6. Navigate to each spoke network resource group. + a. Open the Route table in the group. + b. Choose "Routes". + c. Delete the VPN routes in the list. diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/associate-route-table.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/associate-route-table.bicep new file mode 100644 index 000000000..abee9df67 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/associate-route-table.bicep @@ -0,0 +1,29 @@ + +@description('virtual network resource ID that holds the subnet') +param vnetResourceId string + +@description('route table resource ID to associate with the subnet') +param routeTableResourceId string + +@description('name of the subnet to associate with the route table') +param subnetName string + +@description('address prefix of the gateway subnet') +param subnetAddressPrefix string + +// Reference the existing Virtual Network +resource existingVnet 'Microsoft.Network/virtualNetworks@2023-11-01' existing = { + name: last(split(vnetResourceId, '/')) +} + +// Update the GatewaySubnet to associate the existing Route Table +resource gatewaySubnet 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' = { + parent: existingVnet + name: subnetName + properties: { + addressPrefix: subnetAddressPrefix + routeTable: { + id: resourceId('Microsoft.Network/routeTables', last(split(routeTableResourceId, '/'))) + } + } +} diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/firewall-rules.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/firewall-rules.bicep new file mode 100644 index 000000000..3785a1989 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/firewall-rules.bicep @@ -0,0 +1,70 @@ +@description('The list of virtual network resource IDs to be used as the source IP groups') +param allowVnetAddressSpaces array + +@description('Address prefixes of the on-premises network') +param onPremAddressSpaces array + +@description('Name of the firewall policy') +param firewallPolicyId string + +@description('The priority value for the rule collection') +@minValue(300) +@maxValue(65000) +param priorityValue int + + +// Define the firewall policy reference +resource firewallPolicy 'Microsoft.Network/firewallPolicies@2023-04-01' existing = { + name: last(split(firewallPolicyId, '/')) +} + +// First rule set: Source = allowedIpGroup, Destination = onPremIpGroup +var outboundRules = { + name: 'AllowAzureToOnPremRule' // Unique rule name using index + ruleType: 'NetworkRule' + sourceAddresses: allowVnetAddressSpaces + destinationAddresses: onPremAddressSpaces + destinationPorts: [ + '*' // Modify this as needed + ] + ipProtocols: [ + 'Any' // Modify this as needed + ] +} + +// Second rule set (reverse): Source = onPremIpGroup, Destination = allowedIpGroup +var inboundRules = { + name: 'AllowOnPremToAzureRule' // Unique rule name using index + ruleType: 'NetworkRule' + sourceAddresses: onPremAddressSpaces + destinationAddresses: allowVnetAddressSpaces + destinationPorts: [ + '*' // Modify this as needed + ] + ipProtocols: [ + 'Any' // Modify this as needed + ] +} + +// Define the rule collection group, referencing existing IP groups for source and destination +resource allowVgwCollection 'Microsoft.Network/firewallPolicies/ruleCollectionGroups@2023-04-01' = { + name: 'VgwNetworkRuleCollectionGroup' + parent: firewallPolicy + properties: { + priority: priorityValue + ruleCollections: [ + { + name: 'AllowVgw' + priority: priorityValue + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + action: { + type: 'Allow' + } + rules: [ + outboundRules + inboundRules + ] + } + ] + } +} diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/local-network-gateway.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/local-network-gateway.bicep new file mode 100644 index 000000000..37c8d33cb --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/local-network-gateway.bicep @@ -0,0 +1,17 @@ +param vgwlocation string = resourceGroup().location +param localNetworkGatewayName string +param gatewayIpAddress string +param addressPrefixes array + + +// Local Network Gateway configuration +resource localNetworkGateway 'Microsoft.Network/localNetworkGateways@2023-02-01' = { + name: localNetworkGatewayName + location: vgwlocation + properties: { + gatewayIpAddress: gatewayIpAddress + localNetworkAddressSpace: { + addressPrefixes: addressPrefixes + } + } +} diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/retrieve-existing.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/retrieve-existing.bicep new file mode 100644 index 000000000..7dccaff7e --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/retrieve-existing.bicep @@ -0,0 +1,58 @@ +@description('Name of the Azure Firewall (optional)') +param azureFirewallName string = '' + +@description('Name of the subnet (optional)') +param subnetName string = '' + +@description('The resource ID of the existing spoke virtual network (optional)') +param vnetResourceId string = '' + +@description('The name of the route table associated with the hub virtual network (optional)') +param routeTableName string = '' + +resource vnetRouteTable 'Microsoft.Network/routeTables@2020-11-01' existing = if (!empty(routeTableName) && !empty(vnetResourceId)) { + scope: resourceGroup() + name: routeTableName +} + +// Retrieve internal address of the firewall, conditionally +resource azureFirewall 'Microsoft.Network/azureFirewalls@2020-11-01' existing = if (!empty(azureFirewallName) && !empty(vnetResourceId)) { + scope: resourceGroup(split(vnetResourceId, '/')[2], split(vnetResourceId, '/')[4]) + name: azureFirewallName +} + +// Reference the existing Virtual Network using its resource ID, conditionally +resource vnetInfo 'Microsoft.Network/virtualNetworks@2020-11-01' existing = if (!empty(vnetResourceId)) { + scope: resourceGroup() + name: last(split(vnetResourceId, '/')) // Extract the VNet name from the resource ID +} + +// Loop through the subnets to find the specified subnet, conditionally +resource subnet 'Microsoft.Network/virtualNetworks/subnets@2020-11-01' existing = if (!empty(subnetName) && !empty(vnetResourceId)) { + parent: vnetInfo + name: subnetName +} + +// Output the route table ID of the hub virtual network, if the route table name is provided +output routeTableId string = !empty(routeTableName) ? vnetRouteTable.id : 'N/A' + +// Output the internal IP address of the firewall, if firewall parameters are provided +output firewallPrivateIp string = (!empty(azureFirewallName) && !empty(vnetResourceId)) ? azureFirewall.properties.ipConfigurations[0].properties.privateIPAddress : 'N/A' + +// Output the firewall policy id attached to the firewall +output firewallPolicyId string = !empty(azureFirewallName) ? azureFirewall.properties.firewallPolicy.id : 'N/A' + +// Output the address prefix of the GatewaySubnet, if the parameters are provided +output subnetAddressPrefix string = (!empty(subnetName) && !empty(vnetResourceId)) ? subnet.properties.addressPrefix : 'N/A' + +// Output the address space of the VNet, if the VNet resource ID is provided +output vnetAddressSpace array = !empty(vnetResourceId) ? vnetInfo.properties.addressSpace.addressPrefixes : [] + +// Output the list of peerings from the VNet, if the VNet resource ID is provided +output peeringsData object = !empty(vnetResourceId) ? { + vnetResourceId: vnetResourceId + peeringsList: vnetInfo.properties.virtualNetworkPeerings +} : { + vnetResourceId: 'N/A' + peeringsList: [] +} diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/route-definition.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/route-definition.bicep new file mode 100644 index 000000000..a0cdc6e68 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/route-definition.bicep @@ -0,0 +1,18 @@ +// Assume hubVnetAddressSpace is a parameter or variable from another part of your script +@description('Address space prefixes of the virtual network') +param addressPrefixes array + +@description('Private IP address of the Azure Firewall') +param firewallPrivateIp string + +// Create a variable with the route definitions +output routes array = [ + for i in range(0, length(addressPrefixes)): { + name: 'mlzToOnPrem-${i}' // Ensure unique route names + addressPrefix: addressPrefixes[i] + nextHopType: 'VirtualAppliance' + nextHopIpAddress: firewallPrivateIp + } +] + + diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/route-table.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/route-table.bicep new file mode 100644 index 000000000..38112a970 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/route-table.bicep @@ -0,0 +1,12 @@ +@description('Name of the route table to create') +param routeTableName string + +resource routeTable 'Microsoft.Network/routeTables@2021-02-01' = { + name: routeTableName + location: resourceGroup().location + properties: { + disableBgpRoutePropagation: true + } +} + +output routeTableId string = routeTable.id diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/routes.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/routes.bicep new file mode 100644 index 000000000..6da4f8f6d --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/routes.bicep @@ -0,0 +1,30 @@ +@description('Name of the route table to create') +param routeTableName string + +@description('Name of the route') +param routeName string + +@description('CIDR prefixes for the route') +param addressSpace array + +@description('The next hop type for the route') +param nextHopType string + +@description('The next hop IP address for the route') +param nextHopIpAddress string + +resource routeTable 'Microsoft.Network/routeTables@2021-02-01' existing = { + name: routeTableName + scope: resourceGroup() +} + +// Loop over the address spaces and create routes +resource routes 'Microsoft.Network/routeTables/routes@2023-04-01' = [for (cidr, i) in addressSpace: { + parent: routeTable + name: '${routeName}-${i}' + properties: { + addressPrefix: cidr + nextHopType: nextHopType + nextHopIpAddress: nextHopIpAddress != '' ? nextHopIpAddress : null + } +}] diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/vnet-peerings.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/vnet-peerings.bicep new file mode 100644 index 000000000..a094975c0 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/vnet-peerings.bicep @@ -0,0 +1,35 @@ +@description('The list of peerings to update') +param peeringsList array + +@description('The resource ID of the existing virtual network') +param vnetResourceId string + +// Extract the virtual network name from the resource ID +var vnetName = last(split(vnetResourceId, '/')) + +// Generate the list of updated peerings +var updatedPeerings = [for peering in peeringsList: { + name: last(split(peering.id, '/')) // Extract the peering name from the ID + properties: { + allowGatewayTransit: contains(vnetName, '-hub-') ? true : peering.properties.allowGatewayTransit + useRemoteGateways: !contains(vnetName, '-hub-') ? true : peering.properties.useRemoteGateways + // allowGatewayTransit: contains(split(peering.id, '/')[8], '-hub-') ? true : peering.properties.allowGatewayTransit + // useRemoteGateways: !contains(split(peering.id, '/')[8], '-hub-') ? true : peering.properties.useRemoteGateways + allowForwardedTraffic: peering.properties.allowForwardedTraffic == null ? true : peering.properties.allowForwardedTraffic // Preserve existing value or set to true + remoteVirtualNetwork: peering.properties.remoteVirtualNetwork + } +}] + +// Define the parent virtual network resource +resource vnet 'Microsoft.Network/virtualNetworks@2022-07-01' existing = { + name: vnetName +} + +// Create or update the peerings within the virtual network context +resource peeringUpdates 'Microsoft.Network/virtualNetworks/virtualNetworkPeerings@2021-02-01' = [for (peering, i) in updatedPeerings: { + parent: vnet + name: peering.name + properties: peering.properties +}] + + diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/vpn-connection.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/vpn-connection.bicep new file mode 100644 index 000000000..64179e6bc --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/vpn-connection.bicep @@ -0,0 +1,52 @@ +param vpnConnectionName string +param vgwlocation string +param vpnGatewayName string +param vpnGatewayResourceGroupName string +param sharedKey string +param keyVaultCertificateUri string +param localNetworkGatewayName string + +// Determine if either sharedKey or keyVaultCertificateUri is provided +var useSharedKey = !empty(sharedKey) +var useKeyVaultCertificate = !empty(keyVaultCertificateUri) + +// Conditional validation through variables +var errorMsg = (useSharedKey && useKeyVaultCertificate) ? 'Cannot provide both sharedKey and keyVaultCertificateUri' : '' +var connectionSharedKey = useSharedKey ? sharedKey : null +var connectionIpsecPolicies = useKeyVaultCertificate ? [ + { + saLifeTimeSeconds: 3600 + saDataSizeKilobytes: 102400000 + ipsecEncryption: 'AES256' + ipsecIntegrity: 'SHA256' + ikeEncryption: 'AES256' + ikeIntegrity: 'SHA256' + dhGroup: 'DHGroup2' + pfsGroup: 'PFS2' + } +] : null + +// Deploy the VPN connection only if the conditions are met +resource vpnConnection 'Microsoft.Network/connections@2023-02-01' = if (empty(errorMsg)) { + name: vpnConnectionName + location: vgwlocation + properties: { + virtualNetworkGateway1: { + id: resourceId(vpnGatewayResourceGroupName, 'Microsoft.Network/virtualNetworkGateways', vpnGatewayName) + } + localNetworkGateway2: { + id: resourceId(vpnGatewayResourceGroupName, 'Microsoft.Network/localNetworkGateways', localNetworkGatewayName) + } + connectionType: 'IPsec' + routingWeight: 10 + + sharedKey: connectionSharedKey + + // Use ipsecPolicies if Key Vault certificate URI is provided + ipsecPolicies: connectionIpsecPolicies + + // Additional properties as required + enableBgp: false + usePolicyBasedTrafficSelectors: false + } +} diff --git a/src/bicep/add-ons/virtual-network-gateway/modules/vpn-gateway.bicep b/src/bicep/add-ons/virtual-network-gateway/modules/vpn-gateway.bicep new file mode 100644 index 000000000..661f9ea3e --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/modules/vpn-gateway.bicep @@ -0,0 +1,81 @@ +param vgwname string +param vgwlocation string = resourceGroup().location +param publicIpAddressNames array +param vgwsku string +param vnetName string + +// Existing Virtual Network and Subnet +resource vnet 'Microsoft.Network/virtualNetworks@2023-02-01' existing = { + name: vnetName +} + +// Reference the existing subnet within the specified Virtual Network +resource gatewaySubnet 'Microsoft.Network/virtualNetworks/subnets@2023-02-01' existing = { + parent: vnet + name: 'GatewaySubnet' +} + +var gatewaySubnetId = gatewaySubnet.id + +// Public IP Addresses +resource publicIpAddresses 'Microsoft.Network/publicIPAddresses@2023-02-01' = [for (pipname, index) in publicIpAddressNames: { + name: pipname + location: vgwlocation + sku: { + name: 'Standard' + } + properties: { + publicIPAllocationMethod: 'Static' + } +}] + +var firstPublicIpAddressId = publicIpAddresses[0].id +var secondPublicIpAddressId = publicIpAddresses[1].id + +// VPN Gateway +resource vpnGateway 'Microsoft.Network/virtualNetworkGateways@2023-02-01' = { + name: vgwname + location: vgwlocation + properties: { + gatewayType: 'Vpn' + ipConfigurations: [ + { + name: 'first' + properties: { + privateIPAllocationMethod: 'Dynamic' + subnet: { + id: gatewaySubnetId + } + publicIPAddress: { + id: firstPublicIpAddressId + } + } + } + { + name: 'second' + properties: { + privateIPAllocationMethod: 'Dynamic' + publicIPAddress: { + id: secondPublicIpAddressId + } + subnet: { + id: gatewaySubnetId + } + } + } + ] + activeActive: true + vpnType: 'RouteBased' + vpnGatewayGeneration: 'Generation2' + enableBgp: false + enablePrivateIpAddress: false + sku: { + name: vgwsku + tier: vgwsku + } + } +} + + + + diff --git a/src/bicep/add-ons/virtual-network-gateway/sample.solution.parameters.json b/src/bicep/add-ons/virtual-network-gateway/sample.solution.parameters.json new file mode 100644 index 000000000..8364d4a01 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/sample.solution.parameters.json @@ -0,0 +1,60 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "vgwName": { + "value": "" + }, + "vgwLocation": { + "value": "usgovvirginia" + }, + "vgwPublicIpAddressNames": { + "value": [ + "", + "" + }, + "localGatewayIpAddress": { + "value": "" + }, + "allowedAzureAddressPrefixes": { + "value": [ + "10.0.130.0/24", + "10.0.131.0/24", + "10.0.132.0/24", + "10.0.128.0/23" + ] + }, + "localAddressPrefixes": { + "value": [ + "192.168.0.0/24", + "192.168.1.0/24" + ] + }, + "useSharedKey": { + "value": true + }, + "hubVirtualNetworkResourceId": { + "value": "/subscriptions//resourceGroups//providers/Microsoft.Network/virtualNetworks/" + }, + "vnetResourceIdList": { + "value": [ + "/subscriptions//resourceGroups//providers/Microsoft.Network/virtualNetworks//resourceGroups//providers/Microsoft.Network/virtualNetworks//resourceGroups//providers/Microsoft.Network/virtualNetworks/" + }, + "vgwRouteTableName": { + "value": "" + }, + "hubVnetRouteTableName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/src/bicep/add-ons/virtual-network-gateway/solution.bicep b/src/bicep/add-ons/virtual-network-gateway/solution.bicep new file mode 100644 index 000000000..f356e0441 --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/solution.bicep @@ -0,0 +1,276 @@ +targetScope = 'subscription' + +@description('The name of the VPN Gateway.') +param vgwName string + +@description('The Azure region location of the VPN Gateway.') +param vgwLocation string + +@description('The names of the public IP addresses to use for the VPN Gateway.') +param vgwPublicIpAddressNames array + +@description('The SKU of the VPN Gateway.') +@allowed(['VpnGw2', 'VpnGw3', 'VpnGw4', 'VpnGw5']) +param vgwSku string + +@description('Local Network Gateway Name') +param localNetworkGatewayName string + +@description('IP Address of the Local Network Gateway, must be a public IP address or be able to be connected to from MLZ network') +param localGatewayIpAddress string + +@description('Azure address prefixes allowed to communicate to VPN Gateway to on-premises network') +param allowedAzureAddressPrefixes array + +@description('Address prefixes of the Local Network which will be routable through the VPN Gateway') +param localAddressPrefixes array + +@description('Choose whether to use a shared key or Key Vault certificate URI for the VPN connection.') +param useSharedKey bool + +@description('The shared key to use for the VPN connection. If using the shared key, this must be provided.') +@secure() +param sharedKey string + +@description('The URI of the Key Vault certificate to use for the VPN connection. If using a Key Vault certificate, this must be a valid URI.') +param keyVaultCertificateUri string = '' + +@description('A suffix to use for naming deployments uniquely.') +param deploymentNameSuffix string = utcNow() + +@description('The resource ID of the hub virtual network.') +param hubVirtualNetworkResourceId string +// Extracting the resource group name and virtual network name from the hub virtual network resource ID +var hubResourceGroupName = split(hubVirtualNetworkResourceId, '/')[4] +var hubVnetName = split(hubVirtualNetworkResourceId, '/')[8] + +@description('List of peered networks that should use the VPN Gateway once configured.') +param vnetResourceIdList array + +@description('The name of the Azure Firewall to retrieve the internal IP address from.') +param azureFirewallResourceId string +var azureFirewallName = split(azureFirewallResourceId, '/')[8] + +@description('The name of the vgw route table to create') +param vgwRouteTableName string + +@description('The name of the gateway subnet') +param gatewaySubnetName string = 'GatewaySubnet' + +@description('The name of the hub virtual network route table') +param hubVnetRouteTableResourceId string +var hubVnetRouteTableName = split(hubVnetRouteTableResourceId, '/')[8] + +// Conditional parameter assignment for VPN connection module +var vpnSharedKey = useSharedKey ? sharedKey : '' +var vpnKeyVaultUri = !useSharedKey ? keyVaultCertificateUri : '' + +// Parameter validation +var isValidUri = contains(keyVaultCertificateUri, 'https://') && contains(keyVaultCertificateUri, '/secrets/') + +// Conditional validation to ensure either sharedKey or keyVaultCertificateUri is used correctly +resource validateKeyOrUri 'Microsoft.Resources/deployments@2021-04-01' = if (!useSharedKey && !isValidUri) { + name: 'InvalidKeyVaultCertificateUri-${deploymentNameSuffix}' + properties: { + mode: 'Incremental' + parameters: { + message: { + value: 'Invalid Key Vault Certificate URI. It must start with "https://" and contain "/secrets/".' + } + } + templateLink: { + uri: 'https://validatemessage.com' // Placeholder for validation message, replace if needed + } + } +} + +// calling Virtual Network Gateway Module +module vpnGatewayModule 'modules/vpn-gateway.bicep' = { + name: 'vpnGateway-${deploymentNameSuffix}' + scope: resourceGroup(hubResourceGroupName) + params: { + vgwname: vgwName + vgwlocation: vgwLocation + publicIpAddressNames: vgwPublicIpAddressNames + vgwsku: vgwSku + vnetName: hubVnetName + } +} + +// calling Local Network Gateway Module +module localNetworkGatewayModule 'modules/local-network-gateway.bicep' = { + name: 'localNetworkGateway-${deploymentNameSuffix}' + scope: resourceGroup(hubResourceGroupName) + params: { + vgwlocation: vgwLocation + localNetworkGatewayName: localNetworkGatewayName + gatewayIpAddress: localGatewayIpAddress + addressPrefixes: localAddressPrefixes + } +} + +// calling VPN Connection Module +module vpnConnectionModule 'modules/vpn-connection.bicep' = { + name: 'vpnConnection-${deploymentNameSuffix}' + scope: resourceGroup(hubResourceGroupName) + params: { + vpnConnectionName: '${vgwName}-to-${localNetworkGatewayName}' + vgwlocation: vgwLocation + vpnGatewayName: vgwName + vpnGatewayResourceGroupName: hubResourceGroupName + sharedKey: vpnSharedKey + keyVaultCertificateUri: vpnKeyVaultUri + localNetworkGatewayName: localNetworkGatewayName + } + dependsOn: [ + vpnGatewayModule + localNetworkGatewayModule + validateKeyOrUri + ] +} + +// Loop through the vnetResourceIdList and to retrieve the peerings for each VNet +module retrieveVnetInfo 'modules/retrieve-existing.bicep' = [for (vnetId, i) in vnetResourceIdList: { + name: 'retrieveVnetPeerings-${deploymentNameSuffix}-${i}' + scope: resourceGroup(split(vnetId, '/')[2], split(vnetId, '/')[4]) + params: { + vnetResourceId: vnetId + } + dependsOn: [ + vpnConnectionModule + ] +}] + +// Get the hub virtual network peerings +module retrieveHubVnetInfo 'modules/retrieve-existing.bicep' = { + name: 'retrieveHubVnetPeerings-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + vnetResourceId: hubVirtualNetworkResourceId + } + dependsOn: [ + vpnConnectionModule + ] +} + +// retrieve the route table information for the hub vnet including the firewall private IP and gateway subnet address space info to be used for the new vgw route table and routes +module retrieveRouteTableInfo 'modules/retrieve-existing.bicep' = { + name: 'retrieveRouteTableInfo-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + vnetResourceId: hubVirtualNetworkResourceId + azureFirewallName: azureFirewallName + subnetName: gatewaySubnetName + } + dependsOn: [ + updatePeerings + ] +} + +// Call update the Hub peerings first to enable spokes to use the VPN Gateway, if not done first, spokes will fail their update +module updateHubPeerings 'modules/vnet-peerings.bicep' = { + name: 'updateHubPeerings-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + vnetResourceId: retrieveHubVnetInfo.outputs.peeringsData.vnetResourceId + peeringsList: retrieveHubVnetInfo.outputs.peeringsData.peeringsList + } + dependsOn: [ + retrieveHubVnetInfo + retrieveVnetInfo + ] +} + + +// Update the peerings for each spoke VNet to use the VPN Gateway +module updatePeerings 'modules/vnet-peerings.bicep' = [for (vnetId, i) in vnetResourceIdList: { + name: 'updatePeerings-${deploymentNameSuffix}-${i}' + scope: resourceGroup(split(vnetId, '/')[2], split(vnetId, '/')[4]) + params: { + vnetResourceId: retrieveVnetInfo[i].outputs.peeringsData.vnetResourceId + peeringsList: retrieveVnetInfo[i].outputs.peeringsData.peeringsList + } + dependsOn: [ + retrieveVnetInfo + updateHubPeerings + ] +}] + +// Create the route table for the VPN Gateway subnet, will route spoke vnets to through the firewall, overriding default behavior +module createRouteTable 'modules/route-table.bicep' = { + name: 'createVgwRouteTable-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + routeTableName: vgwRouteTableName + } + dependsOn: [ + retrieveVnetInfo + retrieveRouteTableInfo + updateHubPeerings + updatePeerings + ] +} + +// Create the routes to the firewall for the spoke vnets, if vnet is not provided in the "allowedAzureAddressPrefixes" then the spoke will not be able to use the VPN Gateway +module createRoutes 'modules/routes.bicep' = [for (vnetResourceId, i) in vnetResourceIdList: { + name: 'createRoute-${i}-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + routeTableName: vgwRouteTableName + addressSpace: retrieveVnetInfo[i].outputs.vnetAddressSpace + routeName: 'route-${i}' + nextHopType: 'VirtualAppliance' + nextHopIpAddress: retrieveRouteTableInfo.outputs.firewallPrivateIp + } + dependsOn: [ + createRouteTable + ] +}] + +// Create the routes to the firewall for the hub vnet as and override to the onprem networks +module createHubRoutesToOnPrem 'modules/routes.bicep' = { + name: 'createOverrideRoutes-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + routeTableName: hubVnetRouteTableName + addressSpace: localAddressPrefixes + routeName: 'route-onprem-override' + nextHopType: 'VirtualAppliance' + nextHopIpAddress: retrieveRouteTableInfo.outputs.firewallPrivateIp + } + dependsOn: [ + createRouteTable + ] +} + + +// Associate the vgw route table with the gateway subnet so the gateway routes traffic destined for spokes through the firewall +module associateRouteTable 'modules/associate-route-table.bicep' = { + name: 'associateRouteTable-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + vnetResourceId: hubVirtualNetworkResourceId + routeTableResourceId: createRouteTable.outputs.routeTableId + subnetName: gatewaySubnetName + subnetAddressPrefix: retrieveRouteTableInfo.outputs.subnetAddressPrefix + } + dependsOn: [ + createRouteTable + ] +} + + +// Create the firewall rules +module firewallRules 'modules/firewall-rules.bicep' = { + name: 'firewallRules-${deploymentNameSuffix}' + scope: resourceGroup(split(hubVirtualNetworkResourceId, '/')[2], split(hubVirtualNetworkResourceId, '/')[4]) + params: { + allowVnetAddressSpaces: allowedAzureAddressPrefixes + onPremAddressSpaces: localAddressPrefixes + firewallPolicyId: retrieveRouteTableInfo.outputs.firewallPolicyId + priorityValue: 300 + } + dependsOn: [ + associateRouteTable + ] +} diff --git a/src/bicep/add-ons/virtual-network-gateway/solution.json b/src/bicep/add-ons/virtual-network-gateway/solution.json new file mode 100644 index 000000000..92531d82f --- /dev/null +++ b/src/bicep/add-ons/virtual-network-gateway/solution.json @@ -0,0 +1,1443 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "7891133147346503241" + } + }, + "parameters": { + "vgwName": { + "type": "string", + "metadata": { + "description": "The name of the VPN Gateway." + } + }, + "vgwLocation": { + "type": "string", + "metadata": { + "description": "The Azure region location of the VPN Gateway." + } + }, + "vgwPublicIpAddressNames": { + "type": "array", + "metadata": { + "description": "The names of the public IP addresses to use for the VPN Gateway." + } + }, + "vgwSku": { + "type": "string", + "allowedValues": [ + "VpnGw2", + "VpnGw3", + "VpnGw4", + "VpnGw5" + ], + "metadata": { + "description": "The SKU of the VPN Gateway." + } + }, + "localNetworkGatewayName": { + "type": "string", + "metadata": { + "description": "Local Network Gateway Name" + } + }, + "localGatewayIpAddress": { + "type": "string", + "metadata": { + "description": "IP Address of the Local Network Gateway, must be a public IP address or be able to be connected to from MLZ network" + } + }, + "allowedAzureAddressPrefixes": { + "type": "array", + "metadata": { + "description": "Azure address prefixes allowed to communicate to VPN Gateway to on-premises network" + } + }, + "localAddressPrefixes": { + "type": "array", + "metadata": { + "description": "Address prefixes of the Local Network which will be routable through the VPN Gateway" + } + }, + "useSharedKey": { + "type": "bool", + "metadata": { + "description": "Choose whether to use a shared key or Key Vault certificate URI for the VPN connection." + } + }, + "sharedKey": { + "type": "securestring", + "metadata": { + "description": "The shared key to use for the VPN connection. If using the shared key, this must be provided." + } + }, + "keyVaultCertificateUri": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The URI of the Key Vault certificate to use for the VPN connection. If using a Key Vault certificate, this must be a valid URI." + } + }, + "deploymentNameSuffix": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "A suffix to use for naming deployments uniquely." + } + }, + "hubVirtualNetworkResourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the hub virtual network." + } + }, + "vnetResourceIdList": { + "type": "array", + "metadata": { + "description": "List of peered networks that should use the VPN Gateway once configured." + } + }, + "azureFirewallResourceId": { + "type": "string", + "metadata": { + "description": "The name of the Azure Firewall to retrieve the internal IP address from." + } + }, + "vgwRouteTableName": { + "type": "string", + "metadata": { + "description": "The name of the vgw route table to create" + } + }, + "gatewaySubnetName": { + "type": "string", + "defaultValue": "GatewaySubnet", + "metadata": { + "description": "The name of the gateway subnet" + } + }, + "hubVnetRouteTableResourceId": { + "type": "string", + "metadata": { + "description": "The name of the hub virtual network route table" + } + } + }, + "variables": { + "hubResourceGroupName": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "hubVnetName": "[split(parameters('hubVirtualNetworkResourceId'), '/')[8]]", + "azureFirewallName": "[split(parameters('azureFirewallResourceId'), '/')[8]]", + "hubVnetRouteTableName": "[split(parameters('hubVnetRouteTableResourceId'), '/')[8]]", + "vpnSharedKey": "[if(parameters('useSharedKey'), parameters('sharedKey'), '')]", + "vpnKeyVaultUri": "[if(not(parameters('useSharedKey')), parameters('keyVaultCertificateUri'), '')]", + "isValidUri": "[and(contains(parameters('keyVaultCertificateUri'), 'https://'), contains(parameters('keyVaultCertificateUri'), '/secrets/'))]" + }, + "resources": [ + { + "condition": "[and(not(parameters('useSharedKey')), not(variables('isValidUri')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2021-04-01", + "name": "[format('InvalidKeyVaultCertificateUri-{0}', parameters('deploymentNameSuffix'))]", + "properties": { + "mode": "Incremental", + "parameters": { + "message": { + "value": "Invalid Key Vault Certificate URI. It must start with \"https://\" and contain \"/secrets/\"." + } + }, + "templateLink": { + "uri": "https://validatemessage.com" + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('vpnGateway-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[variables('hubResourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vgwname": { + "value": "[parameters('vgwName')]" + }, + "vgwlocation": { + "value": "[parameters('vgwLocation')]" + }, + "publicIpAddressNames": { + "value": "[parameters('vgwPublicIpAddressNames')]" + }, + "vgwsku": { + "value": "[parameters('vgwSku')]" + }, + "vnetName": { + "value": "[variables('hubVnetName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "3589752526565849732" + } + }, + "parameters": { + "vgwname": { + "type": "string" + }, + "vgwlocation": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "publicIpAddressNames": { + "type": "array" + }, + "vgwsku": { + "type": "string" + }, + "vnetName": { + "type": "string" + } + }, + "variables": { + "gatewaySubnetId": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), 'GatewaySubnet')]", + "firstPublicIpAddressId": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressNames')[0])]", + "secondPublicIpAddressId": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressNames')[1])]" + }, + "resources": [ + { + "copy": { + "name": "publicIpAddresses", + "count": "[length(parameters('publicIpAddressNames'))]" + }, + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2023-02-01", + "name": "[parameters('publicIpAddressNames')[copyIndex()]]", + "location": "[parameters('vgwlocation')]", + "sku": { + "name": "Standard" + }, + "properties": { + "publicIPAllocationMethod": "Static" + } + }, + { + "type": "Microsoft.Network/virtualNetworkGateways", + "apiVersion": "2023-02-01", + "name": "[parameters('vgwname')]", + "location": "[parameters('vgwlocation')]", + "properties": { + "gatewayType": "Vpn", + "ipConfigurations": [ + { + "name": "first", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "subnet": { + "id": "[variables('gatewaySubnetId')]" + }, + "publicIPAddress": { + "id": "[variables('firstPublicIpAddressId')]" + } + } + }, + { + "name": "second", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "[variables('secondPublicIpAddressId')]" + }, + "subnet": { + "id": "[variables('gatewaySubnetId')]" + } + } + } + ], + "activeActive": true, + "vpnType": "RouteBased", + "vpnGatewayGeneration": "Generation2", + "enableBgp": false, + "enablePrivateIpAddress": false, + "sku": { + "name": "[parameters('vgwsku')]", + "tier": "[parameters('vgwsku')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressNames')[1])]", + "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressNames')[0])]" + ] + } + ] + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('localNetworkGateway-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[variables('hubResourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vgwlocation": { + "value": "[parameters('vgwLocation')]" + }, + "localNetworkGatewayName": { + "value": "[parameters('localNetworkGatewayName')]" + }, + "gatewayIpAddress": { + "value": "[parameters('localGatewayIpAddress')]" + }, + "addressPrefixes": { + "value": "[parameters('localAddressPrefixes')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "17093542793626818819" + } + }, + "parameters": { + "vgwlocation": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "localNetworkGatewayName": { + "type": "string" + }, + "gatewayIpAddress": { + "type": "string" + }, + "addressPrefixes": { + "type": "array" + } + }, + "resources": [ + { + "type": "Microsoft.Network/localNetworkGateways", + "apiVersion": "2023-02-01", + "name": "[parameters('localNetworkGatewayName')]", + "location": "[parameters('vgwlocation')]", + "properties": { + "gatewayIpAddress": "[parameters('gatewayIpAddress')]", + "localNetworkAddressSpace": { + "addressPrefixes": "[parameters('addressPrefixes')]" + } + } + } + ] + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('vpnConnection-{0}', parameters('deploymentNameSuffix'))]", + "resourceGroup": "[variables('hubResourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vpnConnectionName": { + "value": "[format('{0}-to-{1}', parameters('vgwName'), parameters('localNetworkGatewayName'))]" + }, + "vgwlocation": { + "value": "[parameters('vgwLocation')]" + }, + "vpnGatewayName": { + "value": "[parameters('vgwName')]" + }, + "vpnGatewayResourceGroupName": { + "value": "[variables('hubResourceGroupName')]" + }, + "sharedKey": { + "value": "[variables('vpnSharedKey')]" + }, + "keyVaultCertificateUri": { + "value": "[variables('vpnKeyVaultUri')]" + }, + "localNetworkGatewayName": { + "value": "[parameters('localNetworkGatewayName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "17808641870371981197" + } + }, + "parameters": { + "vpnConnectionName": { + "type": "string" + }, + "vgwlocation": { + "type": "string" + }, + "vpnGatewayName": { + "type": "string" + }, + "vpnGatewayResourceGroupName": { + "type": "string" + }, + "sharedKey": { + "type": "string" + }, + "keyVaultCertificateUri": { + "type": "string" + }, + "localNetworkGatewayName": { + "type": "string" + } + }, + "variables": { + "useSharedKey": "[not(empty(parameters('sharedKey')))]", + "useKeyVaultCertificate": "[not(empty(parameters('keyVaultCertificateUri')))]", + "errorMsg": "[if(and(variables('useSharedKey'), variables('useKeyVaultCertificate')), 'Cannot provide both sharedKey and keyVaultCertificateUri', '')]", + "connectionSharedKey": "[if(variables('useSharedKey'), parameters('sharedKey'), null())]", + "connectionIpsecPolicies": "[if(variables('useKeyVaultCertificate'), createArray(createObject('saLifeTimeSeconds', 3600, 'saDataSizeKilobytes', 102400000, 'ipsecEncryption', 'AES256', 'ipsecIntegrity', 'SHA256', 'ikeEncryption', 'AES256', 'ikeIntegrity', 'SHA256', 'dhGroup', 'DHGroup2', 'pfsGroup', 'PFS2')), null())]" + }, + "resources": [ + { + "condition": "[empty(variables('errorMsg'))]", + "type": "Microsoft.Network/connections", + "apiVersion": "2023-02-01", + "name": "[parameters('vpnConnectionName')]", + "location": "[parameters('vgwlocation')]", + "properties": { + "virtualNetworkGateway1": { + "id": "[resourceId(parameters('vpnGatewayResourceGroupName'), 'Microsoft.Network/virtualNetworkGateways', parameters('vpnGatewayName'))]" + }, + "localNetworkGateway2": { + "id": "[resourceId(parameters('vpnGatewayResourceGroupName'), 'Microsoft.Network/localNetworkGateways', parameters('localNetworkGatewayName'))]" + }, + "connectionType": "IPsec", + "routingWeight": 10, + "sharedKey": "[variables('connectionSharedKey')]", + "ipsecPolicies": "[variables('connectionIpsecPolicies')]", + "enableBgp": false, + "usePolicyBasedTrafficSelectors": false + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('hubResourceGroupName')), 'Microsoft.Resources/deployments', format('localNetworkGateway-{0}', parameters('deploymentNameSuffix')))]", + "[subscriptionResourceId('Microsoft.Resources/deployments', format('InvalidKeyVaultCertificateUri-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('hubResourceGroupName')), 'Microsoft.Resources/deployments', format('vpnGateway-{0}', parameters('deploymentNameSuffix')))]" + ] + }, + { + "copy": { + "name": "retrieveVnetInfo", + "count": "[length(parameters('vnetResourceIdList'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('retrieveVnetPeerings-{0}-{1}', parameters('deploymentNameSuffix'), copyIndex())]", + "subscriptionId": "[split(parameters('vnetResourceIdList')[copyIndex()], '/')[2]]", + "resourceGroup": "[split(parameters('vnetResourceIdList')[copyIndex()], '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vnetResourceId": { + "value": "[parameters('vnetResourceIdList')[copyIndex()]]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "8229271046057649788" + } + }, + "parameters": { + "azureFirewallName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the Azure Firewall (optional)" + } + }, + "subnetName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the subnet (optional)" + } + }, + "vnetResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The resource ID of the existing spoke virtual network (optional)" + } + }, + "routeTableName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the route table associated with the hub virtual network (optional)" + } + } + }, + "resources": [], + "outputs": { + "routeTableId": { + "type": "string", + "value": "[if(not(empty(parameters('routeTableName'))), resourceId('Microsoft.Network/routeTables', parameters('routeTableName')), 'N/A')]" + }, + "firewallPrivateIp": { + "type": "string", + "value": "[if(and(not(empty(parameters('azureFirewallName'))), not(empty(parameters('vnetResourceId')))), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceId'), '/')[2], split(parameters('vnetResourceId'), '/')[4]), 'Microsoft.Network/azureFirewalls', parameters('azureFirewallName')), '2020-11-01').ipConfigurations[0].properties.privateIPAddress, 'N/A')]" + }, + "firewallPolicyId": { + "type": "string", + "value": "[if(not(empty(parameters('azureFirewallName'))), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceId'), '/')[2], split(parameters('vnetResourceId'), '/')[4]), 'Microsoft.Network/azureFirewalls', parameters('azureFirewallName')), '2020-11-01').firewallPolicy.id, 'N/A')]" + }, + "subnetAddressPrefix": { + "type": "string", + "value": "[if(and(not(empty(parameters('subnetName'))), not(empty(parameters('vnetResourceId')))), reference(resourceId('Microsoft.Network/virtualNetworks/subnets', last(split(parameters('vnetResourceId'), '/')), parameters('subnetName')), '2020-11-01').addressPrefix, 'N/A')]" + }, + "vnetAddressSpace": { + "type": "array", + "value": "[if(not(empty(parameters('vnetResourceId'))), reference(resourceId('Microsoft.Network/virtualNetworks', last(split(parameters('vnetResourceId'), '/'))), '2020-11-01').addressSpace.addressPrefixes, createArray())]" + }, + "peeringsData": { + "type": "object", + "value": "[if(not(empty(parameters('vnetResourceId'))), createObject('vnetResourceId', parameters('vnetResourceId'), 'peeringsList', reference(resourceId('Microsoft.Network/virtualNetworks', last(split(parameters('vnetResourceId'), '/'))), '2020-11-01').virtualNetworkPeerings), createObject('vnetResourceId', 'N/A', 'peeringsList', createArray()))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('hubResourceGroupName')), 'Microsoft.Resources/deployments', format('vpnConnection-{0}', parameters('deploymentNameSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('retrieveHubVnetPeerings-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vnetResourceId": { + "value": "[parameters('hubVirtualNetworkResourceId')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "8229271046057649788" + } + }, + "parameters": { + "azureFirewallName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the Azure Firewall (optional)" + } + }, + "subnetName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the subnet (optional)" + } + }, + "vnetResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The resource ID of the existing spoke virtual network (optional)" + } + }, + "routeTableName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the route table associated with the hub virtual network (optional)" + } + } + }, + "resources": [], + "outputs": { + "routeTableId": { + "type": "string", + "value": "[if(not(empty(parameters('routeTableName'))), resourceId('Microsoft.Network/routeTables', parameters('routeTableName')), 'N/A')]" + }, + "firewallPrivateIp": { + "type": "string", + "value": "[if(and(not(empty(parameters('azureFirewallName'))), not(empty(parameters('vnetResourceId')))), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceId'), '/')[2], split(parameters('vnetResourceId'), '/')[4]), 'Microsoft.Network/azureFirewalls', parameters('azureFirewallName')), '2020-11-01').ipConfigurations[0].properties.privateIPAddress, 'N/A')]" + }, + "firewallPolicyId": { + "type": "string", + "value": "[if(not(empty(parameters('azureFirewallName'))), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceId'), '/')[2], split(parameters('vnetResourceId'), '/')[4]), 'Microsoft.Network/azureFirewalls', parameters('azureFirewallName')), '2020-11-01').firewallPolicy.id, 'N/A')]" + }, + "subnetAddressPrefix": { + "type": "string", + "value": "[if(and(not(empty(parameters('subnetName'))), not(empty(parameters('vnetResourceId')))), reference(resourceId('Microsoft.Network/virtualNetworks/subnets', last(split(parameters('vnetResourceId'), '/')), parameters('subnetName')), '2020-11-01').addressPrefix, 'N/A')]" + }, + "vnetAddressSpace": { + "type": "array", + "value": "[if(not(empty(parameters('vnetResourceId'))), reference(resourceId('Microsoft.Network/virtualNetworks', last(split(parameters('vnetResourceId'), '/'))), '2020-11-01').addressSpace.addressPrefixes, createArray())]" + }, + "peeringsData": { + "type": "object", + "value": "[if(not(empty(parameters('vnetResourceId'))), createObject('vnetResourceId', parameters('vnetResourceId'), 'peeringsList', reference(resourceId('Microsoft.Network/virtualNetworks', last(split(parameters('vnetResourceId'), '/'))), '2020-11-01').virtualNetworkPeerings), createObject('vnetResourceId', 'N/A', 'peeringsList', createArray()))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, variables('hubResourceGroupName')), 'Microsoft.Resources/deployments', format('vpnConnection-{0}', parameters('deploymentNameSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vnetResourceId": { + "value": "[parameters('hubVirtualNetworkResourceId')]" + }, + "azureFirewallName": { + "value": "[variables('azureFirewallName')]" + }, + "subnetName": { + "value": "[parameters('gatewaySubnetName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "8229271046057649788" + } + }, + "parameters": { + "azureFirewallName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the Azure Firewall (optional)" + } + }, + "subnetName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the subnet (optional)" + } + }, + "vnetResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The resource ID of the existing spoke virtual network (optional)" + } + }, + "routeTableName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the route table associated with the hub virtual network (optional)" + } + } + }, + "resources": [], + "outputs": { + "routeTableId": { + "type": "string", + "value": "[if(not(empty(parameters('routeTableName'))), resourceId('Microsoft.Network/routeTables', parameters('routeTableName')), 'N/A')]" + }, + "firewallPrivateIp": { + "type": "string", + "value": "[if(and(not(empty(parameters('azureFirewallName'))), not(empty(parameters('vnetResourceId')))), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceId'), '/')[2], split(parameters('vnetResourceId'), '/')[4]), 'Microsoft.Network/azureFirewalls', parameters('azureFirewallName')), '2020-11-01').ipConfigurations[0].properties.privateIPAddress, 'N/A')]" + }, + "firewallPolicyId": { + "type": "string", + "value": "[if(not(empty(parameters('azureFirewallName'))), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceId'), '/')[2], split(parameters('vnetResourceId'), '/')[4]), 'Microsoft.Network/azureFirewalls', parameters('azureFirewallName')), '2020-11-01').firewallPolicy.id, 'N/A')]" + }, + "subnetAddressPrefix": { + "type": "string", + "value": "[if(and(not(empty(parameters('subnetName'))), not(empty(parameters('vnetResourceId')))), reference(resourceId('Microsoft.Network/virtualNetworks/subnets', last(split(parameters('vnetResourceId'), '/')), parameters('subnetName')), '2020-11-01').addressPrefix, 'N/A')]" + }, + "vnetAddressSpace": { + "type": "array", + "value": "[if(not(empty(parameters('vnetResourceId'))), reference(resourceId('Microsoft.Network/virtualNetworks', last(split(parameters('vnetResourceId'), '/'))), '2020-11-01').addressSpace.addressPrefixes, createArray())]" + }, + "peeringsData": { + "type": "object", + "value": "[if(not(empty(parameters('vnetResourceId'))), createObject('vnetResourceId', parameters('vnetResourceId'), 'peeringsList', reference(resourceId('Microsoft.Network/virtualNetworks', last(split(parameters('vnetResourceId'), '/'))), '2020-11-01').virtualNetworkPeerings), createObject('vnetResourceId', 'N/A', 'peeringsList', createArray()))]" + } + } + } + }, + "dependsOn": [ + "updatePeerings" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('updateHubPeerings-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vnetResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveHubVnetPeerings-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.peeringsData.value.vnetResourceId]" + }, + "peeringsList": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveHubVnetPeerings-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.peeringsData.value.peeringsList]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "10372783504892384694" + } + }, + "parameters": { + "peeringsList": { + "type": "array", + "metadata": { + "description": "The list of peerings to update" + } + }, + "vnetResourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the existing virtual network" + } + } + }, + "variables": { + "copy": [ + { + "name": "updatedPeerings", + "count": "[length(parameters('peeringsList'))]", + "input": { + "name": "[last(split(parameters('peeringsList')[copyIndex('updatedPeerings')].id, '/'))]", + "properties": { + "allowGatewayTransit": "[if(contains(variables('vnetName'), '-hub-'), true(), parameters('peeringsList')[copyIndex('updatedPeerings')].properties.allowGatewayTransit)]", + "useRemoteGateways": "[if(not(contains(variables('vnetName'), '-hub-')), true(), parameters('peeringsList')[copyIndex('updatedPeerings')].properties.useRemoteGateways)]", + "allowForwardedTraffic": "[if(equals(parameters('peeringsList')[copyIndex('updatedPeerings')].properties.allowForwardedTraffic, null()), true(), parameters('peeringsList')[copyIndex('updatedPeerings')].properties.allowForwardedTraffic)]", + "remoteVirtualNetwork": "[parameters('peeringsList')[copyIndex('updatedPeerings')].properties.remoteVirtualNetwork]" + } + } + } + ], + "vnetName": "[last(split(parameters('vnetResourceId'), '/'))]" + }, + "resources": [ + { + "copy": { + "name": "peeringUpdates", + "count": "[length(variables('updatedPeerings'))]" + }, + "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", + "apiVersion": "2021-02-01", + "name": "[format('{0}/{1}', variables('vnetName'), variables('updatedPeerings')[copyIndex()].name)]", + "properties": "[variables('updatedPeerings')[copyIndex()].properties]" + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveHubVnetPeerings-{0}', parameters('deploymentNameSuffix')))]", + "retrieveVnetInfo" + ] + }, + { + "copy": { + "name": "updatePeerings", + "count": "[length(parameters('vnetResourceIdList'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('updatePeerings-{0}-{1}', parameters('deploymentNameSuffix'), copyIndex())]", + "subscriptionId": "[split(parameters('vnetResourceIdList')[copyIndex()], '/')[2]]", + "resourceGroup": "[split(parameters('vnetResourceIdList')[copyIndex()], '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vnetResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceIdList')[copyIndex()], '/')[2], split(parameters('vnetResourceIdList')[copyIndex()], '/')[4]), 'Microsoft.Resources/deployments', format('retrieveVnetPeerings-{0}-{1}', parameters('deploymentNameSuffix'), copyIndex())), '2022-09-01').outputs.peeringsData.value.vnetResourceId]" + }, + "peeringsList": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceIdList')[copyIndex()], '/')[2], split(parameters('vnetResourceIdList')[copyIndex()], '/')[4]), 'Microsoft.Resources/deployments', format('retrieveVnetPeerings-{0}-{1}', parameters('deploymentNameSuffix'), copyIndex())), '2022-09-01').outputs.peeringsData.value.peeringsList]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "10372783504892384694" + } + }, + "parameters": { + "peeringsList": { + "type": "array", + "metadata": { + "description": "The list of peerings to update" + } + }, + "vnetResourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the existing virtual network" + } + } + }, + "variables": { + "copy": [ + { + "name": "updatedPeerings", + "count": "[length(parameters('peeringsList'))]", + "input": { + "name": "[last(split(parameters('peeringsList')[copyIndex('updatedPeerings')].id, '/'))]", + "properties": { + "allowGatewayTransit": "[if(contains(variables('vnetName'), '-hub-'), true(), parameters('peeringsList')[copyIndex('updatedPeerings')].properties.allowGatewayTransit)]", + "useRemoteGateways": "[if(not(contains(variables('vnetName'), '-hub-')), true(), parameters('peeringsList')[copyIndex('updatedPeerings')].properties.useRemoteGateways)]", + "allowForwardedTraffic": "[if(equals(parameters('peeringsList')[copyIndex('updatedPeerings')].properties.allowForwardedTraffic, null()), true(), parameters('peeringsList')[copyIndex('updatedPeerings')].properties.allowForwardedTraffic)]", + "remoteVirtualNetwork": "[parameters('peeringsList')[copyIndex('updatedPeerings')].properties.remoteVirtualNetwork]" + } + } + } + ], + "vnetName": "[last(split(parameters('vnetResourceId'), '/'))]" + }, + "resources": [ + { + "copy": { + "name": "peeringUpdates", + "count": "[length(variables('updatedPeerings'))]" + }, + "type": "Microsoft.Network/virtualNetworks/virtualNetworkPeerings", + "apiVersion": "2021-02-01", + "name": "[format('{0}/{1}', variables('vnetName'), variables('updatedPeerings')[copyIndex()].name)]", + "properties": "[variables('updatedPeerings')[copyIndex()].properties]" + } + ] + } + }, + "dependsOn": [ + "retrieveVnetInfo", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('updateHubPeerings-{0}', parameters('deploymentNameSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('createVgwRouteTable-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "routeTableName": { + "value": "[parameters('vgwRouteTableName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "13855829966458352" + } + }, + "parameters": { + "routeTableName": { + "type": "string", + "metadata": { + "description": "Name of the route table to create" + } + } + }, + "resources": [ + { + "type": "Microsoft.Network/routeTables", + "apiVersion": "2021-02-01", + "name": "[parameters('routeTableName')]", + "location": "[resourceGroup().location]", + "properties": { + "disableBgpRoutePropagation": true + } + } + ], + "outputs": { + "routeTableId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/routeTables', parameters('routeTableName'))]" + } + } + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix')))]", + "retrieveVnetInfo", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('updateHubPeerings-{0}', parameters('deploymentNameSuffix')))]", + "updatePeerings" + ] + }, + { + "copy": { + "name": "createRoutes", + "count": "[length(parameters('vnetResourceIdList'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('createRoute-{0}-{1}', copyIndex(), parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "routeTableName": { + "value": "[parameters('vgwRouteTableName')]" + }, + "addressSpace": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceIdList')[copyIndex()], '/')[2], split(parameters('vnetResourceIdList')[copyIndex()], '/')[4]), 'Microsoft.Resources/deployments', format('retrieveVnetPeerings-{0}-{1}', parameters('deploymentNameSuffix'), copyIndex())), '2022-09-01').outputs.vnetAddressSpace.value]" + }, + "routeName": { + "value": "[format('route-{0}', copyIndex())]" + }, + "nextHopType": { + "value": "VirtualAppliance" + }, + "nextHopIpAddress": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.firewallPrivateIp.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "280047033181845191" + } + }, + "parameters": { + "routeTableName": { + "type": "string", + "metadata": { + "description": "Name of the route table to create" + } + }, + "routeName": { + "type": "string", + "metadata": { + "description": "Name of the route" + } + }, + "addressSpace": { + "type": "array", + "metadata": { + "description": "CIDR prefixes for the route" + } + }, + "nextHopType": { + "type": "string", + "metadata": { + "description": "The next hop type for the route" + } + }, + "nextHopIpAddress": { + "type": "string", + "metadata": { + "description": "The next hop IP address for the route" + } + } + }, + "resources": [ + { + "copy": { + "name": "routes", + "count": "[length(parameters('addressSpace'))]" + }, + "type": "Microsoft.Network/routeTables/routes", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('routeTableName'), format('{0}-{1}', parameters('routeName'), copyIndex()))]", + "properties": { + "addressPrefix": "[parameters('addressSpace')[copyIndex()]]", + "nextHopType": "[parameters('nextHopType')]", + "nextHopIpAddress": "[if(not(equals(parameters('nextHopIpAddress'), '')), parameters('nextHopIpAddress'), null())]" + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('createVgwRouteTable-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('vnetResourceIdList')[copyIndex()], '/')[2], split(parameters('vnetResourceIdList')[copyIndex()], '/')[4]), 'Microsoft.Resources/deployments', format('retrieveVnetPeerings-{0}-{1}', parameters('deploymentNameSuffix'), copyIndex()))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('createOverrideRoutes-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "routeTableName": { + "value": "[variables('hubVnetRouteTableName')]" + }, + "addressSpace": { + "value": "[parameters('localAddressPrefixes')]" + }, + "routeName": { + "value": "route-onprem-override" + }, + "nextHopType": { + "value": "VirtualAppliance" + }, + "nextHopIpAddress": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.firewallPrivateIp.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "280047033181845191" + } + }, + "parameters": { + "routeTableName": { + "type": "string", + "metadata": { + "description": "Name of the route table to create" + } + }, + "routeName": { + "type": "string", + "metadata": { + "description": "Name of the route" + } + }, + "addressSpace": { + "type": "array", + "metadata": { + "description": "CIDR prefixes for the route" + } + }, + "nextHopType": { + "type": "string", + "metadata": { + "description": "The next hop type for the route" + } + }, + "nextHopIpAddress": { + "type": "string", + "metadata": { + "description": "The next hop IP address for the route" + } + } + }, + "resources": [ + { + "copy": { + "name": "routes", + "count": "[length(parameters('addressSpace'))]" + }, + "type": "Microsoft.Network/routeTables/routes", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('routeTableName'), format('{0}-{1}', parameters('routeName'), copyIndex()))]", + "properties": { + "addressPrefix": "[parameters('addressSpace')[copyIndex()]]", + "nextHopType": "[parameters('nextHopType')]", + "nextHopIpAddress": "[if(not(equals(parameters('nextHopIpAddress'), '')), parameters('nextHopIpAddress'), null())]" + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('createVgwRouteTable-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('associateRouteTable-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vnetResourceId": { + "value": "[parameters('hubVirtualNetworkResourceId')]" + }, + "routeTableResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('createVgwRouteTable-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.routeTableId.value]" + }, + "subnetName": { + "value": "[parameters('gatewaySubnetName')]" + }, + "subnetAddressPrefix": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.subnetAddressPrefix.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "17439321293785150273" + } + }, + "parameters": { + "vnetResourceId": { + "type": "string", + "metadata": { + "description": "virtual network resource ID that holds the subnet" + } + }, + "routeTableResourceId": { + "type": "string", + "metadata": { + "description": "route table resource ID to associate with the subnet" + } + }, + "subnetName": { + "type": "string", + "metadata": { + "description": "name of the subnet to associate with the route table" + } + }, + "subnetAddressPrefix": { + "type": "string", + "metadata": { + "description": "address prefix of the gateway subnet" + } + } + }, + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', last(split(parameters('vnetResourceId'), '/')), parameters('subnetName'))]", + "properties": { + "addressPrefix": "[parameters('subnetAddressPrefix')]", + "routeTable": { + "id": "[resourceId('Microsoft.Network/routeTables', last(split(parameters('routeTableResourceId'), '/')))]" + } + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('createVgwRouteTable-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('createHubRoute-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "routeTableName": { + "value": "[variables('hubVnetRouteTableName')]" + }, + "addressSpace": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.vnetAddressSpace.value]" + }, + "routeName": { + "value": "route-hubvnet" + }, + "nextHopType": { + "value": "VirtualAppliance" + }, + "nextHopIpAddress": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.firewallPrivateIp.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "280047033181845191" + } + }, + "parameters": { + "routeTableName": { + "type": "string", + "metadata": { + "description": "Name of the route table to create" + } + }, + "routeName": { + "type": "string", + "metadata": { + "description": "Name of the route" + } + }, + "addressSpace": { + "type": "array", + "metadata": { + "description": "CIDR prefixes for the route" + } + }, + "nextHopType": { + "type": "string", + "metadata": { + "description": "The next hop type for the route" + } + }, + "nextHopIpAddress": { + "type": "string", + "metadata": { + "description": "The next hop IP address for the route" + } + } + }, + "resources": [ + { + "copy": { + "name": "routes", + "count": "[length(parameters('addressSpace'))]" + }, + "type": "Microsoft.Network/routeTables/routes", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('routeTableName'), format('{0}-{1}', parameters('routeName'), copyIndex()))]", + "properties": { + "addressPrefix": "[parameters('addressSpace')[copyIndex()]]", + "nextHopType": "[parameters('nextHopType')]", + "nextHopIpAddress": "[if(not(equals(parameters('nextHopIpAddress'), '')), parameters('nextHopIpAddress'), null())]" + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('createVgwRouteTable-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('firewallRules-{0}', parameters('deploymentNameSuffix'))]", + "subscriptionId": "[split(parameters('hubVirtualNetworkResourceId'), '/')[2]]", + "resourceGroup": "[split(parameters('hubVirtualNetworkResourceId'), '/')[4]]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "allowVnetAddressSpaces": { + "value": "[parameters('allowedAzureAddressPrefixes')]" + }, + "onPremAddressSpaces": { + "value": "[parameters('localAddressPrefixes')]" + }, + "firewallPolicyId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.firewallPolicyId.value]" + }, + "priorityValue": { + "value": 300 + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.31.34.60546", + "templateHash": "17390602903085657580" + } + }, + "parameters": { + "allowVnetAddressSpaces": { + "type": "array", + "metadata": { + "description": "The list of virtual network resource IDs to be used as the source IP groups" + } + }, + "onPremAddressSpaces": { + "type": "array", + "metadata": { + "description": "Address prefixes of the on-premises network" + } + }, + "firewallPolicyId": { + "type": "string", + "metadata": { + "description": "Name of the firewall policy" + } + }, + "priorityValue": { + "type": "int", + "minValue": 300, + "maxValue": 65000, + "metadata": { + "description": "The priority value for the rule collection" + } + } + }, + "variables": { + "outboundRules": { + "name": "AllowAzureToOnPremRule", + "ruleType": "NetworkRule", + "sourceAddresses": "[parameters('allowVnetAddressSpaces')]", + "destinationAddresses": "[parameters('onPremAddressSpaces')]", + "destinationPorts": [ + "*" + ], + "ipProtocols": [ + "Any" + ] + }, + "inboundRules": { + "name": "AllowOnPremToAzureRule", + "ruleType": "NetworkRule", + "sourceAddresses": "[parameters('onPremAddressSpaces')]", + "destinationAddresses": "[parameters('allowVnetAddressSpaces')]", + "destinationPorts": [ + "*" + ], + "ipProtocols": [ + "Any" + ] + } + }, + "resources": [ + { + "type": "Microsoft.Network/firewallPolicies/ruleCollectionGroups", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', last(split(parameters('firewallPolicyId'), '/')), 'VgwNetworkRuleCollectionGroup')]", + "properties": { + "priority": "[parameters('priorityValue')]", + "ruleCollections": [ + { + "name": "AllowVgw", + "priority": "[parameters('priorityValue')]", + "ruleCollectionType": "FirewallPolicyFilterRuleCollection", + "action": { + "type": "Allow" + }, + "rules": [ + "[variables('outboundRules')]", + "[variables('inboundRules')]" + ] + } + ] + } + } + ] + } + }, + "dependsOn": [ + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('associateRouteTable-{0}', parameters('deploymentNameSuffix')))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('hubVirtualNetworkResourceId'), '/')[2], split(parameters('hubVirtualNetworkResourceId'), '/')[4]), 'Microsoft.Resources/deployments', format('retrieveRouteTableInfo-{0}', parameters('deploymentNameSuffix')))]" + ] + } + ] +} \ No newline at end of file diff --git a/src/bicep/data/resourceAbbreviations.json b/src/bicep/data/resourceAbbreviations.json index d21898d97..13a981832 100644 --- a/src/bicep/data/resourceAbbreviations.json +++ b/src/bicep/data/resourceAbbreviations.json @@ -33,6 +33,7 @@ "remoteApplicationGroups": "vdag", "resourceGroups": "rg", "routeTables": "rt", + "scalingPlans": "vdscaling", "storageAccounts": "st", "subnets": "snet", "userAssignedIdentities": "id", diff --git a/src/bicep/form/mlz.portal.json b/src/bicep/form/mlz.portal.json index 77cc1d799..aaccce588 100644 --- a/src/bicep/form/mlz.portal.json +++ b/src/bicep/form/mlz.portal.json @@ -229,13 +229,13 @@ "label": "Resource Naming Prefix", "type": "Microsoft.Common.TextBox", "defaultValue": "", - "toolTip": "Specify a prefix (min 3 and max 6 lowercase characters and numbers) to prepend to all resources.", + "toolTip": "Specify a prefix (min 1 and max 6 lowercase characters and numbers) to prepend to all resources.", "constraints": { "required": true, "validations": [ { - "regex": "^[a-z][a-z0-9]{1,5}$", - "message": "The prefix must contain alphanumeric characters, begin with a letter, letters must be lowercase, and the length must be between 3 to 6 characters." + "regex": "^[a-z0-9]{1,6}$", + "message": "The prefix must contain alphanumeric characters, letters must be lowercase, and the length must be between 1 to 6 characters." } ] } diff --git a/src/bicep/mlz.bicep b/src/bicep/mlz.bicep index 75ee30713..6d54067ed 100644 --- a/src/bicep/mlz.bicep +++ b/src/bicep/mlz.bicep @@ -7,9 +7,9 @@ targetScope = 'subscription' // REQUIRED PARAMETERS -@minLength(3) +@minLength(1) @maxLength(6) -@description('A prefix, 3-6 alphanumeric characters without whitespace, used to prefix resources and generate uniqueness for resources with globally unique naming requirements like Storage Accounts and Log Analytics Workspaces') +@description('A prefix, 1-6 alphanumeric characters without whitespace, used to prefix resources and generate uniqueness for resources with globally unique naming requirements like Storage Accounts and Log Analytics Workspaces') param resourcePrefix string @allowed([ @@ -725,10 +725,12 @@ module storage 'modules/storage.bicep' = { blobsPrivateDnsZoneResourceId: networking.outputs.privateDnsZoneResourceIds.blob //deployIdentity: deployIdentity deploymentNameSuffix: deploymentNameSuffix + filesPrivateDnsZoneResourceId: networking.outputs.privateDnsZoneResourceIds.file keyVaultUri: customerManagedKeys.outputs.keyVaultUri location: location logStorageSkuName: logStorageSkuName mlzTags: logic.outputs.mlzTags + queuesPrivateDnsZoneResourceId: networking.outputs.privateDnsZoneResourceIds.queue resourceGroupNames: resourceGroups.outputs.names serviceToken: logic.outputs.tokens.service storageEncryptionKeyName: customerManagedKeys.outputs.storageKeyName diff --git a/src/bicep/mlz.json b/src/bicep/mlz.json index 2dc902750..df9569d57 100644 --- a/src/bicep/mlz.json +++ b/src/bicep/mlz.json @@ -4,17 +4,17 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "12938630250141528569" + "version": "0.31.92.45157", + "templateHash": "4662751976093611386" } }, "parameters": { "resourcePrefix": { "type": "string", - "minLength": 3, + "minLength": 1, "maxLength": 6, "metadata": { - "description": "A prefix, 3-6 alphanumeric characters without whitespace, used to prefix resources and generate uniqueness for resources with globally unique naming requirements like Storage Accounts and Log Analytics Workspaces" + "description": "A prefix, 1-6 alphanumeric characters without whitespace, used to prefix resources and generate uniqueness for resources with globally unique naming requirements like Storage Accounts and Log Analytics Workspaces" } }, "environmentAbbreviation": { @@ -887,8 +887,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "11424932506966599764" + "version": "0.31.92.45157", + "templateHash": "15810721730485220824" } }, "parameters": { @@ -967,8 +967,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7616623366375139473" + "version": "0.31.92.45157", + "templateHash": "11125044402666498605" } }, "parameters": { @@ -994,6 +994,7 @@ "tokens": { "type": "object", "defaultValue": { + "purpose": "purpose_token", "resource": "resource_token", "service": "service_token" } @@ -1419,6 +1420,7 @@ "remoteApplicationGroups": "vdag", "resourceGroups": "rg", "routeTables": "rt", + "scalingPlans": "vdscaling", "storageAccounts": "st", "subnets": "snet", "userAssignedIdentities": "id", @@ -1429,11 +1431,11 @@ "locations": "[variables('$fxv#0')[environment().name]]", "locationAbbreviation": "[variables('locations')[parameters('location')].abbreviation]", "resourceAbbreviations": "[variables('$fxv#1')]", - "namingConvention": "[format('{0}-{1}{2}-{3}-{4}-{5}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), parameters('environmentAbbreviation'), variables('locationAbbreviation'))]", - "namingConvention_Service": "[format('{0}-{1}{2}-{3}-{4}-{5}-{6}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('tokens').service, parameters('networkName'), parameters('environmentAbbreviation'), variables('locationAbbreviation'))]", + "namingConvention": "[format('{0}-{1}{2}-{3}-{4}-{5}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), variables('locationAbbreviation'), parameters('environmentAbbreviation'))]", + "namingConvention_Service": "[format('{0}-{1}{2}-{3}-{4}-{5}-{6}', toLower(parameters('resourcePrefix')), if(empty(parameters('stampIndex')), '', format('{0}-', parameters('stampIndex'))), parameters('tokens').resource, parameters('networkName'), parameters('tokens').service, variables('locationAbbreviation'), parameters('environmentAbbreviation'))]", "names": { "actionGroup": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').actionGroups)]", - "applicationGroup": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').applicationGroups)]", + "applicationGroup": "[replace(variables('namingConvention'), parameters('tokens').resource, format('{0}-desktop', variables('resourceAbbreviations').applicationGroups))]", "applicationInsights": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').applicationInsights)]", "appServicePlan": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').appServicePlans)]", "automationAccount": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').automationAccounts)]", @@ -1487,10 +1489,18 @@ "recoveryServicesVaultPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, variables('resourceAbbreviations').recoveryServicesVaults)]", "resourceGroup": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').resourceGroups)]", "routeTable": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').routeTables)]", + "scalingPlan": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').scalingPlans)]", + "scalingPlanDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').scalingPlans)]", "storageAccount": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').storageAccounts), parameters('networkName'), parameters('networkShortName'))]", "storageAccountDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", - "storageAccountNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", - "storageAccountPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').storageAccounts))]", + "storageAccountBlobNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-blob', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountFileNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-file', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountQueueNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-queue', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountTableNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-table', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountBlobPrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-blob', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountFilePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-file', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountQueuePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-queue', variables('resourceAbbreviations').storageAccounts))]", + "storageAccountTablePrivateEndpoint": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-table', variables('resourceAbbreviations').storageAccounts))]", "subnet": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').subnets)]", "userAssignedIdentity": "[replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').userAssignedIdentities)]", "virtualMachine": "[replace(replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').virtualMachines), parameters('environmentAbbreviation'), first(parameters('environmentAbbreviation'))), parameters('networkName'), ''), '-', '')]", @@ -1498,14 +1508,14 @@ "virtualMachineNetworkInterface": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').virtualMachines))]", "virtualNetwork": "[replace(variables('namingConvention'), parameters('tokens').resource, variables('resourceAbbreviations').virtualNetworks)]", "virtualNetworkDiagnosticSetting": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, variables('resourceAbbreviations').virtualNetworks)]", - "workspaceFeed": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').workspaces), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceFeedPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobal": "[replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').workspaces), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", - "workspaceGlobalPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-{1}', parameters('tokens').service, variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]" + "workspaceFeed": "[replace(replace(variables('namingConvention'), parameters('tokens').resource, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceFeedDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceFeedNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceFeedPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-feed', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobal": "[replace(replace(variables('namingConvention'), parameters('tokens').resource, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobalDiagnosticSetting": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').diagnosticSettings), parameters('tokens').service, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobalNetworkInterface": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').networkInterfaces), parameters('tokens').service, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]", + "workspaceGlobalPrivateEndpoint": "[replace(replace(replace(variables('namingConvention_Service'), parameters('tokens').resource, variables('resourceAbbreviations').privateEndpoints), parameters('tokens').service, format('{0}-global', variables('resourceAbbreviations').workspaces)), format('-{0}', parameters('stampIndex')), '')]" } }, "resources": [], @@ -1551,8 +1561,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "4151572700986885014" + "version": "0.31.92.45157", + "templateHash": "17619606927129129347" } }, "parameters": { @@ -1696,8 +1706,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "11616529434755756375" + "version": "0.31.92.45157", + "templateHash": "5311912111839888762" } }, "parameters": { @@ -1756,8 +1766,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10109660736443081073" + "version": "0.31.92.45157", + "templateHash": "9371138343009436350" } }, "parameters": { @@ -1897,8 +1907,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "11238215245345532485" + "version": "0.31.92.45157", + "templateHash": "6531207997218247037" } }, "parameters": { @@ -2074,8 +2084,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "246030726359755329" + "version": "0.31.92.45157", + "templateHash": "17606032368841347839" } }, "parameters": { @@ -2342,8 +2352,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "6722359274420487391" + "version": "0.31.92.45157", + "templateHash": "3681943409502537301" } }, "parameters": { @@ -2421,8 +2431,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "6722359274420487391" + "version": "0.31.92.45157", + "templateHash": "3681943409502537301" } }, "parameters": { @@ -2502,8 +2512,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "12652296496577802490" + "version": "0.31.92.45157", + "templateHash": "18425321023142226965" } }, "parameters": { @@ -2603,8 +2613,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17592952825859536181" + "version": "0.31.92.45157", + "templateHash": "15002544166394392504" } }, "parameters": { @@ -2672,8 +2682,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7007835755326231171" + "version": "0.31.92.45157", + "templateHash": "16817025486402215719" } }, "parameters": { @@ -2786,8 +2796,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "13031612584115851814" + "version": "0.31.92.45157", + "templateHash": "782319697243698272" } }, "parameters": { @@ -2876,8 +2886,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "13031612584115851814" + "version": "0.31.92.45157", + "templateHash": "782319697243698272" } }, "parameters": { @@ -2990,8 +3000,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "16308694041027148637" + "version": "0.31.92.45157", + "templateHash": "4750034338253477937" } }, "parameters": { @@ -3391,8 +3401,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3535186238457701125" + "version": "0.31.92.45157", + "templateHash": "11818136489056939588" } }, "parameters": { @@ -3509,8 +3519,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "6722359274420487391" + "version": "0.31.92.45157", + "templateHash": "3681943409502537301" } }, "parameters": { @@ -3592,8 +3602,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "12652296496577802490" + "version": "0.31.92.45157", + "templateHash": "18425321023142226965" } }, "parameters": { @@ -3695,8 +3705,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17592952825859536181" + "version": "0.31.92.45157", + "templateHash": "15002544166394392504" } }, "parameters": { @@ -3772,8 +3782,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7007835755326231171" + "version": "0.31.92.45157", + "templateHash": "16817025486402215719" } }, "parameters": { @@ -3920,8 +3930,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "9502319494004310539" + "version": "0.31.92.45157", + "templateHash": "11553909803736438916" } }, "parameters": { @@ -3973,8 +3983,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2015499370398293300" + "version": "0.31.92.45157", + "templateHash": "5574526676512163672" } }, "parameters": { @@ -4052,8 +4062,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3832300165614083813" + "version": "0.31.92.45157", + "templateHash": "10267893616110384815" } }, "parameters": { @@ -4105,8 +4115,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2015499370398293300" + "version": "0.31.92.45157", + "templateHash": "5574526676512163672" } }, "parameters": { @@ -4182,8 +4192,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "1136616241283939780" + "version": "0.31.92.45157", + "templateHash": "17171440191267536743" } }, "parameters": { @@ -4259,8 +4269,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "2757774880390840506" + "version": "0.31.92.45157", + "templateHash": "16077950968688123011" } }, "parameters": { @@ -4425,8 +4435,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "11152278840466293351" + "version": "0.31.92.45157", + "templateHash": "12787329163785242553" } }, "parameters": { @@ -4514,8 +4524,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "1874197755006172394" + "version": "0.31.92.45157", + "templateHash": "11761568940379970751" } }, "parameters": { @@ -4772,8 +4782,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17613382135787640077" + "version": "0.31.92.45157", + "templateHash": "4207798980384159491" } }, "parameters": { @@ -4852,8 +4862,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8372265102618018066" + "version": "0.31.92.45157", + "templateHash": "1966035938992047983" } }, "parameters": { @@ -4947,8 +4957,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "6675708379514380442" + "version": "0.31.92.45157", + "templateHash": "7930493629995578222" } }, "parameters": { @@ -5092,8 +5102,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17664628665088454883" + "version": "0.31.92.45157", + "templateHash": "17900321188332105834" } }, "parameters": { @@ -5178,8 +5188,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "15985115749591574535" + "version": "0.31.92.45157", + "templateHash": "17298378299072098272" } }, "parameters": { @@ -5346,8 +5356,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "4040791601769189381" + "version": "0.31.92.45157", + "templateHash": "12087616562036012055" } }, "parameters": { @@ -5474,8 +5484,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "9321128177359823831" + "version": "0.31.92.45157", + "templateHash": "2081045465267717136" } }, "parameters": { @@ -5702,8 +5712,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "5548269850951921678" + "version": "0.31.92.45157", + "templateHash": "15042212833055960396" } }, "parameters": { @@ -5879,8 +5889,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3093428281213661074" + "version": "0.31.92.45157", + "templateHash": "2760766953842709390" } }, "parameters": { @@ -6039,8 +6049,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "15035921308988452197" + "version": "0.31.92.45157", + "templateHash": "10850741356290813493" } }, "parameters": { @@ -6195,7 +6205,7 @@ }, { "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2021-03-01", + "apiVersion": "2024-03-01", "name": "[format('{0}/{1}', parameters('name'), 'GuestAttestation')]", "location": "[parameters('location')]", "tags": "[union(if(contains(parameters('tags'), 'Microsoft.Compute/virtualMachines'), parameters('tags')['Microsoft.Compute/virtualMachines'], createObject()), parameters('mlzTags'))]", @@ -6361,8 +6371,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "6795024125500853154" + "version": "0.31.92.45157", + "templateHash": "2785700700806650182" } }, "parameters": { @@ -6512,6 +6522,9 @@ "subnetResourceId": { "value": "[parameters('hubSubnetResourceId')]" }, + "supportedClouds": { + "value": "[parameters('supportedClouds')]" + }, "tags": { "value": "[parameters('tags')]" }, @@ -6525,8 +6538,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8382745830446158671" + "version": "0.31.92.45157", + "templateHash": "10838080267928735788" } }, "parameters": { @@ -6560,8 +6573,7 @@ "type": "string" }, "mlzTags": { - "type": "object", - "defaultValue": {} + "type": "object" }, "name": { "type": "string" @@ -6593,9 +6605,11 @@ "subnetResourceId": { "type": "string" }, + "supportedClouds": { + "type": "array" + }, "tags": { - "type": "object", - "defaultValue": {} + "type": "object" }, "version": { "type": "string" @@ -6673,7 +6687,7 @@ }, { "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2021-03-01", + "apiVersion": "2024-03-01", "name": "[format('{0}/{1}', parameters('name'), 'GuestAttestation')]", "location": "[parameters('location')]", "properties": { @@ -6681,6 +6695,7 @@ "type": "GuestAttestation", "typeHandlerVersion": "1.0", "autoUpgradeMinorVersion": true, + "enableAutomaticUpgrade": true, "settings": { "AttestationConfig": { "MaaSettings": { @@ -6703,13 +6718,15 @@ { "type": "Microsoft.Compute/virtualMachines/extensions", "apiVersion": "2021-04-01", - "name": "[format('{0}/{1}', parameters('name'), 'DependencyAgentWindows')]", + "name": "[format('{0}/{1}', parameters('name'), 'AzurePolicyforWindows')]", "location": "[parameters('location')]", + "tags": "[union(if(contains(parameters('tags'), 'Microsoft.Compute/virtualMachines'), parameters('tags')['Microsoft.Compute/virtualMachines'], createObject()), parameters('mlzTags'))]", "properties": { - "publisher": "Microsoft.Azure.Monitoring.DependencyAgent", - "type": "DependencyAgentWindows", - "typeHandlerVersion": "9.5", - "autoUpgradeMinorVersion": true + "publisher": "Microsoft.GuestConfiguration", + "type": "ConfigurationforWindows", + "typeHandlerVersion": "1.0", + "autoUpgradeMinorVersion": true, + "enableAutomaticUpgrade": true }, "dependsOn": [ "[resourceId('Microsoft.Compute/virtualMachines', parameters('name'))]" @@ -6718,11 +6735,29 @@ { "type": "Microsoft.Compute/virtualMachines/extensions", "apiVersion": "2021-04-01", - "name": "[format('{0}/{1}', parameters('name'), 'AzurePolicyforWindows')]", + "name": "[format('{0}/{1}', parameters('name'), 'Microsoft.Azure.NetworkWatcher')]", "location": "[parameters('location')]", + "tags": "[union(if(contains(parameters('tags'), 'Microsoft.Compute/virtualMachines'), parameters('tags')['Microsoft.Compute/virtualMachines'], createObject()), parameters('mlzTags'))]", "properties": { - "publisher": "Microsoft.GuestConfiguration", - "type": "ConfigurationforWindows", + "publisher": "Microsoft.Azure.NetworkWatcher", + "type": "NetworkWatcherAgentWindows", + "typeHandlerVersion": "1.4" + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('name'), 'AzurePolicyforWindows')]", + "[resourceId('Microsoft.Compute/virtualMachines', parameters('name'))]" + ] + }, + { + "condition": "[contains(parameters('supportedClouds'), environment().name)]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-03-01", + "name": "[format('{0}/{1}', parameters('name'), 'AzureMonitorWindowsAgent')]", + "location": "[parameters('location')]", + "tags": "[union(if(contains(parameters('tags'), 'Microsoft.Compute/virtualMachines'), parameters('tags')['Microsoft.Compute/virtualMachines'], createObject()), parameters('mlzTags'))]", + "properties": { + "publisher": "Microsoft.Azure.Monitor", + "type": "AzureMonitorWindowsAgent", "typeHandlerVersion": "1.0", "autoUpgradeMinorVersion": true, "enableAutomaticUpgrade": true @@ -6732,10 +6767,12 @@ ] }, { + "condition": "[not(contains(parameters('supportedClouds'), environment().name))]", "type": "Microsoft.Compute/virtualMachines/extensions", "apiVersion": "2021-04-01", "name": "[format('{0}/{1}', parameters('name'), 'MMAExtension')]", "location": "[parameters('location')]", + "tags": "[union(if(contains(parameters('tags'), 'Microsoft.Compute/virtualMachines'), parameters('tags')['Microsoft.Compute/virtualMachines'], createObject()), parameters('mlzTags'))]", "properties": { "publisher": "Microsoft.EnterpriseCloud.Monitoring", "type": "MicrosoftMonitoringAgent", @@ -6749,20 +6786,25 @@ } }, "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('name'), 'Microsoft.Azure.NetworkWatcher')]", "[resourceId('Microsoft.Compute/virtualMachines', parameters('name'))]" ] }, { + "condition": "[not(contains(parameters('supportedClouds'), environment().name))]", "type": "Microsoft.Compute/virtualMachines/extensions", "apiVersion": "2021-04-01", - "name": "[format('{0}/{1}', parameters('name'), 'Microsoft.Azure.NetworkWatcher')]", + "name": "[format('{0}/{1}', parameters('name'), 'DependencyAgentWindows')]", "location": "[parameters('location')]", + "tags": "[union(if(contains(parameters('tags'), 'Microsoft.Compute/virtualMachines'), parameters('tags')['Microsoft.Compute/virtualMachines'], createObject()), parameters('mlzTags'))]", "properties": { - "publisher": "Microsoft.Azure.NetworkWatcher", - "type": "NetworkWatcherAgentWindows", - "typeHandlerVersion": "1.4" + "publisher": "Microsoft.Azure.Monitoring.DependencyAgent", + "type": "DependencyAgentWindows", + "typeHandlerVersion": "9.5", + "autoUpgradeMinorVersion": true }, "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('name'), 'MMAExtension')]", "[resourceId('Microsoft.Compute/virtualMachines', parameters('name'))]" ] }, @@ -6804,8 +6846,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "6795024125500853154" + "version": "0.31.92.45157", + "templateHash": "2785700700806650182" } }, "parameters": { @@ -6903,6 +6945,9 @@ "deploymentNameSuffix": { "value": "[parameters('deploymentNameSuffix')]" }, + "filesPrivateDnsZoneResourceId": { + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-networking-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.privateDnsZoneResourceIds.value.file]" + }, "keyVaultUri": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-cmk-hub-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.keyVaultUri.value]" }, @@ -6915,6 +6960,9 @@ "mlzTags": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('get-logic-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.mlzTags.value]" }, + "queuesPrivateDnsZoneResourceId": { + "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-networking-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.privateDnsZoneResourceIds.value.queue]" + }, "resourceGroupNames": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('deploy-resource-groups-{0}', parameters('deploymentNameSuffix'))), '2022-09-01').outputs.names.value]" }, @@ -6943,8 +6991,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "15851262372533796124" + "version": "0.31.92.45157", + "templateHash": "15042903713059976655" } }, "parameters": { @@ -6954,6 +7002,9 @@ "deploymentNameSuffix": { "type": "string" }, + "filesPrivateDnsZoneResourceId": { + "type": "string" + }, "keyVaultUri": { "type": "string" }, @@ -6966,6 +7017,9 @@ "mlzTags": { "type": "object" }, + "queuesPrivateDnsZoneResourceId": { + "type": "string" + }, "resourceGroupNames": { "type": "array" }, @@ -7008,6 +7062,9 @@ "blobsPrivateDnsZoneResourceId": { "value": "[parameters('blobsPrivateDnsZoneResourceId')]" }, + "filesPrivateDnsZoneResourceId": { + "value": "[parameters('filesPrivateDnsZoneResourceId')]" + }, "keyVaultUri": { "value": "[parameters('keyVaultUri')]" }, @@ -7017,6 +7074,9 @@ "mlzTags": { "value": "[parameters('mlzTags')]" }, + "queuesPrivateDnsZoneResourceId": { + "value": "[parameters('queuesPrivateDnsZoneResourceId')]" + }, "serviceToken": { "value": "[parameters('serviceToken')]" }, @@ -7048,14 +7108,17 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10231061264498799420" + "version": "0.31.92.45157", + "templateHash": "1625826941635729014" } }, "parameters": { "blobsPrivateDnsZoneResourceId": { "type": "string" }, + "filesPrivateDnsZoneResourceId": { + "type": "string" + }, "keyVaultUri": { "type": "string" }, @@ -7065,6 +7128,9 @@ "mlzTags": { "type": "object" }, + "queuesPrivateDnsZoneResourceId": { + "type": "string" + }, "serviceToken": { "type": "string" }, @@ -7091,9 +7157,27 @@ } }, "variables": { - "zones": [ - "[parameters('blobsPrivateDnsZoneResourceId')]", - "[parameters('tablesPrivateDnsZoneResourceId')]" + "subResources": [ + { + "id": "[parameters('blobsPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountBlobNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountBlobPrivateEndpoint]" + }, + { + "id": "[parameters('filesPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountFileNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountFilePrivateEndpoint]" + }, + { + "id": "[parameters('queuesPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountQueueNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountQueuePrivateEndpoint]" + }, + { + "id": "[parameters('tablesPrivateDnsZoneResourceId')]", + "nic": "[parameters('tier').namingConvention.storageAccountTableNetworkInterface]", + "pe": "[parameters('tier').namingConvention.storageAccountTablePrivateEndpoint]" + } ] }, "resources": [ @@ -7164,22 +7248,22 @@ { "copy": { "name": "privateEndpoints", - "count": "[length(variables('zones'))]" + "count": "[length(variables('subResources'))]" }, "type": "Microsoft.Network/privateEndpoints", "apiVersion": "2023-04-01", - "name": "[replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]))]", + "name": "[variables('subResources')[copyIndex()].pe]", "location": "[parameters('location')]", "tags": "[union(if(contains(parameters('tags'), 'Microsoft.Network/privateEndpoints'), parameters('tags')['Microsoft.Network/privateEndpoints'], createObject()), parameters('mlzTags'))]", "properties": { - "customNetworkInterfaceName": "[replace(parameters('tier').namingConvention.storageAccountNetworkInterface, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]))]", + "customNetworkInterfaceName": "[variables('subResources')[copyIndex()].nic]", "privateLinkServiceConnections": [ { - "name": "[replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]))]", + "name": "[variables('subResources')[copyIndex()].pe]", "properties": { "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]", "groupIds": [ - "[split(split(variables('zones')[copyIndex()], '/')[8], '.')[1]]" + "[split(split(variables('subResources')[copyIndex()].id, '/')[8], '.')[1]]" ] } } @@ -7195,23 +7279,23 @@ { "copy": { "name": "privateDnsZoneGroups", - "count": "[length(variables('zones'))]" + "count": "[length(variables('subResources'))]" }, "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", "apiVersion": "2021-08-01", - "name": "[format('{0}/{1}', replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1])), uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]", + "name": "[format('{0}/{1}', variables('subResources')[copyIndex()].pe, uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]", "properties": { "privateDnsZoneConfigs": [ { "name": "ipconfig1", "properties": { - "privateDnsZoneId": "[variables('zones')[copyIndex()]]" + "privateDnsZoneId": "[variables('subResources')[copyIndex()].id]" } } ] }, "dependsOn": [ - "[resourceId('Microsoft.Network/privateEndpoints', replace(parameters('tier').namingConvention.storageAccountPrivateEndpoint, parameters('serviceToken'), format('{0}-log', split(split(variables('zones')[copyIndex()], '/')[8], '.')[1])))]", + "[resourceId('Microsoft.Network/privateEndpoints', variables('subResources')[copyIndex()].pe)]", "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(replace(parameters('tier').namingConvention.storageAccount, parameters('serviceToken'), 'log'), resourceGroup().id))]" ] } @@ -7308,8 +7392,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "5804308759558531770" + "version": "0.31.92.45157", + "templateHash": "11341470403202647858" } }, "parameters": { @@ -7394,8 +7478,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8389358797157027271" + "version": "0.31.92.45157", + "templateHash": "4687229436121899773" } }, "parameters": { @@ -7481,8 +7565,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8734968617443229139" + "version": "0.31.92.45157", + "templateHash": "6315472047633861096" } }, "parameters": { @@ -7568,8 +7652,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "9851134383266019486" + "version": "0.31.92.45157", + "templateHash": "2073766618455932098" } }, "parameters": { @@ -7650,8 +7734,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "7358671180047253284" + "version": "0.31.92.45157", + "templateHash": "9546260853018527046" } }, "parameters": { @@ -7732,8 +7816,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "17520636026466541947" + "version": "0.31.92.45157", + "templateHash": "16372121177996394493" } }, "parameters": { @@ -7810,8 +7894,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "16750249074235971016" + "version": "0.31.92.45157", + "templateHash": "3821176451773778831" } }, "parameters": { @@ -7891,8 +7975,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3320497816733398371" + "version": "0.31.92.45157", + "templateHash": "1721966359516622278" } }, "parameters": { @@ -7963,8 +8047,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "12248054724275593988" + "version": "0.31.92.45157", + "templateHash": "8510408576984746573" } }, "parameters": { @@ -8049,8 +8133,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "11524143102332195763" + "version": "0.31.92.45157", + "templateHash": "3495057416767671634" } }, "parameters": { @@ -8106,8 +8190,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "10349924076266160595" + "version": "0.31.92.45157", + "templateHash": "14528983897386416653" } }, "parameters": { @@ -8282,8 +8366,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "8372265102618018066" + "version": "0.31.92.45157", + "templateHash": "1966035938992047983" } }, "parameters": { @@ -8380,8 +8464,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3268860743787686" + "version": "0.31.92.45157", + "templateHash": "17309660590425732791" } }, "parameters": { @@ -8444,8 +8528,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.30.23.60470", - "templateHash": "3588347827815061814" + "version": "0.31.92.45157", + "templateHash": "17047820191891552534" } }, "parameters": { diff --git a/src/bicep/modules/linux-virtual-machine.bicep b/src/bicep/modules/linux-virtual-machine.bicep index c8f586480..3acb93d55 100644 --- a/src/bicep/modules/linux-virtual-machine.bicep +++ b/src/bicep/modules/linux-virtual-machine.bicep @@ -118,7 +118,7 @@ resource virtualMachine 'Microsoft.Compute/virtualMachines@2021-04-01' = { } } -resource guestAttestationExtension 'Microsoft.Compute/virtualMachines/extensions@2021-03-01' = { +resource guestAttestationExtension 'Microsoft.Compute/virtualMachines/extensions@2024-03-01' = { parent: virtualMachine name: 'GuestAttestation' location: location diff --git a/src/bicep/modules/naming-convention.bicep b/src/bicep/modules/naming-convention.bicep index 1fc98d9f4..b9b746e22 100644 --- a/src/bicep/modules/naming-convention.bicep +++ b/src/bicep/modules/naming-convention.bicep @@ -12,6 +12,7 @@ param networkShortName string param resourcePrefix string param stampIndex string = '' // Optional: Added to support AVD deployments param tokens object = { + purpose:'purpose_token' resource: 'resource_token' service: 'service_token' } @@ -33,8 +34,8 @@ var resourceAbbreviations = loadJsonContent('../data/resourceAbbreviations.json' */ -var namingConvention = '${toLower(resourcePrefix)}-${empty(stampIndex) ? '' : '${stampIndex}-'}${tokens.resource}-${networkName}-${environmentAbbreviation}-${locationAbbreviation}' -var namingConvention_Service = '${toLower(resourcePrefix)}-${empty(stampIndex) ? '' : '${stampIndex}-'}${tokens.resource}-${tokens.service}-${networkName}-${environmentAbbreviation}-${locationAbbreviation}' +var namingConvention = '${toLower(resourcePrefix)}-${empty(stampIndex) ? '' : '${stampIndex}-'}${tokens.resource}-${networkName}-${locationAbbreviation}-${environmentAbbreviation}' +var namingConvention_Service = '${toLower(resourcePrefix)}-${empty(stampIndex) ? '' : '${stampIndex}-'}${tokens.resource}-${networkName}-${tokens.service}-${locationAbbreviation}-${environmentAbbreviation}' /* @@ -50,7 +51,7 @@ var namingConvention_Service = '${toLower(resourcePrefix)}-${empty(stampIndex) ? var names = { actionGroup: replace(namingConvention, tokens.resource, resourceAbbreviations.actionGroups) - applicationGroup: replace(namingConvention_Service, tokens.resource, resourceAbbreviations.applicationGroups) + applicationGroup: replace(namingConvention, tokens.resource, '${resourceAbbreviations.applicationGroups}-desktop') applicationInsights: replace(namingConvention_Service, tokens.resource, resourceAbbreviations.applicationInsights) appServicePlan: replace(namingConvention_Service, tokens.resource, resourceAbbreviations.appServicePlans) automationAccount: replace(namingConvention, tokens.resource, resourceAbbreviations.automationAccounts) @@ -104,10 +105,18 @@ var names = { recoveryServicesVaultPrivateEndpoint: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.privateEndpoints), tokens.service, resourceAbbreviations.recoveryServicesVaults) resourceGroup: replace(namingConvention_Service, tokens.resource, resourceAbbreviations.resourceGroups) routeTable: replace(namingConvention, tokens.resource, resourceAbbreviations.routeTables) + scalingPlan: replace(namingConvention, tokens.resource, resourceAbbreviations.scalingPlans) + scalingPlanDiagnosticSetting: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.diagnosticSettings), tokens.service, resourceAbbreviations.scalingPlans) storageAccount: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.storageAccounts), networkName, networkShortName) storageAccountDiagnosticSetting: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.diagnosticSettings), tokens.service, '${tokens.service}-${resourceAbbreviations.storageAccounts}') - storageAccountNetworkInterface: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.networkInterfaces), tokens.service, '${tokens.service}-${resourceAbbreviations.storageAccounts}') - storageAccountPrivateEndpoint: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.privateEndpoints), tokens.service, '${tokens.service}-${resourceAbbreviations.storageAccounts}') + storageAccountBlobNetworkInterface: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.networkInterfaces), tokens.service, '${resourceAbbreviations.storageAccounts}-blob') + storageAccountFileNetworkInterface: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.networkInterfaces), tokens.service, '${resourceAbbreviations.storageAccounts}-file') + storageAccountQueueNetworkInterface: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.networkInterfaces), tokens.service, '${resourceAbbreviations.storageAccounts}-queue') + storageAccountTableNetworkInterface: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.networkInterfaces), tokens.service, '${resourceAbbreviations.storageAccounts}-table') + storageAccountBlobPrivateEndpoint: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.privateEndpoints), tokens.service, '${resourceAbbreviations.storageAccounts}-blob') + storageAccountFilePrivateEndpoint: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.privateEndpoints), tokens.service, '${resourceAbbreviations.storageAccounts}-file') + storageAccountQueuePrivateEndpoint: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.privateEndpoints), tokens.service, '${resourceAbbreviations.storageAccounts}-queue') + storageAccountTablePrivateEndpoint: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.privateEndpoints), tokens.service, '${resourceAbbreviations.storageAccounts}-table') subnet: replace(namingConvention, tokens.resource, resourceAbbreviations.subnets) userAssignedIdentity: replace(namingConvention_Service, tokens.resource, resourceAbbreviations.userAssignedIdentities) virtualMachine: replace(replace(replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.virtualMachines), environmentAbbreviation, first(environmentAbbreviation)), networkName, ''), '-', '') @@ -115,14 +124,14 @@ var names = { virtualMachineNetworkInterface: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.networkInterfaces), tokens.service, '${tokens.service}-${resourceAbbreviations.virtualMachines}') virtualNetwork: replace(namingConvention, tokens.resource, resourceAbbreviations.virtualNetworks) virtualNetworkDiagnosticSetting: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.diagnosticSettings), tokens.service, resourceAbbreviations.virtualNetworks) - workspaceFeed: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.workspaces), '-${stampIndex}', '') - workspaceFeedDiagnosticSetting: replace(replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.diagnosticSettings), tokens.service, '${tokens.service}-${resourceAbbreviations.workspaces}'), '-${stampIndex}', '') - workspaceFeedNetworkInterface: replace(replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.networkInterfaces), tokens.service, '${tokens.service}-${resourceAbbreviations.workspaces}'), '-${stampIndex}', '') - workspaceFeedPrivateEndpoint: replace(replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.privateEndpoints), tokens.service, '${tokens.service}-${resourceAbbreviations.workspaces}'), '-${stampIndex}', '') - workspaceGlobal: replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.workspaces), '-${stampIndex}', '') - workspaceGlobalDiagnosticSetting: replace(replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.diagnosticSettings), tokens.service, '${tokens.service}-${resourceAbbreviations.workspaces}'), '-${stampIndex}', '') - workspaceGlobalNetworkInterface: replace(replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.networkInterfaces), tokens.service, '${tokens.service}-${resourceAbbreviations.workspaces}'), '-${stampIndex}', '') - workspaceGlobalPrivateEndpoint: replace(replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.privateEndpoints), tokens.service, '${tokens.service}-${resourceAbbreviations.workspaces}'), '-${stampIndex}', '') + workspaceFeed: replace(replace(namingConvention, tokens.resource, '${resourceAbbreviations.workspaces}-feed'), '-${stampIndex}', '') + workspaceFeedDiagnosticSetting: replace(replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.diagnosticSettings), tokens.service, '${resourceAbbreviations.workspaces}-feed'), '-${stampIndex}', '') + workspaceFeedNetworkInterface: replace(replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.networkInterfaces), tokens.service, '${resourceAbbreviations.workspaces}-feed'), '-${stampIndex}', '') + workspaceFeedPrivateEndpoint: replace(replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.privateEndpoints), tokens.service, '${resourceAbbreviations.workspaces}-feed'), '-${stampIndex}', '') + workspaceGlobal: replace(replace(namingConvention, tokens.resource, '${resourceAbbreviations.workspaces}-global'), '-${stampIndex}', '') + workspaceGlobalDiagnosticSetting: replace(replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.diagnosticSettings), tokens.service, '${resourceAbbreviations.workspaces}-global'), '-${stampIndex}', '') + workspaceGlobalNetworkInterface: replace(replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.networkInterfaces), tokens.service, '${resourceAbbreviations.workspaces}-global'), '-${stampIndex}', '') + workspaceGlobalPrivateEndpoint: replace(replace(replace(namingConvention_Service, tokens.resource, resourceAbbreviations.privateEndpoints), tokens.service, '${resourceAbbreviations.workspaces}-global'), '-${stampIndex}', '') } output locations object = locations diff --git a/src/bicep/modules/remote-access.bicep b/src/bicep/modules/remote-access.bicep index d07f380d8..b2fa98acf 100644 --- a/src/bicep/modules/remote-access.bicep +++ b/src/bicep/modules/remote-access.bicep @@ -123,6 +123,7 @@ module windowsVirtualMachine '../modules/windows-virtual-machine.bicep' = sku: windowsVmSku storageAccountType: windowsVmStorageAccountType subnetResourceId: hubSubnetResourceId + supportedClouds: supportedClouds tags: tags version: windowsVmVersion } diff --git a/src/bicep/modules/storage-account.bicep b/src/bicep/modules/storage-account.bicep index 629122ea0..7e71ac73d 100644 --- a/src/bicep/modules/storage-account.bicep +++ b/src/bicep/modules/storage-account.bicep @@ -4,9 +4,11 @@ Licensed under the MIT License. */ param blobsPrivateDnsZoneResourceId string +param filesPrivateDnsZoneResourceId string param keyVaultUri string param location string param mlzTags object +param queuesPrivateDnsZoneResourceId string param serviceToken string param skuName string param storageEncryptionKeyName string @@ -16,9 +18,27 @@ param tags object param tier object param userAssignedIdentityResourceId string -var zones = [ - blobsPrivateDnsZoneResourceId - tablesPrivateDnsZoneResourceId +var subResources = [ + { + id: blobsPrivateDnsZoneResourceId + nic: tier.namingConvention.storageAccountBlobNetworkInterface + pe: tier.namingConvention.storageAccountBlobPrivateEndpoint + } + { + id: filesPrivateDnsZoneResourceId + nic: tier.namingConvention.storageAccountFileNetworkInterface + pe: tier.namingConvention.storageAccountFilePrivateEndpoint + } + { + id: queuesPrivateDnsZoneResourceId + nic: tier.namingConvention.storageAccountQueueNetworkInterface + pe: tier.namingConvention.storageAccountQueuePrivateEndpoint + } + { + id: tablesPrivateDnsZoneResourceId + nic: tier.namingConvention.storageAccountTableNetworkInterface + pe: tier.namingConvention.storageAccountTablePrivateEndpoint + } ] resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { @@ -84,19 +104,19 @@ resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { } } -resource privateEndpoints 'Microsoft.Network/privateEndpoints@2023-04-01' = [for (zone, i) in zones: { - name: replace(tier.namingConvention.storageAccountPrivateEndpoint, serviceToken, '${split(split(zone, '/')[8], '.')[1]}-log') +resource privateEndpoints 'Microsoft.Network/privateEndpoints@2023-04-01' = [for (resource, i) in subResources: { + name: resource.pe location: location tags: union(contains(tags, 'Microsoft.Network/privateEndpoints') ? tags['Microsoft.Network/privateEndpoints'] : {}, mlzTags) properties: { - customNetworkInterfaceName: replace(tier.namingConvention.storageAccountNetworkInterface, serviceToken, '${split(split(zone, '/')[8], '.')[1]}-log') + customNetworkInterfaceName: resource.nic privateLinkServiceConnections: [ { - name: replace(tier.namingConvention.storageAccountPrivateEndpoint, serviceToken, '${split(split(zone, '/')[8], '.')[1]}-log') + name: resource.pe properties: { privateLinkServiceId: storageAccount.id groupIds: [ - split(split(zone, '/')[8], '.')[1] + split(split(resource.id, '/')[8], '.')[1] ] } } @@ -107,7 +127,7 @@ resource privateEndpoints 'Microsoft.Network/privateEndpoints@2023-04-01' = [for } }] -resource privateDnsZoneGroups 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2021-08-01' = [for (zone, i) in zones: { +resource privateDnsZoneGroups 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2021-08-01' = [for (resource, i) in subResources: { parent: privateEndpoints[i] name: storageAccount.name properties: { @@ -116,7 +136,7 @@ resource privateDnsZoneGroups 'Microsoft.Network/privateEndpoints/privateDnsZone name: 'ipconfig1' properties: { #disable-next-line use-resource-id-functions - privateDnsZoneId: zone + privateDnsZoneId: resource.id } } ] diff --git a/src/bicep/modules/storage.bicep b/src/bicep/modules/storage.bicep index b90645e37..66963b552 100644 --- a/src/bicep/modules/storage.bicep +++ b/src/bicep/modules/storage.bicep @@ -8,10 +8,12 @@ targetScope = 'subscription' param blobsPrivateDnsZoneResourceId string //param deployIdentity bool param deploymentNameSuffix string +param filesPrivateDnsZoneResourceId string param keyVaultUri string param location string param logStorageSkuName string param mlzTags object +param queuesPrivateDnsZoneResourceId string param resourceGroupNames array param serviceToken string param storageEncryptionKeyName string @@ -25,9 +27,11 @@ module storageAccount 'storage-account.bicep' = [for (tier, i) in tiers: { scope: resourceGroup(tier.subscriptionId, resourceGroupNames[i]) params: { blobsPrivateDnsZoneResourceId: blobsPrivateDnsZoneResourceId + filesPrivateDnsZoneResourceId: filesPrivateDnsZoneResourceId keyVaultUri: keyVaultUri location: location mlzTags: mlzTags + queuesPrivateDnsZoneResourceId: queuesPrivateDnsZoneResourceId serviceToken: serviceToken skuName: logStorageSkuName storageEncryptionKeyName: storageEncryptionKeyName @@ -40,5 +44,3 @@ module storageAccount 'storage-account.bicep' = [for (tier, i) in tiers: { }] output storageAccountResourceIds array = [for (tier, i) in tiers: storageAccount[i].outputs.id] - - diff --git a/src/bicep/modules/windows-virtual-machine.bicep b/src/bicep/modules/windows-virtual-machine.bicep index 21769be0d..fd386f283 100644 --- a/src/bicep/modules/windows-virtual-machine.bicep +++ b/src/bicep/modules/windows-virtual-machine.bicep @@ -14,7 +14,7 @@ param diskName string param hybridUseBenefit bool param location string param logAnalyticsWorkspaceId string -param mlzTags object = {} +param mlzTags object param name string param networkInterfaceName string param networkSecurityGroupResourceId string @@ -25,7 +25,8 @@ param size string param sku string param storageAccountType string param subnetResourceId string -param tags object = {} +param supportedClouds array +param tags object param version string module networkInterface '../modules/network-interface.bicep' = { @@ -106,7 +107,7 @@ resource virtualMachine 'Microsoft.Compute/virtualMachines@2021-04-01' = { } } -resource guestAttestationExtension 'Microsoft.Compute/virtualMachines/extensions@2021-03-01' = { +resource extension_GuestAttestation 'Microsoft.Compute/virtualMachines/extensions@2024-03-01' = { parent: virtualMachine name: 'GuestAttestation' location: location @@ -115,6 +116,7 @@ resource guestAttestationExtension 'Microsoft.Compute/virtualMachines/extensions type: 'GuestAttestation' typeHandlerVersion: '1.0' autoUpgradeMinorVersion: true + enableAutomaticUpgrade: true settings: { AttestationConfig: { MaaSettings: { @@ -132,35 +134,54 @@ resource guestAttestationExtension 'Microsoft.Compute/virtualMachines/extensions } } -resource dependencyAgent 'Microsoft.Compute/virtualMachines/extensions@2021-04-01' = { +resource extension_GuestConfiguration 'Microsoft.Compute/virtualMachines/extensions@2021-04-01' = { parent: virtualMachine - name: 'DependencyAgentWindows' + name: 'AzurePolicyforWindows' location: location + tags: union(contains(tags, 'Microsoft.Compute/virtualMachines') ? tags['Microsoft.Compute/virtualMachines'] : {}, mlzTags) properties: { - publisher: 'Microsoft.Azure.Monitoring.DependencyAgent' - type: 'DependencyAgentWindows' - typeHandlerVersion: '9.5' + publisher: 'Microsoft.GuestConfiguration' + type: 'ConfigurationforWindows' + typeHandlerVersion: '1.0' autoUpgradeMinorVersion: true + enableAutomaticUpgrade: true } } -resource policyExtension 'Microsoft.Compute/virtualMachines/extensions@2021-04-01' = { +resource extension_NetworkWatcher 'Microsoft.Compute/virtualMachines/extensions@2021-04-01' = { parent: virtualMachine - name: 'AzurePolicyforWindows' + name: 'Microsoft.Azure.NetworkWatcher' location: location + tags: union(contains(tags, 'Microsoft.Compute/virtualMachines') ? tags['Microsoft.Compute/virtualMachines'] : {}, mlzTags) properties: { - publisher: 'Microsoft.GuestConfiguration' - type: 'ConfigurationforWindows' + publisher: 'Microsoft.Azure.NetworkWatcher' + type: 'NetworkWatcherAgentWindows' + typeHandlerVersion: '1.4' + } + dependsOn: [ + extension_GuestConfiguration + ] +} + +resource extension_AzureMonitorWindowsAgent 'Microsoft.Compute/virtualMachines/extensions@2023-03-01' = if (contains(supportedClouds, environment().name)) { + parent: virtualMachine + name: 'AzureMonitorWindowsAgent' + location: location + tags: union(contains(tags, 'Microsoft.Compute/virtualMachines') ? tags['Microsoft.Compute/virtualMachines'] : {}, mlzTags) + properties: { + publisher: 'Microsoft.Azure.Monitor' + type: 'AzureMonitorWindowsAgent' typeHandlerVersion: '1.0' autoUpgradeMinorVersion: true enableAutomaticUpgrade: true } } -resource mmaExtension 'Microsoft.Compute/virtualMachines/extensions@2021-04-01' = { +resource extension_MicrosoftMonitoringAgent 'Microsoft.Compute/virtualMachines/extensions@2021-04-01' = if (!contains(supportedClouds, environment().name)) { parent: virtualMachine name: 'MMAExtension' location: location + tags: union(contains(tags, 'Microsoft.Compute/virtualMachines') ? tags['Microsoft.Compute/virtualMachines'] : {}, mlzTags) properties: { publisher: 'Microsoft.EnterpriseCloud.Monitoring' type: 'MicrosoftMonitoringAgent' @@ -173,15 +194,23 @@ resource mmaExtension 'Microsoft.Compute/virtualMachines/extensions@2021-04-01' workspaceKey: listKeys(logAnalyticsWorkspaceId , '2015-11-01-preview').primarySharedKey } } + dependsOn: [ + extension_NetworkWatcher + ] } -resource networkWatcher 'Microsoft.Compute/virtualMachines/extensions@2021-04-01' = { +resource extension_DependencyAgent 'Microsoft.Compute/virtualMachines/extensions@2021-04-01' = if (!contains(supportedClouds, environment().name)) { parent: virtualMachine - name: 'Microsoft.Azure.NetworkWatcher' + name: 'DependencyAgentWindows' location: location + tags: union(contains(tags, 'Microsoft.Compute/virtualMachines') ? tags['Microsoft.Compute/virtualMachines'] : {}, mlzTags) properties: { - publisher: 'Microsoft.Azure.NetworkWatcher' - type: 'NetworkWatcherAgentWindows' - typeHandlerVersion: '1.4' + publisher: 'Microsoft.Azure.Monitoring.DependencyAgent' + type: 'DependencyAgentWindows' + typeHandlerVersion: '9.5' + autoUpgradeMinorVersion: true } + dependsOn: [ + extension_MicrosoftMonitoringAgent + ] }