diff --git a/src/bicep/add-ons/azureVirtualDesktop/modules/controlPlane/workspace.bicep b/src/bicep/add-ons/azureVirtualDesktop/modules/controlPlane/workspace.bicep index 628485430..8c4337bd1 100644 --- a/src/bicep/add-ons/azureVirtualDesktop/modules/controlPlane/workspace.bicep +++ b/src/bicep/add-ons/azureVirtualDesktop/modules/controlPlane/workspace.bicep @@ -44,7 +44,7 @@ resource workspace 'Microsoft.DesktopVirtualization/workspaces@2023-09-05' = if tags: {} properties: { applicationGroupReferences: applicationGroupReferences - friendlyName: '${friendlyName} (${locationControlPlane})' + friendlyName: empty(friendlyName) ? hostPoolName : '${friendlyName} (${locationControlPlane})' publicNetworkAccess: workspacePublicNetworkAccess } } diff --git a/src/bicep/add-ons/azureVirtualDesktop/modules/management/management.bicep b/src/bicep/add-ons/azureVirtualDesktop/modules/management/management.bicep index 27cde8fdc..0c6a4eaa3 100644 --- a/src/bicep/add-ons/azureVirtualDesktop/modules/management/management.bicep +++ b/src/bicep/add-ons/azureVirtualDesktop/modules/management/management.bicep @@ -23,7 +23,7 @@ param domainJoinPassword string param domainJoinUserPrincipalName string param domainName string param enableMonitoring bool -param environmentShortName string +param environmentAbbreviation string param fslogix bool param fslogixStorageService string param hostPoolName string @@ -189,7 +189,7 @@ module customerManagedKeys 'customerManagedKeys.bicep' = { name: 'CustomerManagedKeys_${timestamp}' scope: resourceGroup(resourceGroupManagement) params: { - environment: environmentShortName + environment: environmentAbbreviation keyVaultName: keyVaultName keyVaultNetworkInterfaceName: keyVaultNetworkInterfaceName keyVaultPrivateDnsZoneResourceId: keyVaultPrivateDnsZoneResourceId diff --git a/src/bicep/add-ons/azureVirtualDesktop/modules/resourceNames.bicep b/src/bicep/add-ons/azureVirtualDesktop/modules/resourceNames.bicep index 66ab323d8..fbce26a8b 100644 --- a/src/bicep/add-ons/azureVirtualDesktop/modules/resourceNames.bicep +++ b/src/bicep/add-ons/azureVirtualDesktop/modules/resourceNames.bicep @@ -1,6 +1,6 @@ targetScope = 'subscription' -param environmentShortName string +param environmentAbbreviation string param identifier string param locationControlPlane string param locationVirtualMachines string @@ -13,9 +13,9 @@ var resourceAbbreviation = 'resourceAbbreviation' var serviceName = 'serviceName' var networkName = 'avd' var locationAbbreviation = 'locationAbbreviation' -var namingConvention = '${identifier}-${stampIndex}-${resourceAbbreviation}-${serviceName}-${networkName}-${environmentShortName}-${locationAbbreviation}' -var namingConvention_Global = '${resourceAbbreviation}-${serviceName}-${networkName}-${environmentShortName}-${locationAbbreviation}' -var namingConvention_Shared = '${identifier}-${resourceAbbreviation}-${serviceName}-${networkName}-${environmentShortName}-${locationAbbreviation}' +var namingConvention = '${identifier}-${stampIndex}-${resourceAbbreviation}-${serviceName}-${networkName}-${environmentAbbreviation}-${locationAbbreviation}' +var namingConvention_Global = '${resourceAbbreviation}-${serviceName}-${networkName}-${environmentAbbreviation}-${locationAbbreviation}' +var namingConvention_Shared = '${identifier}-${resourceAbbreviation}-${serviceName}-${networkName}-${environmentAbbreviation}-${locationAbbreviation}' // SUPPORTING DATA var cloudEndpointSuffix = replace(replace(environment().resourceManager, 'https://management.', ''), '/', '') @@ -111,11 +111,11 @@ var routeTables = [ replace(replace(replace(namingConvention, resourceAbbreviation, resourceAbbreviations.routeTables), '-${serviceName}', ''), locationAbbreviation, locations[locationControlPlane].abbreviation) replace(replace(replace(namingConvention, resourceAbbreviation, resourceAbbreviations.routeTables), '-${serviceName}', ''), locationAbbreviation, locations[locationVirtualMachines].abbreviation) ] -var storageAccountNamePrefix = replace(replace(replace(replace(replace(namingConvention, resourceAbbreviation, resourceAbbreviations.storageAccounts), '-${serviceName}', ''), locationAbbreviation, locations[locationVirtualMachines].abbreviation), environmentShortName, first(environmentShortName)), '-', '') -var storageAccountNetworkInterfaceNamePrefix = replace(replace(replace(replace(namingConvention, resourceAbbreviation, resourceAbbreviations.networkInterfaces), serviceName, resourceAbbreviations.storageAccounts), locationAbbreviation, locations[locationVirtualMachines].abbreviation), environmentShortName, first(environmentShortName)) -var storageAccountPrivateEndpointNamePrefix = replace(replace(replace(replace(namingConvention, resourceAbbreviation, resourceAbbreviations.privateEndpoints), serviceName, resourceAbbreviations.storageAccounts), locationAbbreviation, locations[locationVirtualMachines].abbreviation), environmentShortName, first(environmentShortName)) +var storageAccountNamePrefix = replace(replace(replace(replace(replace(namingConvention, resourceAbbreviation, resourceAbbreviations.storageAccounts), '-${serviceName}', ''), locationAbbreviation, locations[locationVirtualMachines].abbreviation), environmentAbbreviation, first(environmentAbbreviation)), '-', '') +var storageAccountNetworkInterfaceNamePrefix = replace(replace(replace(replace(namingConvention, resourceAbbreviation, resourceAbbreviations.networkInterfaces), serviceName, resourceAbbreviations.storageAccounts), locationAbbreviation, locations[locationVirtualMachines].abbreviation), environmentAbbreviation, first(environmentAbbreviation)) +var storageAccountPrivateEndpointNamePrefix = replace(replace(replace(replace(namingConvention, resourceAbbreviation, resourceAbbreviations.privateEndpoints), serviceName, resourceAbbreviations.storageAccounts), locationAbbreviation, locations[locationVirtualMachines].abbreviation), environmentAbbreviation, first(environmentAbbreviation)) var userAssignedIdentityNamePrefix = replace(replace(namingConvention, resourceAbbreviation, resourceAbbreviations.userAssignedIdentities), locationAbbreviation, locations[locationVirtualMachines].abbreviation) -var virtualMachineNamePrefix = replace(replace(replace(replace(namingConvention, resourceAbbreviation, resourceAbbreviations.virtualMachines), locationAbbreviation, locations[locationVirtualMachines].abbreviation), environmentShortName, first(environmentShortName)), '-', '') +var virtualMachineNamePrefix = replace(replace(replace(replace(namingConvention, resourceAbbreviation, resourceAbbreviations.virtualMachines), locationAbbreviation, locations[locationVirtualMachines].abbreviation), environmentAbbreviation, first(environmentAbbreviation)), '-', '') var virtualNetworkNames = [ replace(replace(replace(namingConvention, resourceAbbreviation, resourceAbbreviations.virtualNetworks), '-${serviceName}', ''), locationAbbreviation, locations[locationControlPlane].abbreviation) replace(replace(replace(namingConvention, resourceAbbreviation, resourceAbbreviations.virtualNetworks), '-${serviceName}', ''), locationAbbreviation, locations[locationVirtualMachines].abbreviation) diff --git a/src/bicep/add-ons/azureVirtualDesktop/solution.bicep b/src/bicep/add-ons/azureVirtualDesktop/solution.bicep index b636486a9..4db2db7d6 100644 --- a/src/bicep/add-ons/azureVirtualDesktop/solution.bicep +++ b/src/bicep/add-ons/azureVirtualDesktop/solution.bicep @@ -39,7 +39,7 @@ param azureNetAppFilesSubnetAddressPrefix string = '' param azurePowerShellModuleMsiName string @description('The RDP properties to add or remove RDP functionality on the AVD host pool. The string must end with a semi-colon. Settings reference: https://learn.microsoft.com/windows-server/remote/remote-desktop-services/clients/rdp-files') -param customRdpProperty string = 'audiocapturemode:i:1;camerastoredirect:s:*;use multimon:i:0;drivestoredirect:s:;' +param customRdpProperty string = '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;' @description('The friendly name for the Desktop application in the desktop application group.') param desktopFriendlyName string = '' @@ -73,8 +73,8 @@ param drainMode bool = false 'prod' // Production 'test' // Test ]) -@description('The short name for the target environment.') -param environmentShortName string = 'dev' +@description('The abbreviation for the target environment.') +param environmentAbbreviation string = 'dev' @description('The file share size(s) in GB for the Fslogix storage solution.') param fslogixShareSizeInGB int = 100 @@ -164,9 +164,6 @@ param logAnalyticsWorkspaceRetention int = 30 @description('The SKU for the Log Analytics Workspace to setup the AVD monitoring solution') param logAnalyticsWorkspaceSku string = 'PerGB2018' -@description('The maximum number of sessions per AVD session host.') -param maxSessionLimit int - @description('Deploys the required monitoring resources to enable AVD Insights and monitor features in the automation account.') param monitoring bool = true @@ -234,9 +231,15 @@ param tags object = {} @description('DO NOT MODIFY THIS VALUE! The timestamp is needed to differentiate deployments for certain Azure resources and must be set using a parameter.') param timestamp string = utcNow('yyyyMMddhhmmss') +@description('The number of users per core is used to determine the maximum number of users per session host.') +param usersPerCore int = 1 + @description('The validation environment setting on the AVD host pool determines whether the hostpool should receive AVD preview features for testing.') param validationEnvironment bool = false +@description('The number of virtual CPUs per virtual machine for the selected virtual machine size.') +param virtualMachineVirtualCpuCount int + @allowed([ 'AzureMonitorAgent' 'LogAnalyticsAgent' @@ -281,7 +284,7 @@ var resourceGroupsCount = 4 + length(deploymentLocations) + (fslogixStorageServi module resourceNames 'modules/resourceNames.bicep' = { name: 'ResourceNames_${timestamp}' params: { - environmentShortName: environmentShortName + environmentAbbreviation: environmentAbbreviation identifier: identifier locationControlPlane: locationControlPlane locationVirtualMachines: locationVirtualMachines @@ -400,7 +403,7 @@ module management 'modules/management/management.bicep' = { domainJoinUserPrincipalName: domainJoinUserPrincipalName domainName: domainName enableMonitoring: monitoring - environmentShortName: environmentShortName + environmentAbbreviation: environmentAbbreviation fslogix: logic.outputs.fslogix fslogixStorageService: fslogixStorageService hostPoolName: resourceNames.outputs.hostPoolName @@ -490,7 +493,7 @@ module controlPlane 'modules/controlPlane/controlPlane.bicep' = { locationVirtualMachines: locationVirtualMachines logAnalyticsWorkspaceResourceId: monitoring ? management.outputs.logAnalyticsWorkspaceResourceId : '' managementVirtualMachineName: management.outputs.virtualMachineName - maxSessionLimit: maxSessionLimit + maxSessionLimit: usersPerCore * virtualMachineVirtualCpuCount monitoring: monitoring resourceGroupControlPlane: resourceNames.outputs.resourceGroupControlPlane resourceGroupFeedWorkspace: resourceNames.outputs.resourceGroupFeedWorkspace diff --git a/src/bicep/add-ons/azureVirtualDesktop/solution.json b/src/bicep/add-ons/azureVirtualDesktop/solution.json index 3117b0da3..cf7888b85 100644 --- a/src/bicep/add-ons/azureVirtualDesktop/solution.json +++ b/src/bicep/add-ons/azureVirtualDesktop/solution.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.25.3.34343", - "templateHash": "18030163526956675516" + "templateHash": "799041900324480080" } }, "parameters": { @@ -78,7 +78,7 @@ }, "customRdpProperty": { "type": "string", - "defaultValue": "audiocapturemode:i:1;camerastoredirect:s:*;use multimon:i:0;drivestoredirect:s:;", + "defaultValue": "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;", "metadata": { "description": "The RDP properties to add or remove RDP functionality on the AVD host pool. The string must end with a semi-colon. Settings reference: https://learn.microsoft.com/windows-server/remote/remote-desktop-services/clients/rdp-files" } @@ -137,7 +137,7 @@ "description": "The drain mode option enables drain mode for the sessions hosts in this deployment to prevent users from accessing the hosts until they have been validated." } }, - "environmentShortName": { + "environmentAbbreviation": { "type": "string", "defaultValue": "dev", "allowedValues": [ @@ -146,7 +146,7 @@ "test" ], "metadata": { - "description": "The short name for the target environment." + "description": "The abbreviation for the target environment." } }, "fslogixShareSizeInGB": { @@ -301,12 +301,6 @@ "description": "The SKU for the Log Analytics Workspace to setup the AVD monitoring solution" } }, - "maxSessionLimit": { - "type": "int", - "metadata": { - "description": "The maximum number of sessions per AVD session host." - } - }, "monitoring": { "type": "bool", "defaultValue": true, @@ -448,6 +442,13 @@ "description": "DO NOT MODIFY THIS VALUE! The timestamp is needed to differentiate deployments for certain Azure resources and must be set using a parameter." } }, + "usersPerCore": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "The number of users per core is used to determine the maximum number of users per session host." + } + }, "validationEnvironment": { "type": "bool", "defaultValue": false, @@ -455,6 +456,12 @@ "description": "The validation environment setting on the AVD host pool determines whether the hostpool should receive AVD preview features for testing." } }, + "virtualMachineVirtualCpuCount": { + "type": "int", + "metadata": { + "description": "The number of virtual CPUs per virtual machine for the selected virtual machine size." + } + }, "virtualMachineMonitoringAgent": { "type": "string", "defaultValue": "LogAnalyticsAgent", @@ -528,8 +535,8 @@ }, "mode": "Incremental", "parameters": { - "environmentShortName": { - "value": "[parameters('environmentShortName')]" + "environmentAbbreviation": { + "value": "[parameters('environmentAbbreviation')]" }, "identifier": { "value": "[parameters('identifier')]" @@ -551,11 +558,11 @@ "_generator": { "name": "bicep", "version": "0.25.3.34343", - "templateHash": "6308965299810592998" + "templateHash": "2478040738242739476" } }, "parameters": { - "environmentShortName": { + "environmentAbbreviation": { "type": "string" }, "identifier": { @@ -1000,9 +1007,9 @@ "serviceName": "serviceName", "networkName": "avd", "locationAbbreviation": "locationAbbreviation", - "namingConvention": "[format('{0}-{1}-{2}-{3}-{4}-{5}-{6}', parameters('identifier'), parameters('stampIndex'), variables('resourceAbbreviation'), variables('serviceName'), variables('networkName'), parameters('environmentShortName'), variables('locationAbbreviation'))]", - "namingConvention_Global": "[format('{0}-{1}-{2}-{3}-{4}', variables('resourceAbbreviation'), variables('serviceName'), variables('networkName'), parameters('environmentShortName'), variables('locationAbbreviation'))]", - "namingConvention_Shared": "[format('{0}-{1}-{2}-{3}-{4}-{5}', parameters('identifier'), variables('resourceAbbreviation'), variables('serviceName'), variables('networkName'), parameters('environmentShortName'), variables('locationAbbreviation'))]", + "namingConvention": "[format('{0}-{1}-{2}-{3}-{4}-{5}-{6}', parameters('identifier'), parameters('stampIndex'), variables('resourceAbbreviation'), variables('serviceName'), variables('networkName'), parameters('environmentAbbreviation'), variables('locationAbbreviation'))]", + "namingConvention_Global": "[format('{0}-{1}-{2}-{3}-{4}', variables('resourceAbbreviation'), variables('serviceName'), variables('networkName'), parameters('environmentAbbreviation'), variables('locationAbbreviation'))]", + "namingConvention_Shared": "[format('{0}-{1}-{2}-{3}-{4}-{5}', parameters('identifier'), variables('resourceAbbreviation'), variables('serviceName'), variables('networkName'), parameters('environmentAbbreviation'), variables('locationAbbreviation'))]", "cloudEndpointSuffix": "[replace(replace(environment().resourceManager, 'https://management.', ''), '/', '')]", "privateDnsZoneSuffixes_AzureAutomation": { "AzureCloud": "net", @@ -1093,11 +1100,11 @@ "[replace(replace(replace(variables('namingConvention'), variables('resourceAbbreviation'), variables('resourceAbbreviations').routeTables), format('-{0}', variables('serviceName')), ''), variables('locationAbbreviation'), variables('locations')[parameters('locationControlPlane')].abbreviation)]", "[replace(replace(replace(variables('namingConvention'), variables('resourceAbbreviation'), variables('resourceAbbreviations').routeTables), format('-{0}', variables('serviceName')), ''), variables('locationAbbreviation'), variables('locations')[parameters('locationVirtualMachines')].abbreviation)]" ], - "storageAccountNamePrefix": "[replace(replace(replace(replace(replace(variables('namingConvention'), variables('resourceAbbreviation'), variables('resourceAbbreviations').storageAccounts), format('-{0}', variables('serviceName')), ''), variables('locationAbbreviation'), variables('locations')[parameters('locationVirtualMachines')].abbreviation), parameters('environmentShortName'), first(parameters('environmentShortName'))), '-', '')]", - "storageAccountNetworkInterfaceNamePrefix": "[replace(replace(replace(replace(variables('namingConvention'), variables('resourceAbbreviation'), variables('resourceAbbreviations').networkInterfaces), variables('serviceName'), variables('resourceAbbreviations').storageAccounts), variables('locationAbbreviation'), variables('locations')[parameters('locationVirtualMachines')].abbreviation), parameters('environmentShortName'), first(parameters('environmentShortName')))]", - "storageAccountPrivateEndpointNamePrefix": "[replace(replace(replace(replace(variables('namingConvention'), variables('resourceAbbreviation'), variables('resourceAbbreviations').privateEndpoints), variables('serviceName'), variables('resourceAbbreviations').storageAccounts), variables('locationAbbreviation'), variables('locations')[parameters('locationVirtualMachines')].abbreviation), parameters('environmentShortName'), first(parameters('environmentShortName')))]", + "storageAccountNamePrefix": "[replace(replace(replace(replace(replace(variables('namingConvention'), variables('resourceAbbreviation'), variables('resourceAbbreviations').storageAccounts), format('-{0}', variables('serviceName')), ''), variables('locationAbbreviation'), variables('locations')[parameters('locationVirtualMachines')].abbreviation), parameters('environmentAbbreviation'), first(parameters('environmentAbbreviation'))), '-', '')]", + "storageAccountNetworkInterfaceNamePrefix": "[replace(replace(replace(replace(variables('namingConvention'), variables('resourceAbbreviation'), variables('resourceAbbreviations').networkInterfaces), variables('serviceName'), variables('resourceAbbreviations').storageAccounts), variables('locationAbbreviation'), variables('locations')[parameters('locationVirtualMachines')].abbreviation), parameters('environmentAbbreviation'), first(parameters('environmentAbbreviation')))]", + "storageAccountPrivateEndpointNamePrefix": "[replace(replace(replace(replace(variables('namingConvention'), variables('resourceAbbreviation'), variables('resourceAbbreviations').privateEndpoints), variables('serviceName'), variables('resourceAbbreviations').storageAccounts), variables('locationAbbreviation'), variables('locations')[parameters('locationVirtualMachines')].abbreviation), parameters('environmentAbbreviation'), first(parameters('environmentAbbreviation')))]", "userAssignedIdentityNamePrefix": "[replace(replace(variables('namingConvention'), variables('resourceAbbreviation'), variables('resourceAbbreviations').userAssignedIdentities), variables('locationAbbreviation'), variables('locations')[parameters('locationVirtualMachines')].abbreviation)]", - "virtualMachineNamePrefix": "[replace(replace(replace(replace(variables('namingConvention'), variables('resourceAbbreviation'), variables('resourceAbbreviations').virtualMachines), variables('locationAbbreviation'), variables('locations')[parameters('locationVirtualMachines')].abbreviation), parameters('environmentShortName'), first(parameters('environmentShortName'))), '-', '')]", + "virtualMachineNamePrefix": "[replace(replace(replace(replace(variables('namingConvention'), variables('resourceAbbreviation'), variables('resourceAbbreviations').virtualMachines), variables('locationAbbreviation'), variables('locations')[parameters('locationVirtualMachines')].abbreviation), parameters('environmentAbbreviation'), first(parameters('environmentAbbreviation'))), '-', '')]", "virtualNetworkNames": [ "[replace(replace(replace(variables('namingConvention'), variables('resourceAbbreviation'), variables('resourceAbbreviations').virtualNetworks), format('-{0}', variables('serviceName')), ''), variables('locationAbbreviation'), variables('locations')[parameters('locationControlPlane')].abbreviation)]", "[replace(replace(replace(variables('namingConvention'), variables('resourceAbbreviation'), variables('resourceAbbreviations').virtualNetworks), format('-{0}', variables('serviceName')), ''), variables('locationAbbreviation'), variables('locations')[parameters('locationVirtualMachines')].abbreviation)]" @@ -2890,8 +2897,8 @@ "enableMonitoring": { "value": "[parameters('monitoring')]" }, - "environmentShortName": { - "value": "[parameters('environmentShortName')]" + "environmentAbbreviation": { + "value": "[parameters('environmentAbbreviation')]" }, "fslogix": { "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('Logic_{0}', parameters('timestamp'))), '2022-09-01').outputs.fslogix.value]" @@ -3028,7 +3035,7 @@ "_generator": { "name": "bicep", "version": "0.25.3.34343", - "templateHash": "11487646520353594492" + "templateHash": "7693085944855554195" } }, "parameters": { @@ -3095,7 +3102,7 @@ "enableMonitoring": { "type": "bool" }, - "environmentShortName": { + "environmentAbbreviation": { "type": "string" }, "fslogix": { @@ -3760,7 +3767,7 @@ "mode": "Incremental", "parameters": { "environment": { - "value": "[parameters('environmentShortName')]" + "value": "[parameters('environmentAbbreviation')]" }, "keyVaultName": { "value": "[parameters('keyVaultName')]" @@ -6498,7 +6505,7 @@ "value": "[reference(subscriptionResourceId('Microsoft.Resources/deployments', format('Management_{0}', parameters('timestamp'))), '2022-09-01').outputs.virtualMachineName.value]" }, "maxSessionLimit": { - "value": "[parameters('maxSessionLimit')]" + "value": "[mul(parameters('usersPerCore'), parameters('virtualMachineVirtualCpuCount'))]" }, "monitoring": { "value": "[parameters('monitoring')]" @@ -6559,7 +6566,7 @@ "_generator": { "name": "bicep", "version": "0.25.3.34343", - "templateHash": "13352333195132125801" + "templateHash": "14356988404339835709" } }, "parameters": { @@ -7267,7 +7274,7 @@ "_generator": { "name": "bicep", "version": "0.25.3.34343", - "templateHash": "6899448606452743793" + "templateHash": "2209985937950190486" } }, "parameters": { @@ -7345,7 +7352,7 @@ "tags": {}, "properties": { "applicationGroupReferences": "[parameters('applicationGroupReferences')]", - "friendlyName": "[format('{0} ({1})', parameters('friendlyName'), parameters('locationControlPlane'))]", + "friendlyName": "[if(empty(parameters('friendlyName')), parameters('hostPoolName'), format('{0} ({1})', parameters('friendlyName'), parameters('locationControlPlane')))]", "publicNetworkAccess": "[parameters('workspacePublicNetworkAccess')]" } }, diff --git a/src/bicep/add-ons/azureVirtualDesktop/uiDefinition.json b/src/bicep/add-ons/azureVirtualDesktop/uiDefinition.json index c356f3547..e14dbedbf 100644 --- a/src/bicep/add-ons/azureVirtualDesktop/uiDefinition.json +++ b/src/bicep/add-ons/azureVirtualDesktop/uiDefinition.json @@ -1,1612 +1,1660 @@ { - "$schema": "https://schema.management.azure.com/schemas/2021-09-09/uiFormDefinition.schema.json", - "view": { - "kind": "Form", - "properties": { - "title": "Mission Landing Zone Add-On: Azure Virtual Desktop", - "steps": [ - { - "name": "basics", - "label": "Basics", - "elements": [ - { - "name": "description", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "The Azure Virtual Desktop (AVD) add-on reduces the complexity in deploying AVD with SCCA and zero trust compliance. Click on the link below to learn more about the solution.", - "link": { - "label": "https://github.com/Azure/missionlz/blob/main/src/bicep/add-ons/azureVirtualDesktop/README.md", - "uri": "https://github.com/Azure/missionlz/blob/main/src/bicep/add-ons/azureVirtualDesktop/README.md" - } - } - }, - { - "name": "scope", - "type": "Microsoft.Common.ResourceScope", - "instanceDetailsLabel": "", - "location": { - "resourceTypes": [] - } - }, - { - "name": "hub", - "label": "Hub Resources", - "type": "Microsoft.Common.Section", - "elements": [ - { - "name": "api", - "type": "Microsoft.Solutions.ArmApiControl", - "request": { - "method": "GET", - "path": "subscriptions?api-version=2020-01-01" - } - }, - { - "name": "subscription", - "label": "Subscription", - "type": "Microsoft.Common.DropDown", - "defaultValue": "[first(map(steps('basics').hub.api.value, (item) => item.displayName))]", - "toolTip": "Select the subscription for your Mission Landing Zone Hub network, firewall, and remote access resources.", - "filter": true, - "constraints": { - "allowedValues": "[map(steps('basics').hub.api.value, (item) => parse(concat('{\"label\":\"', item.displayName, '\",\"value\":\"', item.id, '\",\"description\":\"', 'ID: ', item.subscriptionId, '\"}')))]", - "required": true - } - }, - { - "name": "virtualNetworksApi", - "type": "Microsoft.Solutions.ArmApiControl", - "request": { - "method": "GET", - "path": "[concat(steps('basics').hub.subscription, '/providers/Microsoft.Network/virtualNetworks?api-version=2023-05-01')]" - } - }, - { - "name": "virtualNetwork", - "type": "Microsoft.Common.DropDown", - "visible": true, - "label": "Virtual network", - "defaultValue": "[filter(map(steps('basics').hub.virtualNetworksApi.value, (item) => item.name), (item) => contains(item, 'hub'))]", - "filter": true, - "toolTip": "Select the existing Hub virtual network.", - "constraints": { - "required": true, - "allowedValues": "[map(steps('basics').hub.virtualNetworksApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.id, '\"}')))]" - } - }, - { - "name": "azureFirewallsApi", - "type": "Microsoft.Solutions.ArmApiControl", - "request": { - "method": "GET", - "path": "[concat(steps('basics').hub.subscription, '/providers/Microsoft.Network/azureFirewalls?api-version=2023-05-01')]" - } - }, - { - "name": "azureFirewall", - "type": "Microsoft.Common.DropDown", - "visible": true, - "label": "Azure firewall", - "defaultValue": "[first(map(steps('basics').hub.azureFirewallsApi.value, (item) => item.name))]", - "filter": true, - "toolTip": "Select the existing Hub Azure firewall.", - "constraints": { - "required": true, - "allowedValues": "[map(steps('basics').hub.azureFirewallsApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.id, '\"}')))]" - } - } - ] - }, - { - "name": "naming", - "type": "Microsoft.Common.Section", - "label": "Naming Components", - "elements": [ - { - "name": "description", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "The values selected below will be used as components in your naming convention to name your Azure resource groups and resources. For more information on the naming convention used in this solution, refer to the documentation.", - "link": { - "label": "https://github.com/Azure/missionlz/blob/main/src/bicep/add-ons/azureVirtualDesktop/docs/design/naming.md", - "uri": "https://github.com/Azure/missionlz/blob/main/src/bicep/add-ons/azureVirtualDesktop/docs/design/naming.md" - } - } - }, - { - "name": "identifier", - "type": "Microsoft.Common.TextBox", - "label": "Identifier", - "toolTip": "Input a 3 character identifier for the resource group and resource names created with this solution. The identifier should represent a unique value within your organization, such as a business unit or project.", - "placeholder": "Example: it1", - "constraints": { - "required": true, - "regex": "^[a-z][a-z0-9]{1,2}$", - "validationMessage": "The value must be 1 - 3 characters in length, alphanumeric, and lowercase." - } - }, - { - "name": "environment", - "type": "Microsoft.Common.DropDown", - "visible": true, - "label": "Environment short name", - "defaultValue": "Development (dev)", - "toolTip": "Select the target environment for the solution. The single letter environment abbreviation will be used as part of the naming convention for the resoure groups and resources.", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "Development (dev)", - "value": "dev" - }, - { - "label": "Production (prod)", - "value": "prod" - }, - { - "label": "Test (test)", - "value": "test" - } - ] - } - }, - { - "name": "stampIndex", - "type": "Microsoft.Common.Slider", - "label": "Stamp Index", - "defaultValue": 0, - "toolTip": "The stamp index differentiates multiple AVD stamps within the same business unit or project. For example, '0' could be used for an office workers host pool and '1' could be used for a developers host pool.", - "min": 0, - "max": 9, - "showStepMarkers": false, - "constraints": { - "required": true - }, - "visible": true - } - ] - }, - { - "name": "servicePrincipalApi", - "type": "Microsoft.Solutions.GraphApiControl", - "request": { - "method": "GET", - "path": "/v1.0/serviceprincipals?$filter=appId eq '9cdead84-a844-4324-93f2-b2e6bb768d07'" - } - } + "$schema": "https://schema.management.azure.com/schemas/2021-09-09/uiFormDefinition.schema.json", + "view": { + "kind": "Form", + "properties": { + "title": "Mission Landing Zone Add-On: Azure Virtual Desktop", + "steps": [ + { + "name": "basics", + "label": "Basics", + "elements": [ + { + "name": "description", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "The Azure Virtual Desktop (AVD) add-on reduces the complexity in deploying AVD with SCCA and zero trust compliance. Click on the link below to learn more about the solution.", + "link": { + "label": "https://github.com/Azure/missionlz/blob/main/src/bicep/add-ons/azureVirtualDesktop/README.md", + "uri": "https://github.com/Azure/missionlz/blob/main/src/bicep/add-ons/azureVirtualDesktop/README.md" + } + } + }, + { + "name": "scope", + "type": "Microsoft.Common.ResourceScope", + "instanceDetailsLabel": "", + "location": { + "resourceTypes": [] + } + }, + { + "name": "hub", + "label": "Hub Resources", + "type": "Microsoft.Common.Section", + "elements": [ + { + "name": "api", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "subscriptions?api-version=2020-01-01" + } + }, + { + "name": "subscription", + "label": "Subscription", + "type": "Microsoft.Common.DropDown", + "defaultValue": "[steps('basics').scope.subscription.displayName]", + "toolTip": "Select the subscription for your Mission Landing Zone Hub network, firewall, and remote access resources.", + "filter": true, + "constraints": { + "allowedValues": "[map(steps('basics').hub.api.value, (item) => parse(concat('{\"label\":\"', item.displayName, '\",\"value\":\"', item.id, '\",\"description\":\"', 'ID: ', item.subscriptionId, '\"}')))]", + "required": true + } + }, + { + "name": "virtualNetworksApi", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(steps('basics').hub.subscription, '/providers/Microsoft.Network/virtualNetworks?api-version=2023-05-01')]" + } + }, + { + "name": "virtualNetwork", + "type": "Microsoft.Common.DropDown", + "visible": true, + "label": "Virtual network", + "defaultValue": "[filter(map(steps('basics').hub.virtualNetworksApi.value, (item) => item.name), (item) => contains(item, 'hub'))]", + "filter": true, + "toolTip": "Select the existing Hub virtual network.", + "constraints": { + "required": true, + "allowedValues": "[map(steps('basics').hub.virtualNetworksApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.id, '\"}')))]" + } + }, + { + "name": "azureFirewallsApi", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(steps('basics').hub.subscription, '/providers/Microsoft.Network/azureFirewalls?api-version=2023-05-01')]" + } + }, + { + "name": "azureFirewall", + "type": "Microsoft.Common.DropDown", + "visible": true, + "label": "Azure firewall", + "defaultValue": "[first(map(steps('basics').hub.azureFirewallsApi.value, (item) => item.name))]", + "filter": true, + "toolTip": "Select the existing Hub Azure firewall.", + "constraints": { + "required": true, + "allowedValues": "[map(steps('basics').hub.azureFirewallsApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.id, '\"}')))]" + } + } + ] + }, + { + "name": "naming", + "type": "Microsoft.Common.Section", + "label": "Naming Components", + "elements": [ + { + "name": "description", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "The values selected below will be used as components in your naming convention to name your Azure resource groups and resources. For more information on the naming convention used in this solution, refer to the documentation.", + "link": { + "label": "https://github.com/Azure/missionlz/blob/main/src/bicep/add-ons/azureVirtualDesktop/docs/design/naming.md", + "uri": "https://github.com/Azure/missionlz/blob/main/src/bicep/add-ons/azureVirtualDesktop/docs/design/naming.md" + } + } + }, + { + "name": "identifier", + "type": "Microsoft.Common.TextBox", + "label": "Identifier", + "toolTip": "Input a 3 character identifier for the resource group and resource names created with this solution. The identifier should represent a unique value within your organization, such as a business unit or project.", + "placeholder": "Example: it1", + "constraints": { + "required": true, + "regex": "^[a-z][a-z0-9]{1,2}$", + "validationMessage": "The value must be 1 - 3 characters in length, alphanumeric, and lowercase." + } + }, + { + "name": "environment", + "type": "Microsoft.Common.DropDown", + "visible": true, + "label": "Environment Abbreviation", + "defaultValue": "Development (dev)", + "toolTip": "Select the target environment for the solution. The single letter environment abbreviation will be used as part of the naming convention for the resoure groups and resources.", + "constraints": { + "required": true, + "allowedValues": [ + { + "label": "Development (dev)", + "value": "dev" + }, + { + "label": "Production (prod)", + "value": "prod" + }, + { + "label": "Test (test)", + "value": "test" + } ] + } }, { - "name": "controlPlane", - "label": "Control Plane", - "elements": [ - { - "name": "controlPlane", - "type": "Microsoft.Common.Section", - "visible": true, - "label": "Control Plane", - "elements": [ - { - "name": "locationsApi", - "type": "Microsoft.Solutions.ArmApiControl", - "request": { - "method": "GET", - "path": "[concat(steps('basics').scope.subscription.id, '/locations?api-version=2022-12-01')]" - } - }, - { - "name": "providerApi", - "type": "Microsoft.Solutions.ArmApiControl", - "request": { - "method": "GET", - "path": "[concat(steps('basics').scope.subscription.id, '/providers/Microsoft.DesktopVirtualization/resourceTypes?api-version=2021-04-01')]" - } - }, - { - "name": "location", - "label": "Location", - "type": "Microsoft.Common.DropDown", - "defaultValue": "[steps('basics').scope.location.displayName]", - "toolTip": "Select the location for the AVD management resources: host pool, workspace, application group, etc.", - "filter": true, - "constraints": { - "allowedValues": "[map(filter(steps('controlPlane').controlPlane.locationsApi.value, (item) => contains(first(map(filter(steps('controlPlane').controlPlane.providerApi.value, (item) => equals(item.resourceType, 'hostpools')), (item) => item.locations)), item.displayName)), (item) => parse(concat('{\"label\":\"', item.displayName, '\",\"value\":\"', item.name, '\"}')))]", - "required": true - } - } - ] - }, - { - "name": "hostPool", - "type": "Microsoft.Common.Section", - "visible": true, - "label": "Host Pool", - "elements": [ - { - "name": "validation", - "type": "Microsoft.Common.CheckBox", - "label": "Validation environment", - "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": "type", - "type": "Microsoft.Common.DropDown", - "visible": true, - "label": "Type", - "defaultValue": "Pooled", - "multiLine": true, - "toolTip": "", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "Pooled", - "value": "Pooled" - }, - { - "label": "Personal", - "value": "Personal" - } - ] - } - }, - { - "name": "loadBalancerAlgorithm", - "type": "Microsoft.Common.DropDown", - "visible": "[equals(steps('controlPlane').hostPool.type, 'Pooled')]", - "label": "Load balancing algorithm", - "defaultValue": "BreadthFirst", - "multiLine": true, - "toolTip": "Breadth-first load balancing distributes new user sessions across all available session hosts in the host pool. Depth-first load balancing distributes new user sessions to an available session host with the highest number of connections but has not reached its maximum session limit threshold.", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "BreadthFirst", - "description": "Each new session is placed on the next VM. (Performance Optimized)", - "value": "BreadthFirst" - }, - { - "label": "DepthFirst", - "description": "Each new session is placed on the same VM until max sessions limit. (Cost Optimized)", - "value": "DepthFirst" - } - ] - } - }, - { - "name": "maxSessions", - "type": "Microsoft.Common.TextBox", - "visible": "[equals(steps('controlPlane').hostPool.type, 'Pooled')]", - "label": "Max session limit", - "defaultValue": "4", - "toolTip": "The maximum number of users that have concurrent sessions on a session host. When setting a host pool to have depth first load balancing or planning to use Autoscaling, you must set an appropriate max session limit according to the configuration of your deployment and capacity of your VMs.", - "constraints": { - "required": true, - "regex": "\\d+", - "validationMessage": "The value must be one or more digits." - } - }, - { - "name": "assignmentType", - "type": "Microsoft.Common.DropDown", - "visible": "[equals(steps('controlPlane').hostPool.type, 'Personal')]", - "label": "Assignment type", - "defaultValue": "Automatic (Recommended)", - "multiLine": true, - "toolTip": "Automatic assignment - The service will select an available host and assign it to an user.\nDirect assignment - Admin selects a specific host to assign to an user.", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "Automatic (Recommended)", - "description": "Users are assigned an available VM the first time they connect.", - "value": "Automatic" - }, - { - "label": "Direct", - "description": "An administrator assigns a VM for each individual user.", - "value": "Direct" - } - ] - } - }, - { - "name": "customRdpProperties", - "type": "Microsoft.Common.TextBox", - "visible": true, - "label": "Custom RDP properties", - "defaultValue": "audiocapturemode:i:1;camerastoredirect:s:*;use multimon:i:0;drivestoredirect:s:;", - "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": "workspace", - "type": "Microsoft.Common.Section", - "visible": true, - "label": "Workspace", - "elements": [ - { - "name": "friendlyName", - "type": "Microsoft.Common.TextBox", - "label": "Friendly name (feed workspace)", - "defaultValue": "", - "placeholder": "Example: Information Technology", - "toolTip": "Input the friendly name for the AVD workspace / identifier that will be displayed in the end user's client. This value should apply to all the stamp indexes within the same identifier.", - "constraints": { - "required": false, - "regex": "^.{1,64}$", - "validationMessage": "The value must be between 1 and 64 characters in length." - }, - "visible": true - }, - { - "name": "publicNetworkAccess", - "type": "Microsoft.Common.DropDown", - "visible": true, - "label": "Public network access (feed workspace)", - "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": "subnetsApi", - "type": "Microsoft.Solutions.ArmApiControl", - "request": { - "method": "GET", - "path": "[concat(steps('basics').hub.virtualNetwork, '/subnets?api-version=2022-05-01')]" - } - }, - { - "name": "subnet", - "type": "Microsoft.Common.DropDown", - "visible": true, - "label": "Hub subnet (global workspace)", - "defaultValue": "[first(map(filter(steps('controlPlane').workspace.subnetsApi.value, (item) => and(and(not(equals(item.name, 'AzureFirewallSubnet')), not(equals(item.name, 'AzureFirewallManagementSubnet'))), not(equals(item.name, 'AzureBastionSubnet')))), (item) => item.name))]", - "filter": true, - "toolTip": "Select the existing Hub subnet for the AVD Global Workspace.", - "constraints": { - "required": true, - "allowedValues": "[map(steps('controlPlane').workspace.subnetsApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.id, '\"}')))]" - } - } - ] - }, - { - "name": "assignment", - "type": "Microsoft.Common.Section", - "label": "Assignment", - "visible": true, - "elements": [ - { - "name": "groupsApi", - "type": "Microsoft.Solutions.GraphApiControl", - "request": { - "method": "GET", - "path": "/v1.0/groups?$top=999" - } - }, - { - "name": "groupsDropDown", - "type": "Microsoft.Common.DropDown", - "label": "Groups", - "defaultValue": "", - "toolTip": "Select the desired group(s) to give access to this AVD stamp and if applicable, the FSLogix file share.", - "multiselect": true, - "filter": true, - "constraints": { - "allowedValues": "[map(steps('controlPlane').assignment.groupsApi.value, (item) => parse(concat('{\"label\":\"', item.displayName, '\",\"value\": {\"name\":\"', item.displayName, '\",\"objectId\":\"', item.id, '\"}}')))]", - "required": true - }, - "visible": "[not(empty(steps('controlPlane').assignment.groupsApi))]" - }, - { - "name": "groupsGrid", - "type": "Microsoft.Common.EditableGrid", - "ariaLabel": "Enter the security groups for access to AVD and if applicable, FSLogix. If deploying FSLogix, a storage account will be deployed for each group to support sharding.", - "label": "Security Groups", - "visible": "[empty(steps('controlPlane').assignment.groupsApi)]", - "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": "desktopFriendlyName", - "type": "Microsoft.Common.TextBox", - "label": "Desktop Friendly Name", - "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.", - "constraints": { - "required": false, - "regex": "^.{1,64}$", - "validationMessage": "The value must be between 1 and 64 characters in length." - }, - "visible": true - } + "name": "stampIndex", + "type": "Microsoft.Common.Slider", + "label": "Stamp Index", + "defaultValue": 0, + "toolTip": "The stamp index differentiates multiple AVD stamps within the same business unit or project. For example, '0' could be used for an office workers host pool and '1' could be used for a developers host pool.", + "min": 0, + "max": 9, + "showStepMarkers": false, + "constraints": { + "required": true + }, + "visible": true + } + ] + }, + { + "name": "scenario", + "type": "Microsoft.Common.Section", + "visible": true, + "label": "AVD Scenario", + "elements": [ + { + "name": "profile", + "type": "Microsoft.Common.DropDown", + "label": "Profile", + "filter": true, + "defaultValue": [ + "Generic" + ], + "toolTip": "Select the type of image to deploy on the session hosts.", + "constraints": { + "required": true, + "allowedValues": [ + { + "label": "ArcGIS Pro", + "description": "Geospatial data analysis and visualization", + "value": "arcGisPro" + }, + { + "label": "Generic", + "description": "Common deployment scenario for office workers, developers, and administrators.", + "value": "generic" + } ] + } }, { - "name": "hosts", - "label": "Session Hosts", - "elements": [ - { - "name": "image", - "type": "Microsoft.Common.Section", - "visible": true, - "label": "Image", - "elements": [ - { - "name": "source", - "type": "Microsoft.Common.DropDown", - "label": "Image Source", - "filter": true, - "defaultValue": "Marketplace", - "toolTip": "Select the type of image to deploy on the session hosts.", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "Marketplace", - "value": "marketplace" - }, - { - "label": "Compute Gallery", - "value": "gallery" - } - ] - } - }, - { - "name": "offer", - "type": "Microsoft.Common.DropDown", - "label": "Offer", - "defaultValue": "Windows 11", - "toolTip": "Select the desired marketplace image offer.", - "constraints": { - "allowedValues": [ - { - "label": "Office 365", - "value": "office-365" - }, - { - "label": "Windows 10", - "value": "Windows-10" - }, - { - "label": "Windows 11", - "value": "windows-11" - } - ], - "required": true - }, - "visible": "[equals(steps('hosts').image.source, 'marketplace')]" - }, - { - "name": "skuApi", - "type": "Microsoft.Solutions.ArmApiControl", - "request": { - "method": "GET", - "path": "[concat(steps('basics').scope.subscription.id, '/providers/Microsoft.Compute/locations/', steps('basics').scope.location.name, '/publishers/MicrosoftWindowsDesktop/artifacttypes/vmimage/offers/', steps('hosts').image.offer, '/skus?api-version=2022-08-01')]" - } - }, - { - "name": "sku", - "type": "Microsoft.Common.DropDown", - "label": "SKU", - "defaultValue": "[if(equals(steps('hosts').image.offer, 'windows-11'), 'win11-23h2-avd', if(equals(steps('hosts').image.offer, 'Windows-10'),'win10-22h2-avd-g2', 'win11-23h2-avd-m365'))]", - "filter": true, - "toolTip": "Select the desired marketplace image SKU.", - "constraints": { - "allowedValues": "[map(steps('hosts').image.skuApi, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]", - "required": true - }, - "visible": "[equals(steps('hosts').image.source, 'marketplace')]" - }, - { - "name": "galleryImage", - "type": "Microsoft.Solutions.ResourceSelector", - "visible": "[equals(steps('hosts').image.source, 'gallery')]", - "label": "Image", - "resourceType": "Microsoft.Compute/galleries/images", - "constraints": { - "required": true - } - } - ] - }, - { - "name": "virtualMachine", - "type": "Microsoft.Common.Section", - "visible": true, - "label": "Virtual Machine", - "elements": [ - { - "name": "availability", - "type": "Microsoft.Common.DropDown", - "label": "Availability Options", - "filter": true, - "defaultValue": "Availability Zones", - "toolTip": "Select the redundancy / resiliency for the virtual machines.", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "Availability Sets", - "value": "AvailabilitySets" - }, - { - "label": "Availability Zones", - "value": "AvailabilityZones" - }, - { - "label": "No infrastructure redundancy required", - "value": "None" - } - ] - } - }, - { - "name": "count", - "type": "Microsoft.Common.Slider", - "label": "Count", - "defaultValue": 1, - "toolTip": "Select the number of virtual machines to deploy in your AVD host pool.", - "min": 1, - "max": 4999, - "showStepMarkers": true, - "constraints": { - "required": true - }, - "visible": true - }, - { - "name": "size", - "type": "Microsoft.Compute.SizeSelector", - "label": "Size", - "toolTip": "Select the size of the virtual machines. Multi-session hosts should have 4 - 24 vCPUs. Single session host should have 2 or more vCPUs.", - "recommendedSizes": [ - "Standard_D4ads_v5" - ], - "constraints": { - "allowedSizes": [], - "excludedSizes": [], - "numAvailabilityZonesRequired": "[if(equals(steps('hosts').virtualMachine.availability, 'AvailabilitySets'), 3, 1)]" - }, - "options": { - "hideDiskTypeFilter": false - }, - "osPlatform": "Windows", - "count": "[steps('hosts').virtualMachine.count]", - "visible": true - }, - { - "name": "diskSku", - "type": "Microsoft.Common.DropDown", - "label": "OS Disk SKU", - "filter": true, - "defaultValue": "Premium_LRS (Recommended)", - "toolTip": "Select the disk SKU for the operating system disk.", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "Premium_LRS (Recommended)", - "value": "Premium_LRS" - }, - { - "label": "Standard_LRS", - "value": "Standard_LRS" - }, - { - "label": "StandardSSD_LRS", - "value": "StandardSSD_LRS" - } - ] - } - } - ] - }, - { - "name": "identity", - "type": "Microsoft.Common.Section", - "label": "Identity", - "visible": true, - "elements": [ - { - "name": "solution", - "type": "Microsoft.Common.OptionsGroup", - "visible": true, - "label": "Active Directory Solution", - "defaultValue": "Active Directory Domain Services (ADDS)", - "toolTip": "Choose the Active Directory solution that already exists.", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "Active Directory Domain Services (ADDS)", - "value": "ActiveDirectoryDomainServices" - }, - { - "label": "Microsoft Entra ID", - "value": "MicrosoftEntraId" - }, - { - "label": "Microsoft Entra Domain Services", - "value": "MicrosoftEntraDomainServices" - } - ] - } - }, - { - "name": "intune", - "type": "Microsoft.Common.OptionsGroup", - "visible": "[equals(steps('hosts').identity.solution, 'MicrosoftEntraId')]", - "label": "Intune Enrollment", - "defaultValue": "No", - "toolTip": "If Intune is configured in your Azure AD tenant, you can choose to have the VM automatically enrolled during the deployment by selecting Yes.", - "constraints": { - "required": false, - "allowedValues": [ - { - "label": "Yes", - "value": true - }, - { - "label": "No", - "value": false - } - ] - } - }, - { - "name": "domainName", - "type": "Microsoft.Common.TextBox", - "visible": "[not(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'))]", - "label": "Domain Name", - "toolTip": "Provide domain name for the selected Active Directory solution.", - "placeholder": "Example: contoso.com", - "constraints": { - "required": true - } - }, - { - "name": "ouPath", - "type": "Microsoft.Common.TextBox", - "visible": "[not(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'))]", - "label": "OU Path", - "toolTip": "Input the distinguished name of the desired organization unit for the AVD session hosts.", - "placeholder": "Example: OU=pooled,OU=avd,DC=contoso,DC=com", - "constraints": { - "required": true - } - } - ] - }, - { - "name": "domainJoinCredentials", - "type": "Microsoft.Common.Section", - "label": "Domain Join Credentials", - "visible": "[not(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'))]", - "elements": [ - { - "name": "userPrincipalName", - "type": "Microsoft.Common.TextBox", - "label": "User Principal Name", - "toolTip": "Enter the user principal name with domain join privileges.", - "placeholder": "Example: xadmin@contoso.com", - "constraints": { - "required": true, - "regex": "^[a-z0-9A-Z_.-]+@(?:[a-z0-9]+\\.)+[a-z]+$", - "validationMessage": "The value must be a valid user principal name." - } - }, - { - "name": "password", - "type": "Microsoft.Common.PasswordBox", - "label": { - "password": "Password" - }, - "toolTip": "Enter a password that is alphanumeric, contains at least 12 characters, 1 letter, 1 number and 1 special character.", - "constraints": { - "required": true - }, - "options": { - "hideConfirmation": true - } - } - ] - }, - { - "name": "localAdminCredentials", - "type": "Microsoft.Common.Section", - "visible": true, - "label": "Local Administrator Credential", - "elements": [ - { - "name": "username", - "type": "Microsoft.Common.TextBox", - "label": "Username", - "defaultValue": "", - "placeholder": "Example: xadmin", - "toolTip": "Input the username for the local administrator account.", - "constraints": { - "required": true, - "regex": "", - "validationMessage": "" - }, - "visible": true - }, - { - "name": "password", - "type": "Microsoft.Common.PasswordBox", - "label": { - "password": "Password", - "confirmPassword": "Confirm Password" - }, - "toolTip": "Input the password for the local administrator account.", - "constraints": { - "required": true, - "regex": "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=_!*<>()])(?=\\S+$).{12,123}$", - "validationMessage": "The value must be within 12 to 123 characters, be alphanumeric, and include 1 lower case character, 1 upper case character, 1 number, and 1 special character that is not '\\' or '-'." - }, - "options": { - "hideConfirmation": false - }, - "visible": true - } - ] - } + "name": "virtualMachineSizesApi", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(steps('basics').scope.subscription.id, '/providers/Microsoft.Compute/locations/', steps('basics').scope.location.name, '/vmSizes?api-version=2023-09-01')]" + } + } + ] + }, + { + "name": "servicePrincipalApi", + "type": "Microsoft.Solutions.GraphApiControl", + "request": { + "method": "GET", + "path": "/v1.0/serviceprincipals?$filter=appId eq '9cdead84-a844-4324-93f2-b2e6bb768d07'" + } + } + ] + }, + { + "name": "controlPlane", + "label": "Control Plane", + "elements": [ + { + "name": "controlPlane", + "type": "Microsoft.Common.Section", + "visible": true, + "label": "Control Plane", + "elements": [ + { + "name": "locationsApi", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(steps('basics').scope.subscription.id, '/locations?api-version=2022-12-01')]" + } + }, + { + "name": "providerApi", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(steps('basics').scope.subscription.id, '/providers/Microsoft.DesktopVirtualization/resourceTypes?api-version=2021-04-01')]" + } + }, + { + "name": "location", + "label": "Location", + "type": "Microsoft.Common.DropDown", + "defaultValue": "[steps('basics').scope.location.displayName]", + "toolTip": "Select the location for the AVD management resources: host pool, workspace, application group, etc.", + "filter": true, + "constraints": { + "allowedValues": "[map(filter(steps('controlPlane').controlPlane.locationsApi.value, (item) => contains(first(map(filter(steps('controlPlane').controlPlane.providerApi.value, (item) => equals(item.resourceType, 'hostpools')), (item) => item.locations)), item.displayName)), (item) => parse(concat('{\"label\":\"', item.displayName, '\",\"value\":\"', item.name, '\"}')))]", + "required": true + } + } + ] + }, + { + "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": "type", + "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": "userProfiles", - "label": "User Profiles", - "elements": [ - { - "name": "profileSolution", - "type": "Microsoft.Common.DropDown", - "visible": true, - "label": "Profile Solution", - "defaultValue": "[if(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'), 'Local', 'FSLogix')]", - "toolTip": "Select the user profile solution for your end users.", - "constraints": { - "required": true, - "allowedValues": "[if(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'), parse('[{\"label\":\"Local\",\"value\":\"local\"}]'), parse('[{\"label\":\"FSLogix\",\"value\":\"fslogix\"},{\"label\":\"Local\",\"value\":\"local\"}]'))]" - } - }, - { - "name": "storage", - "type": "Microsoft.Common.Section", - "label": "Profile Storage", - "visible": "[equals(steps('userProfiles').profileSolution, 'fslogix')]", - "elements": [ - { - "name": "service", - "type": "Microsoft.Common.DropDown", - "visible": true, - "label": "Service", - "defaultValue": "Azure Files", - "toolTip": "Select the Azure storage service for the user profiles.", - "constraints": { - "required": true, - "allowedValues": "[if(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'), parse('[{\"label\":\"Azure Files\",\"value\":\"AzureFiles\"}]'), parse('[{\"label\":\"Azure Files\",\"value\":\"AzureFiles\"},{\"label\":\"Azure NetApp Files\",\"value\":\"AzureNetAppFiles\"}]'))]" - } - }, - { - "name": "sku", - "type": "Microsoft.Common.DropDown", - "visible": "[equals(steps('userProfiles').profileSolution, 'fslogix')]", - "label": "SKU", - "defaultValue": "[if(equals(steps('userProfiles').profileSolution, 'local'), concat('Standard ', if(equals(steps('userProfiles').storage.service, 'AzureFiles'), '(20K IOPS)', '(100K IOPS)')), concat('Premium ', if(equals(steps('userProfiles').storage.service, 'AzureFiles'), '(100K IOPS)', '(450K IOPS)')))]", - "toolTip": "Select the storage SKU for the SMB file shares to support the use of FSLogix.", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "[concat('Premium ', if(equals(steps('userProfiles').storage.service, 'AzureFiles'), '(100K IOPS)', '(450K IOPS)'))]", - "value": "Premium" - }, - { - "label": "[concat('Standard ', if(equals(steps('userProfiles').storage.service, 'AzureFiles'), '(20K IOPS)', '(320K IOPS)'))]", - "value": "Standard" - } - ] - } - }, - { - "name": "fileShareSize", - "type": "Microsoft.Common.Slider", - "label": "File share size (GB)", - "defaultValue": 100, - "toolTip": "Input the quota size for the SMB file share to support the use of FSLogix.", - "min": 100, - "max": 100000, - "showStepMarkers": false, - "constraints": { - "required": true - }, - "visible": "[equals(steps('userProfiles').profileSolution, 'fslogix')]" - }, - { - "name": "fslogixContainerType", - "type": "Microsoft.Common.DropDown", - "visible": "[equals(steps('userProfiles').profileSolution, 'fslogix')]", - "label": "FSLogix solution", - "defaultValue": "Profile Container", - "toolTip": "Select the solution for the FSLogix profiles.", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "Cloud Cache, Profile Container", - "value": "CloudCacheProfileContainer" - }, - { - "label": "Cloud Cache, Profile & Office Container", - "value": "CloudCacheProfileOfficeContainer" - }, - { - "label": "Profile Container", - "value": "ProfileContainer" - }, - { - "label": "Profile & Office Container", - "value": "ProfileOfficeContainer" - } - ] - } - } - ] - } + "name": "workloadType", + "type": "Microsoft.Common.DropDown", + "label": "Workload Type", + "visible": "[equals(steps('controlPlane').hostPool.type, 'Pooled')]", + "filter": true, + "defaultValue": "[if(equals(steps('basics').scenario.profile, 'arcGisPro'), 'Power (1 user per core)', 'Heavy (2 users per core)')]", + "toolTip": "Select the type of image to deploy on the session hosts.", + "multiLine": true, + "constraints": { + "required": true, + "allowedValues": [ + { + "label": "Light (6 users per core)", + "description": "Basic data entry tasks", + "value": 6 + }, + { + "label": "Medium (4 users per core)", + "description": "Consultants and market researchers", + "value": 4 + }, + { + "label": "Heavy (2 users per core)", + "description": "Software engineers and content creators", + "value": 2 + }, + { + "label": "Power (1 user per core)", + "description": "Graphic designers, 3D model makers, and machine learning researchers", + "value": 1 + } ] + } }, { - "name": "networking", - "label": "Networking", - "elements": [ - { - "name": "controlPlane", - "label": "[if(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location), 'Control Plane & Session Hosts', 'Control Plane')]", - "type": "Microsoft.Common.Section", - "visible": true, - "elements": [ - { - "name": "virtualNetworkAddressCidrRange", - "label": "Virtual network CIDR range", - "type": "Microsoft.Common.TextBox", - "defaultValue": "10.0.121.0/24", - "toolTip": "Specify an address CIDR range within the range [10,26].", - "constraints": { - "required": true, - "validations": [ - { - "regex": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\/(1[0-9]|2[0-6]))$", - "message": "Invalid CIDR range. The address prefix must be in the range [10,26]." - } - ] - } - }, - { - "name": "subnetAddressCidrRangeWorkload", - "label": "Subnet CIDR range (Workload)", - "type": "Microsoft.Common.TextBox", - "defaultValue": "[concat('10.0.121.0/2', if(and(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location), equals(steps('userProfiles').storage.service, 'AzureNetAppFiles')), '5', '4'))]", - "toolTip": "Specify a CIDR range for the workload subnet within the AVD spoke virtual network range [24].", - "constraints": { - "required": true, - "validations": [ - { - "regex": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\/(2[4-8]))$", - "message": "Invalid CIDR range. The address prefix must be in the range [24,28]." - }, - { - "isValid": "[if(greaterOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), 8), equals(last(take(split(first(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), '.'), 1)), last(take(split(first(split(steps('networking').controlPlane.subnetAddressCidrRangeWorkload, '/')), '.'), 1))), true)]", - "message": "CIDR range not within virtual network CIDR range (first octet)." - }, - { - "isValid": "[if(greaterOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), 16), equals(last(take(split(first(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), '.'), 2)), last(take(split(first(split(steps('networking').controlPlane.subnetAddressCidrRangeWorkload, '/')), '.'), 2))), true)]", - "message": "CIDR range not within virtual network CIDR range (second octet)." - }, - { - "isValid": "[if(greaterOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), 24), equals(last(take(split(first(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), '.'), 3)), last(take(split(first(split(steps('networking').controlPlane.subnetAddressCidrRangeWorkload, '/')), '.'), 3))), true)]", - "message": "CIDR range not within virtual network CIDR range (third octet)." - }, - { - "isValid": "[lessOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), last(split(steps('networking').controlPlane.subnetAddressCidrRangeWorkload, '/')))]", - "message": "CIDR range not within virtual network CIDR range (subnet mask)." - } - ] - } - }, - { - "name": "subnetAddressCidrRangeAnf", - "label": "Subnet CIDR range (Azure NetApp Files)", - "type": "Microsoft.Common.TextBox", - "visible": "[and(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location), equals(steps('userProfiles').storage.service, 'AzureNetAppFiles'))]", - "defaultValue": "10.0.121.128/25", - "toolTip": "Specify a CIDR range for the Azure NetApp Files subnet within the AVD spoke virtual network range [24].", - "constraints": { - "required": true, - "validations": [ - { - "regex": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\/(2[4-8]))$", - "message": "Invalid CIDR range. The address prefix must be in the range [24,28]." - }, - { - "isValid": "[if(greaterOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), 8), equals(last(take(split(first(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), '.'), 1)), last(take(split(first(split(steps('networking').controlPlane.subnetAddressCidrRangeAnf, '/')), '.'), 1))), true)]", - "message": "CIDR range not within virtual network CIDR range (first octet)." - }, - { - "isValid": "[if(greaterOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), 16), equals(last(take(split(first(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), '.'), 2)), last(take(split(first(split(steps('networking').controlPlane.subnetAddressCidrRangeAnf, '/')), '.'), 2))), true)]", - "message": "CIDR range not within virtual network CIDR range (second octet)." - }, - { - "isValid": "[if(greaterOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), 24), equals(last(take(split(first(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), '.'), 3)), last(take(split(first(split(steps('networking').controlPlane.subnetAddressCidrRangeAnf, '/')), '.'), 3))), true)]", - "message": "CIDR range not within virtual network CIDR range (third octet)." - }, - { - "isValid": "[lessOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), last(split(steps('networking').controlPlane.subnetAddressCidrRangeAnf, '/')))]", - "message": "CIDR range not within virtual network CIDR range (subnet mask)." - } - ] - } - }, - { - "name": "disableBgpRoutePropagation", - "type": "Microsoft.Common.CheckBox", - "visible": true, - "label": "Disable BGP route propagation", - "defaultValue": true, - "toolTip": "Choose whether to disable BGP route propagation on the route table." - } - ] - }, - { - "name": "hosts", - "label": "Session Hosts", - "type": "Microsoft.Common.Section", - "visible": "[not(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location))]", - "elements": [ - { - "name": "virtualNetworkAddressCidrRange", - "label": "Virtual network CIDR range", - "type": "Microsoft.Common.TextBox", - "defaultValue": "10.0.122.0/24", - "toolTip": "Specify an address CIDR range within the range [10,24].", - "constraints": { - "required": true, - "validations": [ - { - "regex": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\/(1[0-9]|2[0-6]))$", - "message": "Invalid CIDR range. The address prefix must be in the range [10,26]." - } - ] - } - }, - { - "name": "subnetAddressCidrRangeWorkload", - "label": "Subnet CIDR range (Workload)", - "type": "Microsoft.Common.TextBox", - "defaultValue": "[concat('10.0.122.0/2', if(and(not(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location)), equals(steps('userProfiles').storage.service, 'AzureNetAppFiles')), '5', '4'))]", - "toolTip": "Specify a CIDR range for the default subnet within the AVD Hosts spoke virtual network range [24].", - "constraints": { - "required": true, - "validations": [ - { - "regex": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\/(2[4-8]))$", - "message": "Invalid CIDR range. The address prefix must be in the range [26,28]." - }, - { - "isValid": "[if(greaterOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), 8), equals(last(take(split(first(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), '.'), 1)), last(take(split(first(split(steps('networking').hosts.subnetAddressCidrRangeWorkload, '/')), '.'), 1))), true)]", - "message": "CIDR range not within virtual network CIDR range (first octet)." - }, - { - "isValid": "[if(greaterOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), 16), equals(last(take(split(first(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), '.'), 2)), last(take(split(first(split(steps('networking').hosts.subnetAddressCidrRangeWorkload, '/')), '.'), 2))), true)]", - "message": "CIDR range not within virtual network CIDR range (second octet)." - }, - { - "isValid": "[if(greaterOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), 24), equals(last(take(split(first(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), '.'), 3)), last(take(split(first(split(steps('networking').hosts.subnetAddressCidrRangeWorkload, '/')), '.'), 3))), true)]", - "message": "CIDR range not within virtual network CIDR range (third octet)." - }, - { - "isValid": "[lessOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), last(split(steps('networking').hosts.subnetAddressCidrRangeWorkload, '/')))]", - "message": "CIDR range not within virtual network CIDR range (subnet mask)." - } - ] - } - }, - { - "name": "subnetAddressCidrRangeAnf", - "label": "Subnet CIDR range (Azure NetApp Files)", - "type": "Microsoft.Common.TextBox", - "visible": "[and(not(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location)), equals(steps('userProfiles').storage.service, 'AzureNetAppFiles'))]", - "defaultValue": "10.0.122.128/25", - "toolTip": "Specify a CIDR range for the Azure NetApp Files subnet within the AVD spoke virtual network range [26].", - "constraints": { - "required": true, - "validations": [ - { - "regex": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\/(2[4-8]))$", - "message": "Invalid CIDR range. The address prefix must be in the range [24,28]." - }, - { - "isValid": "[if(greaterOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), 8), equals(last(take(split(first(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), '.'), 1)), last(take(split(first(split(steps('networking').hosts.subnetAddressCidrRangeAnf, '/')), '.'), 1))), true)]", - "message": "CIDR range not within virtual network CIDR range (first octet)." - }, - { - "isValid": "[if(greaterOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), 16), equals(last(take(split(first(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), '.'), 2)), last(take(split(first(split(steps('networking').hosts.subnetAddressCidrRangeAnf, '/')), '.'), 2))), true)]", - "message": "CIDR range not within virtual network CIDR range (second octet)." - }, - { - "isValid": "[if(greaterOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), 24), equals(last(take(split(first(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), '.'), 3)), last(take(split(first(split(steps('networking').hosts.subnetAddressCidrRangeAnf, '/')), '.'), 3))), true)]", - "message": "CIDR range not within virtual network CIDR range (third octet)." - }, - { - "isValid": "[lessOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), last(split(steps('networking').hosts.subnetAddressCidrRangeAnf, '/')))]", - "message": "CIDR range not within virtual network CIDR range (subnet mask)." - } - ] - } - } - ] - } + "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": "workspace", + "type": "Microsoft.Common.Section", + "visible": true, + "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": "subnetsApi", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(steps('basics').hub.virtualNetwork, '/subnets?api-version=2022-05-01')]" + } }, { - "name": "management", - "label": "Management", - "elements": [ - { - "name": "startVmOnConnect", - "type": "Microsoft.Common.Section", - "label": "Start VM On Connect", - "visible": "[empty(steps('basics').servicePrincipalApi)]", - "elements": [ - { - "name": "objectId", - "type": "Microsoft.Common.TextBox", - "label": "AVD Object ID", - "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.", - "constraints": { - "required": true, - "validations": [ - { - "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." - } - ] - } - } - ] - }, - { - "name": "drainMode", - "type": "Microsoft.Common.Section", - "label": "Drain Mode", - "visible": true, - "elements": [ - { - "name": "enable", - "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": "scaling", - "type": "Microsoft.Common.Section", - "label": "Scaling", - "visible": "[equals(steps('controlPlane').hostPool.type, 'Pooled')]", - "elements": [ - { - "name": "enable", - "type": "Microsoft.Common.CheckBox", - "visible": true, - "label": "Enable scaling tool?", - "defaultValue": false, - "toolTip": "Choose whether to deploy the required resources to enable the Scaling Tool." - }, - { - "name": "beginPeakTime", - "type": "Microsoft.Common.TextBox", - "label": "Begin Peak Time", - "visible": "[steps('management').scaling.enable]", - "defaultValue": "8:00", - "placeholder": "", - "toolTip": "Input the time when peak hours begin for your end users.", - "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." - } - ] - } - }, - { - "name": "endPeakTime", - "type": "Microsoft.Common.TextBox", - "label": "End Peak Time", - "visible": "[steps('management').scaling.enable]", - "defaultValue": "17:00", - "placeholder": "", - "toolTip": "Input the time when peak hours end for your end users.", - "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. 8pm should be input as 20:00." - } - ] - } - }, - { - "name": "forceLogOff", - "type": "Microsoft.Common.TextBox", - "label": "Force Log Off (Seconds)", - "visible": "[steps('management').scaling.enable]", - "defaultValue": "0", - "toolTip": "Use this setting to force logoff users if session time limit settings cannot be used.", - "placeholder": "", - "multiLine": false, - "constraints": { - "required": true, - "validations": [ - { - "regex": "^[0-9]{1,3}$", - "message": "The value must be between 1 and 3 digits." - } - ] - } - }, - { - "name": "minimumHosts", - "type": "Microsoft.Common.TextBox", - "label": "Minimum Number of Session Hosts", - "visible": "[steps('management').scaling.enable]", - "defaultValue": "0", - "toolTip": "Use this setting to determine the minimum number of sessions hosts to keep online.", - "placeholder": "", - "multiLine": false, - "constraints": { - "required": true, - "validations": [ - { - "regex": "^[0-9]{1,4}$", - "message": "The value must be between 1 and 4 digits." - } - ] - } - }, - { - "name": "cpuThreshold", - "type": "Microsoft.Common.TextBox", - "label": "Session Threshold Per CPU", - "visible": "[steps('management').scaling.enable]", - "defaultValue": "1", - "toolTip": "Use this setting to determine the number of sessions per CPU before turning on another host.", - "placeholder": "", - "multiLine": false, - "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." - } - ] - } - } - ] - }, - { - "name": "backup", - "type": "Microsoft.Common.Section", - "label": "Backup", - "visible": "[or(equals(steps('userProfiles').profileSolution, 'local'), and(equals(steps('userProfiles').profileSolution, 'fslogix'), equals(steps('userProfiles').storage.service, 'AzureFiles')))]", - "elements": [ - { - "name": "recoveryServices", - "type": "Microsoft.Common.CheckBox", - "visible": true, - "label": "Enable recovery services?", - "defaultValue": false, - "toolTip": "Choose to deploy backups for your solution. For a personal host pool, this will enable backups on the virtual machines. For a pooled host pool, this will enable backups on the FSLogix file share when using Azure Files." - } - ] - }, - { - "name": "monitoring", - "type": "Microsoft.Common.Section", - "label": "Monitoring", - "visible": true, - "elements": [ - { - "name": "enable", - "type": "Microsoft.Common.CheckBox", - "visible": true, - "label": "Enable monitoring for AVD Insights?", - "defaultValue": false, - "toolTip": "Deploy the required resources to enable AVD Insights." - }, - { - "name": "agent", - "type": "Microsoft.Common.DropDown", - "visible": "[steps('management').monitoring.enable]", - "label": "Monitoring agent", - "defaultValue": "Log Analytics Agent", - "toolTip": "Select the solution for the FSLogix profiles.", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "Azure Monitor Agent", - "value": "AzureMonitorAgent" - }, - { - "label": "Log Analytics Agent", - "value": "LogAnalyticsAgent" - } - ] - } - }, - { - "name": "enableSecurity", - "type": "Microsoft.Common.CheckBox", - "visible": "[and(steps('management').monitoring.enable, equals(steps('management').monitoring.agent, 'LogAnalyticsAgent'))]", - "label": "Multi-home agent for security monitoring?", - "defaultValue": false, - "toolTip": "Deploy the required configuration to multi-home the Microsoft Monitoring Agent for security monitoring." - }, - { - "name": "logAnalyticsWorkspace", - "type": "Microsoft.Solutions.ResourceSelector", - "label": "Existing Log Analytics Workspace for Security", - "visible": "[and(and(steps('management').monitoring.enable, equals(steps('management').monitoring.agent, 'LogAnalyticsAgent')),steps('management').monitoring.enableSecurity)]", - "resourceType": "Microsoft.OperationalInsights/workspaces", - "toolTip": "Select the log analytics workspace used for collecting security data for Sentinel or Defender for Cloud. This is required to multihome the Microsoft Monitoring Agent.", - "options": {} - } - ] + "name": "subnet", + "type": "Microsoft.Common.DropDown", + "visible": true, + "label": "Hub subnet (global)", + "defaultValue": "[first(map(filter(steps('controlPlane').workspace.subnetsApi.value, (item) => and(and(not(equals(item.name, 'AzureFirewallSubnet')), not(equals(item.name, 'AzureFirewallManagementSubnet'))), not(equals(item.name, 'AzureBastionSubnet')))), (item) => item.name))]", + "filter": true, + "toolTip": "Select the existing Hub subnet for the AVD Global Workspace.", + "constraints": { + "required": true, + "allowedValues": "[map(steps('controlPlane').workspace.subnetsApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.id, '\"}')))]" + } + } + ] + }, + { + "name": "assignment", + "type": "Microsoft.Common.Section", + "label": "Assignment", + "visible": true, + "elements": [ + { + "name": "groupsApi", + "type": "Microsoft.Solutions.GraphApiControl", + "request": { + "method": "GET", + "path": "/v1.0/groups?$top=999" + } + }, + { + "name": "groupsDropDown", + "type": "Microsoft.Common.DropDown", + "label": "Groups", + "defaultValue": "", + "toolTip": "Select the desired group(s) to give access to this AVD stamp and if applicable, the FSLogix file share.", + "multiselect": true, + "filter": true, + "constraints": { + "allowedValues": "[map(steps('controlPlane').assignment.groupsApi.value, (item) => parse(concat('{\"label\":\"', item.displayName, '\",\"value\": {\"name\":\"', item.displayName, '\",\"objectId\":\"', item.id, '\"}}')))]", + "required": true + }, + "visible": "[not(empty(steps('controlPlane').assignment.groupsApi))]" + }, + { + "name": "groupsGrid", + "type": "Microsoft.Common.EditableGrid", + "ariaLabel": "Enter the security groups for access to AVD and if applicable, FSLogix. If deploying FSLogix, a storage account will be deployed for each group to support sharding.", + "label": "Security Groups", + "visible": "[empty(steps('controlPlane').assignment.groupsApi)]", + "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": "[not(equals(steps('basics').scenario.profile, 'arcGisPro'))]", + "elements": [ + { + "name": "custom", + "type": "Microsoft.Common.CheckBox", + "visible": true, + "label": "Customize friendly names for AVD Client?", + "defaultValue": false, + "toolTip": "Choose whether to add custom names to the AVD feed workspace and the desktop for the AVD client." }, { - "name": "artifacts", - "label": "Artifacts", - "elements": [ - { - "name": "storage", - "type": "Microsoft.Common.Section", - "label": "Artifacts storage", - "elements": [ - { - "name": "description", - "type": "Microsoft.Common.TextBlock", - "visible": true, - "options": { - "text": "Select the storage account and container that hosts the artifacts required to deploy this solution." - } - }, - { - "name": "storageAccount", - "type": "Microsoft.Solutions.ResourceSelector", - "label": "Existing storage account", - "resourceType": "Microsoft.Storage/storageAccounts", - "options": { - "filter": { - "subscription": "all", - "location": "all" - } - } - }, - { - "name": "containerApi", - "type": "Microsoft.Solutions.ArmApiControl", - "request": { - "method": "GET", - "path": "[concat(steps('artifacts').storage.storageAccount.id, '/blobServices/default/containers?api-version=2023-01-01')]" - } - }, - { - "name": "container", - "type": "Microsoft.Common.DropDown", - "visible": true, - "label": "Existing container", - "defaultValue": "", - "filter": true, - "toolTip": "Select the existing container containing the required artifacts.", - "constraints": { - "required": true, - "allowedValues": "[map(steps('artifacts').storage.containerApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]" - } - } - ], - "visible": true - }, - { - "name": "files", - "type": "Microsoft.Common.Section", - "label": "Artifacts file names", - "elements": [ - { - "name": "description", - "type": "Microsoft.Common.TextBlock", - "visible": true, - "options": { - "text": "Input the file names for the required artifacts from the specified Azure Blobs container above." - } - }, - { - "name": "avdAgentMsiName", - "type": "Microsoft.Common.TextBox", - "label": "Azure Virtual Desktop Agent (.msi)", - "defaultValue": "Microsoft.RDInfra.RDAgent.Installer-x64-1.0.7909.2600.msi", - "toolTip": "Input the file / blob name for the AVD Agent installer.", - "placeholder": "", - "multiLine": false, - "constraints": { - "required": true, - "validations": [ - { - "isValid": "[endsWith(steps('artifacts').files.avdAgentMsiName, '.msi')]", - "message": "The file name must end with '.msi'." - } - ] - }, - "visible": true - }, - { - "name": "avdAgentBootLoaderMsiName", - "type": "Microsoft.Common.TextBox", - "label": "Azure Virtual Desktop Boot Loader (.msi)", - "defaultValue": "Microsoft.RDInfra.RDAgentBootLoader.Installer-x64 (5).msi", - "toolTip": "Input the file / blob name for the AVD Boot Loader installer.", - "placeholder": "", - "multiLine": false, - "constraints": { - "required": true, - "validations": [ - { - "isValid": "[endsWith(steps('artifacts').files.avdAgentBootLoaderMsiName, '.msi')]", - "message": "The file name must end with '.msi'." - } - ] - }, - "visible": true - }, - { - "name": "azurePowerShellModuleMsiName", - "type": "Microsoft.Common.TextBox", - "label": "Azure PowerShell Module Installer (.msi)", - "defaultValue": "Az-Cmdlets-10.2.0.37547-x64.msi", - "toolTip": "Input the file / blob name for the Azure PowerShell Module installer.", - "placeholder": "", - "multiLine": false, - "constraints": { - "required": true, - "validations": [ - { - "isValid": "[endsWith(steps('artifacts').files.azurePowerShellModuleMsiName, '.msi')]", - "message": "The file name must end with '.msi'." - } - ] - }, - "visible": true - }, - { - "name": "filesWarning", - "type": "Microsoft.Common.InfoBox", - "visible": true, - "options": { - "style": "Warning", - "text": "The files listed above are prerequisites for this solution. They must be downloaded and staged in Azure Blob storage. Once staged, ensure the file names listed above match the file names in Azure Blob storage since the names can change over time. Refer to the following link to download the files:", - "uri": { - "text": "https://github.com/Azure/missionlz/blob/main/src/bicep/add-ons/azureVirtualDesktop/docs/prerequisites.md#required", - "value": "https://github.com/Azure/missionlz/blob/main/src/bicep/add-ons/azureVirtualDesktop/docs/prerequisites.md#required" - } - } - } - ] - } + "name": "workspace", + "type": "Microsoft.Common.TextBox", + "label": "Feed Workspace", + "defaultValue": "", + "placeholder": "Example: Information Technology", + "toolTip": "Input the friendly name for the AVD workspace / identifier that will be displayed in the end user's client. This value should apply to all the stamp indexes within the same identifier.", + "visible": "[steps('controlPlane').friendlyNames.custom]", + "constraints": { + "required": false, + "regex": "^.{1,64}$", + "validationMessage": "The value must be between 1 and 64 characters in length." + } + }, + { + "name": "desktop", + "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.", + "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", + "elements": [ + { + "name": "image", + "type": "Microsoft.Common.Section", + "visible": true, + "label": "Image", + "elements": [ + { + "name": "source", + "type": "Microsoft.Common.DropDown", + "label": "Image Source", + "filter": true, + "defaultValue": "[if(equals(steps('basics').scenario.profile, 'arcGisPro'), 'Compute Gallery', 'Marketplace')]", + "toolTip": "Select the type of image to deploy on the session hosts.", + "constraints": { + "required": true, + "allowedValues": "[if(equals(steps('basics').scenario.profile, 'arcGisPro'), parse('[{\"label\":\"Compute Gallery\",\"value\":\"gallery\"}]'), parse('[{\"label\":\"Marketplace\",\"value\":\"marketplace\"},{\"label\":\"Compute Gallery\",\"value\":\"gallery\"}]'))]" + } + }, + { + "name": "offer", + "type": "Microsoft.Common.DropDown", + "label": "Offer", + "defaultValue": "Windows 11", + "toolTip": "Select the desired marketplace image offer.", + "constraints": { + "allowedValues": [ + { + "label": "Office 365", + "value": "office-365" + }, + { + "label": "Windows 10", + "value": "Windows-10" + }, + { + "label": "Windows 11", + "value": "windows-11" + } + ], + "required": true + }, + "visible": "[equals(steps('hosts').image.source, 'marketplace')]" + }, + { + "name": "skuApi", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(steps('basics').scope.subscription.id, '/providers/Microsoft.Compute/locations/', steps('basics').scope.location.name, '/publishers/MicrosoftWindowsDesktop/artifacttypes/vmimage/offers/', steps('hosts').image.offer, '/skus?api-version=2022-08-01')]" + } + }, + { + "name": "sku", + "type": "Microsoft.Common.DropDown", + "label": "SKU", + "defaultValue": "[if(equals(steps('hosts').image.offer, 'windows-11'), 'win11-23h2-avd', if(equals(steps('hosts').image.offer, 'Windows-10'),'win10-22h2-avd-g2', 'win11-23h2-avd-m365'))]", + "filter": true, + "toolTip": "Select the desired marketplace image SKU.", + "constraints": { + "allowedValues": "[map(steps('hosts').image.skuApi, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]", + "required": true + }, + "visible": "[equals(steps('hosts').image.source, 'marketplace')]" + }, + { + "name": "galleryImage", + "type": "Microsoft.Solutions.ResourceSelector", + "visible": "[equals(steps('hosts').image.source, 'gallery')]", + "label": "Image", + "resourceType": "Microsoft.Compute/galleries/images", + "constraints": { + "required": true + } + } + ] + }, + { + "name": "virtualMachine", + "type": "Microsoft.Common.Section", + "visible": true, + "label": "Virtual Machine", + "elements": [ + { + "name": "availability", + "type": "Microsoft.Common.DropDown", + "label": "Availability Options", + "filter": true, + "defaultValue": "Availability Zones", + "toolTip": "Select the redundancy / resiliency for the virtual machines.", + "constraints": { + "required": true, + "allowedValues": [ + { + "label": "Availability Sets", + "value": "AvailabilitySets" + }, + { + "label": "Availability Zones", + "value": "AvailabilityZones" + }, + { + "label": "No infrastructure redundancy required", + "value": "None" + } ] + } }, { - "name": "tags", - "label": "Tags", - "elements": [ - { - "name": "tags", - "type": "Microsoft.Common.TagsByResource", - "resources": [ - "Microsoft.Automation/automationAccounts", - "Microsoft.Compute/availabilitySets", - "Microsoft.Compute/diskAccesses", - "Microsoft.Compute/diskEncryptionSets", - "Microsoft.Compute/virtualMachines", - "Microsoft.DesktopVirtualization/applicationGroups", - "Microsoft.DesktopVirtualization/hostPools", - "Microsoft.Insights/dataCollectionRules", - "Microsoft.KeyVault/vaults", - "Microsoft.ManagedIdentity/userAssignedIdentities", - "Microsoft.NetApp/netAppAccounts", - "Microsoft.Network/networkInterfaces", - "Microsoft.Network/networkSecurityGroups", - "Microsoft.Network/privateEndpoints", - "Microsoft.Network/routeTables", - "Microsoft.Network/virtualNetworks", - "Microsoft.OperationalInsights/workspaces", - "Microsoft.RecoveryServices/vaults", - "Microsoft.Resources/resourceGroups", - "Microsoft.Storage/storageAccounts" - ] - } + "name": "count", + "type": "Microsoft.Common.Slider", + "label": "Count", + "defaultValue": 1, + "toolTip": "Select the number of virtual machines to deploy in your AVD host pool.", + "min": 1, + "max": 4999, + "showStepMarkers": true, + "constraints": { + "required": true + }, + "visible": true + }, + { + "name": "diskSku", + "type": "Microsoft.Common.DropDown", + "label": "OS Disk SKU", + "filter": true, + "defaultValue": "Premium_LRS (Recommended)", + "toolTip": "Select the disk SKU for the operating system disk.", + "constraints": { + "required": true, + "allowedValues": [ + { + "label":"Premium_LRS (Recommended)", + "value":"Premium_LRS" + }, + { + "label":"Standard_LRS", + "value":"Standard_LRS" + }, + { + "label":"StandardSSD_LRS", + "value":"StandardSSD_LRS" + } + ] + }, + "visible": "[not(equals(steps('basics').scenario.profile, 'arcGisPro'))]" + }, + { + "name": "sizeGeneric", + "type": "Microsoft.Compute.SizeSelector", + "label": "Size", + "toolTip": "Select the size of the virtual machines. Multi-session hosts should have 4 - 24 vCPUs. Single session host should have 2 or more vCPUs.", + "recommendedSizes": [ + "Standard_D4ads_v5" + ], + "constraints": { + "allowedSizes": [], + "numAvailabilityZonesRequired": "[if(equals(steps('hosts').virtualMachine.availability, 'AvailabilityZones'), 3, 1)]" + }, + "options": { + "hideDiskTypeFilter": "[if(equals(steps('hosts').virtualMachine.diskSku, 'Premium_LRS'), true, false)]" + }, + "osPlatform": "Windows", + "count": "[steps('hosts').virtualMachine.count]", + "visible": "[not(equals(steps('basics').scenario.profile, 'arcGisPro'))]" + }, + { + "name": "sizeArcGisPro", + "type": "Microsoft.Compute.SizeSelector", + "label": "Size", + "toolTip": "Select the size of the virtual machines. Multi-session hosts should have 4 - 24 vCPUs. Single session host should have 2 or more vCPUs.", + "recommendedSizes": [ + "Standard_NV4as_v4" + ], + "constraints": { + "allowedSizes": [ + "Standard_NV4as_v4", + "Standard_NV8as_v4", + "Standard_NV16as_v4", + "Standard_NV32as_v4" + ], + "numAvailabilityZonesRequired": "[if(equals(steps('hosts').virtualMachine.availability, 'AvailabilityZones'), 3, 1)]" + }, + "options": { + "hideDiskTypeFilter": "[if(equals(steps('hosts').virtualMachine.diskSku, 'Premium_LRS'), true, false)]" + }, + "osPlatform": "Windows", + "count": "[steps('hosts').virtualMachine.count]", + "visible": "[equals(steps('basics').scenario.profile, 'arcGisPro')]" + } + ] + }, + { + "name": "identity", + "type": "Microsoft.Common.Section", + "label": "Identity", + "visible": true, + "elements": [ + { + "name": "solution", + "type": "Microsoft.Common.OptionsGroup", + "visible": true, + "label": "Active Directory Solution", + "defaultValue": "Active Directory Domain Services (ADDS)", + "toolTip": "Choose the Active Directory solution that already exists.", + "constraints": { + "required": true, + "allowedValues": [ + { + "label": "Active Directory Domain Services (ADDS)", + "value": "ActiveDirectoryDomainServices" + }, + { + "label": "Microsoft Entra ID", + "value": "MicrosoftEntraId" + }, + { + "label": "Microsoft Entra Domain Services", + "value": "MicrosoftEntraDomainServices" + } + ] + } + }, + { + "name": "intune", + "type": "Microsoft.Common.OptionsGroup", + "visible": "[equals(steps('hosts').identity.solution, 'MicrosoftEntraId')]", + "label": "Intune Enrollment", + "defaultValue": "No", + "toolTip": "If Intune is configured in your Azure AD tenant, you can choose to have the VM automatically enrolled during the deployment by selecting Yes.", + "constraints": { + "required": false, + "allowedValues": [ + { + "label": "Yes", + "value": true + }, + { + "label": "No", + "value": false + } + ] + } + }, + { + "name": "domainName", + "type": "Microsoft.Common.TextBox", + "visible": "[not(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'))]", + "label": "Domain Name", + "toolTip": "Provide domain name for the selected Active Directory solution.", + "placeholder": "Example: contoso.com", + "constraints": { + "required": true + } + }, + { + "name": "ouPath", + "type": "Microsoft.Common.TextBox", + "visible": "[not(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'))]", + "label": "OU Path", + "toolTip": "Input the distinguished name of the desired organization unit for the AVD session hosts.", + "placeholder": "Example: OU=pooled,OU=avd,DC=contoso,DC=com", + "constraints": { + "required": true + } + } + ] + }, + { + "name": "domainJoinCredentials", + "type": "Microsoft.Common.Section", + "label": "Domain Join Credentials", + "visible": "[not(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'))]", + "elements": [ + { + "name": "userPrincipalName", + "type": "Microsoft.Common.TextBox", + "label": "User Principal Name", + "toolTip": "Enter the user principal name with domain join privileges.", + "placeholder": "Example: xadmin@contoso.com", + "constraints": { + "required": true, + "regex": "^[a-z0-9A-Z_.-]+@(?:[a-z0-9]+\\.)+[a-z]+$", + "validationMessage": "The value must be a valid user principal name." + } + }, + { + "name": "password", + "type": "Microsoft.Common.PasswordBox", + "label": { + "password": "Password" + }, + "toolTip": "Enter a password that is alphanumeric, contains at least 12 characters, 1 letter, 1 number and 1 special character.", + "constraints": { + "required": true + }, + "options": { + "hideConfirmation": true + } + } + ] + }, + { + "name": "localAdminCredentials", + "type": "Microsoft.Common.Section", + "visible": true, + "label": "Local Administrator Credential", + "elements": [ + { + "name": "username", + "type": "Microsoft.Common.TextBox", + "label": "Username", + "defaultValue": "", + "placeholder": "Example: xadmin", + "toolTip": "Input the username for the local administrator account.", + "constraints": { + "required": true, + "regex": "", + "validationMessage": "" + }, + "visible": true + }, + { + "name": "password", + "type": "Microsoft.Common.PasswordBox", + "label": { + "password": "Password", + "confirmPassword": "Confirm Password" + }, + "toolTip": "Input the password for the local administrator account.", + "constraints": { + "required": true, + "regex": "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=_!*<>()])(?=\\S+$).{12,123}$", + "validationMessage": "The value must be within 12 to 123 characters, be alphanumeric, and include 1 lower case character, 1 upper case character, 1 number, and 1 special character that is not '\\' or '-'." + }, + "options": { + "hideConfirmation": false + }, + "visible": true + } + ] + } + ] + }, + { + "name": "userProfiles", + "label": "User Profiles", + "elements": [ + { + "name": "profileSolution", + "type": "Microsoft.Common.DropDown", + "visible": true, + "label": "Profile Solution", + "defaultValue": "[if(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'), 'Local', 'FSLogix')]", + "toolTip": "Select the user profile solution for your end users.", + "constraints": { + "required": true, + "allowedValues": "[if(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'), parse('[{\"label\":\"Local\",\"value\":\"local\"}]'), parse('[{\"label\":\"FSLogix\",\"value\":\"fslogix\"},{\"label\":\"Local\",\"value\":\"local\"}]'))]" + } + }, + { + "name": "storage", + "type": "Microsoft.Common.Section", + "label": "Profile Storage", + "visible": "[equals(steps('userProfiles').profileSolution, 'fslogix')]", + "elements": [ + { + "name": "service", + "type": "Microsoft.Common.DropDown", + "visible": true, + "label": "Service", + "defaultValue": "[if(equals(steps('basics').scenario.profile, 'arcGisPro'), 'Azure NetApp Files', 'Azure Files')]", + "toolTip": "Select the Azure storage service for the user profiles.", + "constraints": { + "required": true, + "allowedValues": "[if(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'), parse('[{\"label\":\"Azure Files\",\"value\":\"AzureFiles\"}]'), parse('[{\"label\":\"Azure Files\",\"value\":\"AzureFiles\"},{\"label\":\"Azure NetApp Files\",\"value\":\"AzureNetAppFiles\"}]'))]" + } + }, + { + "name": "sku", + "type": "Microsoft.Common.DropDown", + "visible": "[equals(steps('userProfiles').profileSolution, 'fslogix')]", + "label": "SKU", + "defaultValue": "[if(equals(steps('userProfiles').profileSolution, 'local'), concat('Standard ', if(equals(steps('userProfiles').storage.service, 'AzureFiles'), '(20K IOPS)', '(100K IOPS)')), concat('Premium ', if(equals(steps('userProfiles').storage.service, 'AzureFiles'), '(100K IOPS)', '(450K IOPS)')))]", + "toolTip": "Select the storage SKU for the SMB file shares to support the use of FSLogix.", + "constraints": { + "required": true, + "allowedValues": [ + { + "label": "[concat('Premium ', if(equals(steps('userProfiles').storage.service, 'AzureFiles'), '(100K IOPS)', '(450K IOPS)'))]", + "value": "Premium" + }, + { + "label": "[concat('Standard ', if(equals(steps('userProfiles').storage.service, 'AzureFiles'), '(20K IOPS)', '(320K IOPS)'))]", + "value": "Standard" + } + ] + } + }, + { + "name": "fileShareSize", + "type": "Microsoft.Common.Slider", + "label": "File share size (GB)", + "defaultValue": 100, + "toolTip": "Input the quota size for the SMB file share to support the use of FSLogix.", + "min": 100, + "max": 100000, + "showStepMarkers": false, + "constraints": { + "required": true + }, + "visible": "[equals(steps('userProfiles').profileSolution, 'fslogix')]" + }, + { + "name": "fslogixContainerType", + "type": "Microsoft.Common.DropDown", + "visible": "[equals(steps('userProfiles').profileSolution, 'fslogix')]", + "label": "FSLogix solution", + "defaultValue": "Profile Container", + "toolTip": "Select the solution for the FSLogix profiles.", + "constraints": { + "required": true, + "allowedValues": [ + { + "label": "Cloud Cache, Profile Container", + "value": "CloudCacheProfileContainer" + }, + { + "label": "Cloud Cache, Profile & Office Container", + "value": "CloudCacheProfileOfficeContainer" + }, + { + "label": "Profile Container", + "value": "ProfileContainer" + }, + { + "label": "Profile & Office Container", + "value": "ProfileOfficeContainer" + } + ] + } + } + ] + } + ] + }, + { + "name": "networking", + "label": "Networking", + "elements": [ + { + "name": "controlPlane", + "label": "[if(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location), 'Control Plane & Session Hosts', 'Control Plane')]", + "type": "Microsoft.Common.Section", + "visible": true, + "elements": [ + { + "name": "virtualNetworkAddressCidrRange", + "label": "Virtual network CIDR range", + "type": "Microsoft.Common.TextBox", + "defaultValue": "10.0.121.0/24", + "toolTip": "Specify an address CIDR range within the range [10,26].", + "constraints": { + "required": true, + "validations": [ + { + "regex": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\/(1[0-9]|2[0-6]))$", + "message": "Invalid CIDR range. The address prefix must be in the range [10,26]." + } + ] + } + }, + { + "name": "subnetAddressCidrRangeWorkload", + "label": "Subnet CIDR range (Workload)", + "type": "Microsoft.Common.TextBox", + "defaultValue": "[concat('10.0.121.0/2', if(and(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location), equals(steps('userProfiles').storage.service, 'AzureNetAppFiles')), '5', '4'))]", + "toolTip": "Specify a CIDR range for the workload subnet within the AVD spoke virtual network range [24].", + "constraints": { + "required": true, + "validations": [ + { + "regex": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\/(2[4-8]))$", + "message": "Invalid CIDR range. The address prefix must be in the range [24,28]." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), 8), equals(last(take(split(first(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), '.'), 1)), last(take(split(first(split(steps('networking').controlPlane.subnetAddressCidrRangeWorkload, '/')), '.'), 1))), true)]", + "message": "CIDR range not within virtual network CIDR range (first octet)." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), 16), equals(last(take(split(first(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), '.'), 2)), last(take(split(first(split(steps('networking').controlPlane.subnetAddressCidrRangeWorkload, '/')), '.'), 2))), true)]", + "message": "CIDR range not within virtual network CIDR range (second octet)." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), 24), equals(last(take(split(first(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), '.'), 3)), last(take(split(first(split(steps('networking').controlPlane.subnetAddressCidrRangeWorkload, '/')), '.'), 3))), true)]", + "message": "CIDR range not within virtual network CIDR range (third octet)." + }, + { + "isValid": "[lessOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), last(split(steps('networking').controlPlane.subnetAddressCidrRangeWorkload, '/')))]", + "message": "CIDR range not within virtual network CIDR range (subnet mask)." + } + ] + } + }, + { + "name": "subnetAddressCidrRangeAnf", + "label": "Subnet CIDR range (Azure NetApp Files)", + "type": "Microsoft.Common.TextBox", + "visible": "[and(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location), equals(steps('userProfiles').storage.service, 'AzureNetAppFiles'))]", + "defaultValue": "10.0.121.128/25", + "toolTip": "Specify a CIDR range for the Azure NetApp Files subnet within the AVD spoke virtual network range [24].", + "constraints": { + "required": true, + "validations": [ + { + "regex": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\/(2[4-8]))$", + "message": "Invalid CIDR range. The address prefix must be in the range [24,28]." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), 8), equals(last(take(split(first(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), '.'), 1)), last(take(split(first(split(steps('networking').controlPlane.subnetAddressCidrRangeAnf, '/')), '.'), 1))), true)]", + "message": "CIDR range not within virtual network CIDR range (first octet)." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), 16), equals(last(take(split(first(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), '.'), 2)), last(take(split(first(split(steps('networking').controlPlane.subnetAddressCidrRangeAnf, '/')), '.'), 2))), true)]", + "message": "CIDR range not within virtual network CIDR range (second octet)." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), 24), equals(last(take(split(first(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), '.'), 3)), last(take(split(first(split(steps('networking').controlPlane.subnetAddressCidrRangeAnf, '/')), '.'), 3))), true)]", + "message": "CIDR range not within virtual network CIDR range (third octet)." + }, + { + "isValid": "[lessOrEquals(last(split(steps('networking').controlPlane.virtualNetworkAddressCidrRange, '/')), last(split(steps('networking').controlPlane.subnetAddressCidrRangeAnf, '/')))]", + "message": "CIDR range not within virtual network CIDR range (subnet mask)." + } + ] + } + }, + { + "name": "disableBgpRoutePropagation", + "type": "Microsoft.Common.CheckBox", + "visible": true, + "label": "Disable BGP route propagation", + "defaultValue": true, + "toolTip": "Choose whether to disable BGP route propagation on the route table." + } + ] + }, + { + "name": "hosts", + "label": "Session Hosts", + "type": "Microsoft.Common.Section", + "visible": "[not(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location))]", + "elements": [ + { + "name": "virtualNetworkAddressCidrRange", + "label": "Virtual network CIDR range", + "type": "Microsoft.Common.TextBox", + "defaultValue": "10.0.122.0/24", + "toolTip": "Specify an address CIDR range within the range [10,24].", + "constraints": { + "required": true, + "validations": [ + { + "regex": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\/(1[0-9]|2[0-6]))$", + "message": "Invalid CIDR range. The address prefix must be in the range [10,26]." + } + ] + } + }, + { + "name": "subnetAddressCidrRangeWorkload", + "label": "Subnet CIDR range (Workload)", + "type": "Microsoft.Common.TextBox", + "defaultValue": "[concat('10.0.122.0/2', if(and(not(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location)), equals(steps('userProfiles').storage.service, 'AzureNetAppFiles')), '5', '4'))]", + "toolTip": "Specify a CIDR range for the default subnet within the AVD Hosts spoke virtual network range [24].", + "constraints": { + "required": true, + "validations": [ + { + "regex": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\/(2[4-8]))$", + "message": "Invalid CIDR range. The address prefix must be in the range [26,28]." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), 8), equals(last(take(split(first(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), '.'), 1)), last(take(split(first(split(steps('networking').hosts.subnetAddressCidrRangeWorkload, '/')), '.'), 1))), true)]", + "message": "CIDR range not within virtual network CIDR range (first octet)." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), 16), equals(last(take(split(first(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), '.'), 2)), last(take(split(first(split(steps('networking').hosts.subnetAddressCidrRangeWorkload, '/')), '.'), 2))), true)]", + "message": "CIDR range not within virtual network CIDR range (second octet)." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), 24), equals(last(take(split(first(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), '.'), 3)), last(take(split(first(split(steps('networking').hosts.subnetAddressCidrRangeWorkload, '/')), '.'), 3))), true)]", + "message": "CIDR range not within virtual network CIDR range (third octet)." + }, + { + "isValid": "[lessOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), last(split(steps('networking').hosts.subnetAddressCidrRangeWorkload, '/')))]", + "message": "CIDR range not within virtual network CIDR range (subnet mask)." + } + ] + } + }, + { + "name": "subnetAddressCidrRangeAnf", + "label": "Subnet CIDR range (Azure NetApp Files)", + "type": "Microsoft.Common.TextBox", + "visible": "[and(not(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location)), equals(steps('userProfiles').storage.service, 'AzureNetAppFiles'))]", + "defaultValue": "10.0.122.128/25", + "toolTip": "Specify a CIDR range for the Azure NetApp Files subnet within the AVD spoke virtual network range [26].", + "constraints": { + "required": true, + "validations": [ + { + "regex": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?:\/(2[4-8]))$", + "message": "Invalid CIDR range. The address prefix must be in the range [24,28]." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), 8), equals(last(take(split(first(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), '.'), 1)), last(take(split(first(split(steps('networking').hosts.subnetAddressCidrRangeAnf, '/')), '.'), 1))), true)]", + "message": "CIDR range not within virtual network CIDR range (first octet)." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), 16), equals(last(take(split(first(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), '.'), 2)), last(take(split(first(split(steps('networking').hosts.subnetAddressCidrRangeAnf, '/')), '.'), 2))), true)]", + "message": "CIDR range not within virtual network CIDR range (second octet)." + }, + { + "isValid": "[if(greaterOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), 24), equals(last(take(split(first(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), '.'), 3)), last(take(split(first(split(steps('networking').hosts.subnetAddressCidrRangeAnf, '/')), '.'), 3))), true)]", + "message": "CIDR range not within virtual network CIDR range (third octet)." + }, + { + "isValid": "[lessOrEquals(last(split(steps('networking').hosts.virtualNetworkAddressCidrRange, '/')), last(split(steps('networking').hosts.subnetAddressCidrRangeAnf, '/')))]", + "message": "CIDR range not within virtual network CIDR range (subnet mask)." + } + ] + } + } + ] + } + ] + }, + { + "name": "management", + "label": "Management", + "elements": [ + { + "name": "startVmOnConnect", + "type": "Microsoft.Common.Section", + "label": "Start VM On Connect", + "visible": "[empty(steps('basics').servicePrincipalApi)]", + "elements": [ + { + "name": "objectId", + "type": "Microsoft.Common.TextBox", + "label": "AVD Object ID", + "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.", + "constraints": { + "required": true, + "validations": [ + { + "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." + } + ] + } + } + ] + }, + { + "name": "drainMode", + "type": "Microsoft.Common.Section", + "label": "Drain Mode", + "visible": true, + "elements": [ + { + "name": "enable", + "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": "scaling", + "type": "Microsoft.Common.Section", + "label": "Scaling", + "visible": "[equals(steps('controlPlane').hostPool.type, 'Pooled')]", + "elements": [ + { + "name": "enable", + "type": "Microsoft.Common.CheckBox", + "visible": true, + "label": "Enable scaling tool?", + "defaultValue": false, + "toolTip": "Choose whether to deploy the required resources to enable the Scaling Tool." + }, + { + "name": "beginPeakTime", + "type": "Microsoft.Common.TextBox", + "label": "Begin Peak Time", + "visible": "[steps('management').scaling.enable]", + "defaultValue": "8:00", + "placeholder": "", + "toolTip": "Input the time when peak hours begin for your end users.", + "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." + } ] + } + }, + { + "name": "endPeakTime", + "type": "Microsoft.Common.TextBox", + "label": "End Peak Time", + "visible": "[steps('management').scaling.enable]", + "defaultValue": "17:00", + "placeholder": "", + "toolTip": "Input the time when peak hours end for your end users.", + "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. 8pm should be input as 20:00." + } + ] + } + }, + { + "name": "forceLogOff", + "type": "Microsoft.Common.TextBox", + "label": "Force Log Off (Seconds)", + "visible": "[steps('management').scaling.enable]", + "defaultValue": "0", + "toolTip": "Use this setting to force logoff users if session time limit settings cannot be used.", + "placeholder": "", + "multiLine": false, + "constraints": { + "required": true, + "validations": [ + { + "regex": "^[0-9]{1,3}$", + "message": "The value must be between 1 and 3 digits." + } + ] + } + }, + { + "name": "minimumHosts", + "type": "Microsoft.Common.TextBox", + "label": "Minimum Number of Session Hosts", + "visible": "[steps('management').scaling.enable]", + "defaultValue": "0", + "toolTip": "Use this setting to determine the minimum number of sessions hosts to keep online.", + "placeholder": "", + "multiLine": false, + "constraints": { + "required": true, + "validations": [ + { + "regex": "^[0-9]{1,4}$", + "message": "The value must be between 1 and 4 digits." + } + ] + } + }, + { + "name": "cpuThreshold", + "type": "Microsoft.Common.TextBox", + "label": "Session Threshold Per CPU", + "visible": "[steps('management').scaling.enable]", + "defaultValue": "1", + "toolTip": "Use this setting to determine the number of sessions per CPU before turning on another host.", + "placeholder": "", + "multiLine": false, + "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." + } + ] + } + } + ] + }, + { + "name": "backup", + "type": "Microsoft.Common.Section", + "label": "Backup", + "visible": "[or(equals(steps('userProfiles').profileSolution, 'local'), and(equals(steps('userProfiles').profileSolution, 'fslogix'), equals(steps('userProfiles').storage.service, 'AzureFiles')))]", + "elements": [ + { + "name": "recoveryServices", + "type": "Microsoft.Common.CheckBox", + "visible": true, + "label": "Enable recovery services?", + "defaultValue": false, + "toolTip": "Choose to deploy backups for your solution. For a personal host pool, this will enable backups on the virtual machines. For a pooled host pool, this will enable backups on the FSLogix file share when using Azure Files." + } + ] + }, + { + "name": "monitoring", + "type": "Microsoft.Common.Section", + "label": "Monitoring", + "visible": true, + "elements": [ + { + "name": "enable", + "type": "Microsoft.Common.CheckBox", + "visible": true, + "label": "Enable monitoring for AVD Insights?", + "defaultValue": false, + "toolTip": "Deploy the required resources to enable AVD Insights." + }, + { + "name": "agent", + "type": "Microsoft.Common.DropDown", + "visible": "[steps('management').monitoring.enable]", + "label": "Monitoring agent", + "defaultValue": "Log Analytics Agent", + "toolTip": "Select the solution for the FSLogix profiles.", + "constraints": { + "required": true, + "allowedValues": [ + { + "label": "Azure Monitor Agent", + "value": "AzureMonitorAgent" + }, + { + "label": "Log Analytics Agent", + "value": "LogAnalyticsAgent" + } + ] + } + }, + { + "name": "enableSecurity", + "type": "Microsoft.Common.CheckBox", + "visible": "[and(steps('management').monitoring.enable, equals(steps('management').monitoring.agent, 'LogAnalyticsAgent'))]", + "label": "Multi-home agent for security monitoring?", + "defaultValue": false, + "toolTip": "Deploy the required configuration to multi-home the Microsoft Monitoring Agent for security monitoring." + }, + { + "name": "logAnalyticsWorkspace", + "type": "Microsoft.Solutions.ResourceSelector", + "label": "Existing Log Analytics Workspace for Security", + "visible": "[and(and(steps('management').monitoring.enable, equals(steps('management').monitoring.agent, 'LogAnalyticsAgent')),steps('management').monitoring.enableSecurity)]", + "resourceType": "Microsoft.OperationalInsights/workspaces", + "toolTip": "Select the log analytics workspace used for collecting security data for Sentinel or Defender for Cloud. This is required to multihome the Microsoft Monitoring Agent.", + "options": {} } - ] + ] + } + ] }, - "outputs": { - "parameters": { - "activeDirectorySolution": "[if(and(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'), steps('hosts').identity.intune), 'MicrosoftEntraIdIntuneEnrollment', steps('hosts').identity.solution)]", - "artifactsContainerName": "[steps('artifacts').storage.container]", - "artifactsStorageAccountResourceId": "[steps('artifacts').storage.storageAccount.id]", - "availability": "[steps('hosts').virtualMachine.availability]", - "avdAgentMsiName": "[steps('artifacts').files.avdAgentMsiName.blobName]", - "avdAgentBootLoaderMsiName": "[steps('artifacts').files.avdAgentBootLoaderMsiName.blobName]", - "avdObjectId": "[if(empty(steps('basics').servicePrincipalApi), steps('management').startVmOnConnect.objectId, first(map(steps('basics').servicePrincipalApi.value, (item) => item.id)))]", - "azurePowerShellModuleMsiName": "[steps('artifacts').files.azurePowerShellModuleMsiName.blobName]", - "azureNetAppFilesSubnetAddressPrefix": "[if(and(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location), equals(steps('userProfiles').storage.service, 'AzureNetAppFiles')), steps('networking').controlPlane.subnetAddressCidrRangeAnf, steps('networking').hosts.subnetAddressCidrRangeAnf)]", - "customRdpProperty": "[steps('controlPlane').hostPool.customRdpProperties]", - "desktopFriendlyName": "[steps('controlPlane').desktopFriendlyName]", - "disableBgpRoutePropagation": "[steps('networking').controlPlane.disableBgpRoutePropagation]", - "diskSku": "[steps('hosts').virtualMachine.diskSku]", - "domainJoinPassword": "[steps('hosts').domainJoinCredentials.password]", - "domainJoinUserPrincipalName": "[steps('hosts').domainJoinCredentials.userPrincipalName]", - "domainName": "[steps('hosts').identity.domainName]", - "drainMode": "[steps('management').drainMode.enable]", - "environmentShortName": "[steps('basics').naming.environment]", - "fslogixShareSizeInGB": "[if(equals(steps('userProfiles').profileSolution, 'local'), 100, steps('userProfiles').storage.fileShareSize)]", - "fslogixContainerType": "[steps('userProfiles').storage.fslogixContainerType]", - "fslogixStorageService": "[if(equals(steps('userProfiles').profileSolution, 'local'), 'None', concat( steps('userProfiles').storage.service, ' ', steps('userProfiles').storage.sku))]", - "hostPoolPublicNetworkAccess": "[steps('controlPlane').hostPool.publicNetworkAccess]", - "hostPoolType": "[if(equals(steps('controlPlane').hostPool.type, 'Pooled'), concat(steps('controlPlane').hostPool.type, ' ', steps('controlPlane').hostPool.loadBalancerAlgorithm), concat(steps('controlPlane').hostPool.type, ' ', steps('controlPlane').hostPool.assignmentType))]", - "hubAzureFirewallResourceId": "[steps('basics').hub.azureFirewall]", - "hubSubnetResourceId": "[steps('controlPlane').workspace.subnet]", - "hubVirtualNetworkResourceId": "[steps('basics').hub.virtualNetwork]", - "identifier": "[steps('basics').naming.identifier]", - "imageDefinitionResourceId": "[if(equals(steps('hosts').image.source, 'gallery'), steps('hosts').image.galleryImage.id, '')]", - "imageOffer": "[steps('hosts').image.offer]", - "imagePublisher": "MicrosoftWindowsDesktop", - "imageSku": "[steps('hosts').image.sku]", - "locationControlPlane": "[steps('controlPlane').controlPlane.location]", - "locationVirtualMachines": "[steps('basics').scope.location.name]", - "logAnalyticsWorkspaceRetention": 30, - "logAnalyticsWorkspaceSku": "PerGB2018", - "maxSessionLimit": "[if(equals(steps('controlPlane').hostPool.type, 'Pooled'), steps('controlPlane').hostPool.maxSessions, 1)]", - "monitoring": "[steps('management').monitoring.enable]", - "organizationalUnitPath": "[if(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'), '', steps('hosts').identity.ouPath)]", - "recoveryServices": "[steps('management').backup.recoveryServices]", - "scalingBeginPeakTime": "[if(steps('management').scaling.enable, steps('management').scaling.beginPeakTime, '9:00')]", - "scalingEndPeakTime": "[if(steps('management').scaling.enable, steps('management').scaling.endPeakTime, '17:00')]", - "scalingLimitSecondsToForceLogOffUser": "[if(steps('management').scaling.enable, steps('management').scaling.forceLogOff, '0')]", - "scalingMinimumNumberOfRdsh": "[if(steps('management').scaling.enable, steps('management').scaling.minimumHosts, '0')]", - "scalingSessionThresholdPerCPU": "[if(steps('management').scaling.enable, steps('management').scaling.cpuThreshold, '1')]", - "scalingTool": "[steps('management').scaling.enable]", - "securityLogAnalyticsWorkspaceResourceId": "[if(steps('management').monitoring.enableSecurity, steps('management').monitoring.logAnalyticsWorkspace.id, '')]", - "securityPrincipals": "[if(empty(steps('controlPlane').assignment.groupsApi), steps('controlPlane').assignment.groupsGrid, steps('controlPlane').assignment.groupsDropDown)]", - "sessionHostCount": "[steps('hosts').virtualMachine.count]", - "sessionHostIndex": 0, - "stampIndex": "[steps('basics').naming.stampIndex]", - "storageCount": "[if(equals(steps('userProfiles').profileSolution, 'local'), 0, if(empty(steps('controlPlane').assignment.groupsApi), length(steps('controlPlane').assignment.groupsGrid), length(steps('controlPlane').assignment.groupsDropDown)))]", - "storageIndex": 0, - "subnetAddressPrefixes": "[if(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location), parse(concat('[\"', steps('networking').controlPlane.subnetAddressCidrRangeWorkload, '\"]')), parse(concat('[\"', steps('networking').controlPlane.subnetAddressCidrRangeWorkload, '\", \"', steps('networking').hosts.subnetAddressCidrRangeWorkload, '\"]')))]", - "tags": "[steps('tags').tags]", - "validationEnvironment": "[steps('controlPlane').hostPool.validation]", - "virtualMachineMonitoringAgent": "[steps('management').monitoring.agent]", - "virtualMachinePassword": "[steps('hosts').localAdminCredentials.password]", - "virtualMachineSize": "[steps('hosts').virtualMachine.size]", - "virtualMachineUsername": "[steps('hosts').localAdminCredentials.username]", - "virtualNetworkAddressPrefixes": "[if(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location), parse(concat('[\"', steps('networking').controlPlane.virtualNetworkAddressCidrRange, '\"]')), parse(concat('[\"', steps('networking').controlPlane.virtualNetworkAddressCidrRange, '\", \"', steps('networking').hosts.virtualNetworkAddressCidrRange, '\"]')))]", - "workspaceFriendlyName": "[steps('controlPlane').workspace.friendlyName]", - "workspacePublicNetworkAccess": "[steps('controlPlane').workspace.publicNetworkAccess]" + { + "name": "artifacts", + "label": "Artifacts", + "elements": [ + { + "name": "storage", + "type": "Microsoft.Common.Section", + "label": "Artifacts storage", + "elements": [ + { + "name": "description", + "type": "Microsoft.Common.TextBlock", + "visible": true, + "options": { + "text": "Select the storage account and container that hosts the artifacts required to deploy this solution." + } + }, + { + "name": "storageAccount", + "type": "Microsoft.Solutions.ResourceSelector", + "label": "Existing storage account", + "resourceType": "Microsoft.Storage/storageAccounts", + "options": { + "filter": { + "subscription": "all", + "location": "all" + } + } + }, + { + "name": "containerApi", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(steps('artifacts').storage.storageAccount.id, '/blobServices/default/containers?api-version=2023-01-01')]" + } + }, + { + "name": "container", + "type": "Microsoft.Common.DropDown", + "visible": true, + "label": "Existing container", + "defaultValue": "", + "filter": true, + "toolTip": "Select the existing container containing the required artifacts.", + "constraints": { + "required": true, + "allowedValues": "[map(steps('artifacts').storage.containerApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]" + } + } + ], + "visible": true }, - "kind": "Subscription", - "location": "[steps('basics').scope.location.name]", - "subscriptionId": "[steps('basics').scope.subscription.id]" + { + "name": "files", + "type": "Microsoft.Common.Section", + "label": "Artifacts file names", + "elements": [ + { + "name": "description", + "type": "Microsoft.Common.TextBlock", + "visible": true, + "options": { + "text": "Input the file names for the required artifacts from the specified Azure Blobs container above." + } + }, + { + "name": "avdAgentMsiName", + "type": "Microsoft.Common.TextBox", + "label": "Azure Virtual Desktop Agent (.msi)", + "defaultValue": "Microsoft.RDInfra.RDAgent.Installer-x64-1.0.7909.2600.msi", + "toolTip": "Input the file / blob name for the AVD Agent installer.", + "placeholder": "", + "multiLine": false, + "constraints": { + "required": true, + "validations": [ + { + "isValid": "[endsWith(steps('artifacts').files.avdAgentMsiName, '.msi')]", + "message": "The file name must end with '.msi'." + } + ] + }, + "visible": true + }, + { + "name": "avdAgentBootLoaderMsiName", + "type": "Microsoft.Common.TextBox", + "label": "Azure Virtual Desktop Boot Loader (.msi)", + "defaultValue": "Microsoft.RDInfra.RDAgentBootLoader.Installer-x64 (5).msi", + "toolTip": "Input the file / blob name for the AVD Boot Loader installer.", + "placeholder": "", + "multiLine": false, + "constraints": { + "required": true, + "validations": [ + { + "isValid": "[endsWith(steps('artifacts').files.avdAgentBootLoaderMsiName, '.msi')]", + "message": "The file name must end with '.msi'." + } + ] + }, + "visible": true + }, + { + "name": "azurePowerShellModuleMsiName", + "type": "Microsoft.Common.TextBox", + "label": "Azure PowerShell Module Installer (.msi)", + "defaultValue": "Az-Cmdlets-10.2.0.37547-x64.msi", + "toolTip": "Input the file / blob name for the Azure PowerShell Module installer.", + "placeholder": "", + "multiLine": false, + "constraints": { + "required": true, + "validations": [ + { + "isValid": "[endsWith(steps('artifacts').files.azurePowerShellModuleMsiName, '.msi')]", + "message": "The file name must end with '.msi'." + } + ] + }, + "visible": true + }, + { + "name": "filesWarning", + "type": "Microsoft.Common.InfoBox", + "visible": true, + "options": { + "style": "Warning", + "text": "The files listed above are prerequisites for this solution. They must be downloaded and staged in Azure Blob storage. Once staged, ensure the file names listed above match the file names in Azure Blob storage since the names can change over time. Refer to the following link to download the files:", + "uri": { + "text": "https://github.com/Azure/missionlz/blob/main/src/bicep/add-ons/azureVirtualDesktop/docs/prerequisites.md#required", + "value": "https://github.com/Azure/missionlz/blob/main/src/bicep/add-ons/azureVirtualDesktop/docs/prerequisites.md#required" + } + } + } + ] + } + ] + }, + { + "name": "tags", + "label": "Tags", + "elements": [ + { + "name": "tags", + "type": "Microsoft.Common.TagsByResource", + "resources": [ + "Microsoft.Automation/automationAccounts", + "Microsoft.Compute/availabilitySets", + "Microsoft.Compute/diskAccesses", + "Microsoft.Compute/diskEncryptionSets", + "Microsoft.Compute/virtualMachines", + "Microsoft.DesktopVirtualization/applicationGroups", + "Microsoft.DesktopVirtualization/hostPools", + "Microsoft.Insights/dataCollectionRules", + "Microsoft.KeyVault/vaults", + "Microsoft.ManagedIdentity/userAssignedIdentities", + "Microsoft.NetApp/netAppAccounts", + "Microsoft.Network/networkInterfaces", + "Microsoft.Network/networkSecurityGroups", + "Microsoft.Network/privateEndpoints", + "Microsoft.Network/routeTables", + "Microsoft.Network/virtualNetworks", + "Microsoft.OperationalInsights/workspaces", + "Microsoft.RecoveryServices/vaults", + "Microsoft.Resources/resourceGroups", + "Microsoft.Storage/storageAccounts" + ] + } + ] } + ] + }, + "outputs": { + "parameters": { + "activeDirectorySolution": "[if(and(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'), steps('hosts').identity.intune), 'MicrosoftEntraIdIntuneEnrollment', steps('hosts').identity.solution)]", + "artifactsContainerName": "[steps('artifacts').storage.container]", + "artifactsStorageAccountResourceId": "[steps('artifacts').storage.storageAccount.id]", + "availability": "[steps('hosts').virtualMachine.availability]", + "avdAgentMsiName": "[steps('artifacts').files.avdAgentMsiName.blobName]", + "avdAgentBootLoaderMsiName": "[steps('artifacts').files.avdAgentBootLoaderMsiName.blobName]", + "avdObjectId": "[if(empty(steps('basics').servicePrincipalApi), steps('management').startVmOnConnect.objectId, first(map(steps('basics').servicePrincipalApi.value, (item) => item.id)))]", + "azurePowerShellModuleMsiName": "[steps('artifacts').files.azurePowerShellModuleMsiName.blobName]", + "azureNetAppFilesSubnetAddressPrefix": "[if(and(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location), equals(steps('userProfiles').storage.service, 'AzureNetAppFiles')), steps('networking').controlPlane.subnetAddressCidrRangeAnf, steps('networking').hosts.subnetAddressCidrRangeAnf)]", + "customRdpProperty": "[steps('controlPlane').hostPool.customRdpProperties]", + "desktopFriendlyName": "[steps('controlPlane').friendlyNames.desktop]", + "disableBgpRoutePropagation": "[steps('networking').controlPlane.disableBgpRoutePropagation]", + "diskSku": "[if(equals(steps('basics').scenario.profile, 'arcGisPro'), 'Premium_LRS', steps('hosts').virtualMachine.diskSku)]", + "domainJoinPassword": "[steps('hosts').domainJoinCredentials.password]", + "domainJoinUserPrincipalName": "[steps('hosts').domainJoinCredentials.userPrincipalName]", + "domainName": "[steps('hosts').identity.domainName]", + "drainMode": "[steps('management').drainMode.enable]", + "environmentAbbreviation": "[steps('basics').naming.environment]", + "fslogixShareSizeInGB": "[if(equals(steps('userProfiles').profileSolution, 'local'), 100, steps('userProfiles').storage.fileShareSize)]", + "fslogixContainerType": "[steps('userProfiles').storage.fslogixContainerType]", + "fslogixStorageService": "[if(equals(steps('userProfiles').profileSolution, 'local'), 'None', concat( steps('userProfiles').storage.service, ' ', steps('userProfiles').storage.sku))]", + "hostPoolPublicNetworkAccess": "[steps('controlPlane').hostPool.publicNetworkAccess]", + "hostPoolType": "[if(equals(steps('controlPlane').hostPool.type, 'Pooled'), 'Pooled DepthFirst', 'Personal Automatic')]", + "hubAzureFirewallResourceId": "[steps('basics').hub.azureFirewall]", + "hubSubnetResourceId": "[steps('controlPlane').workspace.subnet]", + "hubVirtualNetworkResourceId": "[steps('basics').hub.virtualNetwork]", + "identifier": "[steps('basics').naming.identifier]", + "imageDefinitionResourceId": "[if(equals(steps('hosts').image.source, 'gallery'), steps('hosts').image.galleryImage.id, '')]", + "imageOffer": "[steps('hosts').image.offer]", + "imagePublisher": "MicrosoftWindowsDesktop", + "imageSku": "[steps('hosts').image.sku]", + "locationControlPlane": "[steps('controlPlane').controlPlane.location]", + "locationVirtualMachines": "[steps('basics').scope.location.name]", + "logAnalyticsWorkspaceRetention": 30, + "logAnalyticsWorkspaceSku": "PerGB2018", + "monitoring": "[steps('management').monitoring.enable]", + "organizationalUnitPath": "[if(equals(steps('hosts').identity.solution, 'MicrosoftEntraId'), '', steps('hosts').identity.ouPath)]", + "recoveryServices": "[steps('management').backup.recoveryServices]", + "scalingBeginPeakTime": "[if(steps('management').scaling.enable, steps('management').scaling.beginPeakTime, '9:00')]", + "scalingEndPeakTime": "[if(steps('management').scaling.enable, steps('management').scaling.endPeakTime, '17:00')]", + "scalingLimitSecondsToForceLogOffUser": "[if(steps('management').scaling.enable, steps('management').scaling.forceLogOff, '0')]", + "scalingMinimumNumberOfRdsh": "[if(steps('management').scaling.enable, steps('management').scaling.minimumHosts, '0')]", + "scalingSessionThresholdPerCPU": "[if(steps('management').scaling.enable, steps('management').scaling.cpuThreshold, '1')]", + "scalingTool": "[steps('management').scaling.enable]", + "securityLogAnalyticsWorkspaceResourceId": "[if(steps('management').monitoring.enableSecurity, steps('management').monitoring.logAnalyticsWorkspace.id, '')]", + "securityPrincipals": "[if(empty(steps('controlPlane').assignment.groupsApi), steps('controlPlane').assignment.groupsGrid, steps('controlPlane').assignment.groupsDropDown)]", + "sessionHostCount": "[steps('hosts').virtualMachine.count]", + "sessionHostIndex": 0, + "stampIndex": "[steps('basics').naming.stampIndex]", + "storageCount": "[if(equals(steps('userProfiles').profileSolution, 'local'), 0, if(empty(steps('controlPlane').assignment.groupsApi), length(steps('controlPlane').assignment.groupsGrid), length(steps('controlPlane').assignment.groupsDropDown)))]", + "storageIndex": 0, + "subnetAddressPrefixes": "[if(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location), parse(concat('[\"', steps('networking').controlPlane.subnetAddressCidrRangeWorkload, '\"]')), parse(concat('[\"', steps('networking').controlPlane.subnetAddressCidrRangeWorkload, '\", \"', steps('networking').hosts.subnetAddressCidrRangeWorkload, '\"]')))]", + "tags": "[steps('tags').tags]", + "usersPerCore": "[if(equals(steps('controlPlane').hostPool.type, 'Pooled'), steps('controlPlane').hostPool.workloadType, 1)]", + "validationEnvironment": "[steps('controlPlane').hostPool.validation]", + "virtualMachineMonitoringAgent": "[steps('management').monitoring.agent]", + "virtualMachinePassword": "[steps('hosts').localAdminCredentials.password]", + "virtualMachineSize": "[if(equals(steps('basics').scenario.profile, 'arcGisPro'), steps('hosts').virtualMachine.sizeArcGisPro, steps('hosts').virtualMachine.sizeGeneric)]", + "virtualMachineUsername": "[steps('hosts').localAdminCredentials.username]", + "virtualMachineVirtualCpuCount": "[first(map(filter(steps('basics').scenario.virtualMachineSizesApi.value, (item) => equals(item.name, steps('hosts').virtualMachine.size)), (item) => item.numberOfCores))]", + "virtualNetworkAddressPrefixes": "[if(equals(steps('basics').scope.location.name, steps('controlPlane').controlPlane.location), parse(concat('[\"', steps('networking').controlPlane.virtualNetworkAddressCidrRange, '\"]')), parse(concat('[\"', steps('networking').controlPlane.virtualNetworkAddressCidrRange, '\", \"', steps('networking').hosts.virtualNetworkAddressCidrRange, '\"]')))]", + "workspaceFriendlyName": "[steps('controlPlane').friendlyNames.workspace]", + "workspacePublicNetworkAccess": "[steps('controlPlane').workspace.publicNetworkAccess]" + }, + "kind": "Subscription", + "location": "[steps('basics').scope.location.name]", + "subscriptionId": "[steps('basics').scope.subscription.id]" } + } } \ No newline at end of file