diff --git a/readme.md b/readme.md index e91921c3b..9cb13d795 100644 --- a/readme.md +++ b/readme.md @@ -48,11 +48,11 @@ The brownfield section contains templates to deploy additional features for Azur - [App Attach Tools VM](./workload/bicep/brownfield/appAttachToolsVM/Readme.md) - [Auto Increase Premium File Share Quota](./workload/bicep/brownfield/autoIncreasePremiumFileShareQuota/readme.md) - [AVD Session Host Replacer](https://github.com/Azure/AVDSessionHostReplacer) +- [Custom Image Templates Prerequisites](./workload/bicep/brownfield/customImageTemplatesPrerequisites/readme.md) - [Migrate monitoring agent from MMA to AMA](./workload/scripts/Monitoring/readme.md) - [Scaling Tool](./workload/bicep/brownfield/scalingTool/readme.md) - [Start VM On Connect](./workload/bicep/brownfield/startVmOnConnect/readme.md) - ### Monitoring workbooks - [Deep Insights Workbook](./workload/workbooks/deepInsightsWorkbook/readme.md) @@ -63,12 +63,12 @@ The brownfield section contains templates to deploy additional features for Azur [Getting Started](/workload/docs/getting-started-custom-image-build.md) deploying a custom image based on the latest version of the Azure marketplace image to an Azure Compute Gallery. The following images are offered: - - Windows 10 22H2 (Gen 2) - - Windows 11 22H2 (Gen 2) - - Windows 11 23H2 (Gen 2) - - Windows 10 22H2 with O365 (Gen 2) - - Windows 11 22H2 with O365 (Gen 2) - - Windows 11 23H2 with O365 (Gen 2) +- Windows 10 22H2 (Gen 2) +- Windows 11 22H2 (Gen 2) +- Windows 11 23H2 (Gen 2) +- Windows 10 22H2 with O365 (Gen 2) +- Windows 11 22H2 with O365 (Gen 2) +- Windows 11 23H2 with O365 (Gen 2) You can also select to enable the Trusted Launch or Confidential VM security type feature on the Azure Compute Gallery image definition. diff --git a/workload/arm/brownfield/deployCustomImageTemplatesPrerequisites.json b/workload/arm/brownfield/deployCustomImageTemplatesPrerequisites.json new file mode 100644 index 000000000..8421c4272 --- /dev/null +++ b/workload/arm/brownfield/deployCustomImageTemplatesPrerequisites.json @@ -0,0 +1,658 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "16157207237605124434" + } + }, + "parameters": { + "computeGalleryName": { + "type": "string", + "metadata": { + "description": "The name of the compute gallery for managing the images." + } + }, + "deploymentScriptName": { + "type": "string", + "metadata": { + "description": "The name of the deployment script for configuring an existing subnet." + } + }, + "existingResourceGroup": { + "type": "bool", + "metadata": { + "description": "Determine whether to use an existing resource group." + } + }, + "existingVirtualNetworkResourceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The resource ID of an existing virtual network." + } + }, + "imageDefinitionIsAcceleratedNetworkSupported": { + "type": "bool", + "metadata": { + "description": "Indicates whether the image definition supports accelerated networking." + } + }, + "imageDefinitionIsHibernateSupported": { + "type": "bool", + "metadata": { + "description": "Indicates whether the image definition supports hibernation." + } + }, + "imageDefinitionName": { + "type": "string", + "metadata": { + "description": "The name of the Image Definition for the Shared Image Gallery." + } + }, + "imageDefinitionSecurityType": { + "type": "string", + "allowedValues": [ + "ConfidentialVM", + "ConfidentialVMSupported", + "Standard", + "TrustedLaunch" + ], + "metadata": { + "description": "The security type for the Image Definition." + } + }, + "imageOffer": { + "type": "string", + "metadata": { + "description": "The offer of the marketplace image." + } + }, + "imagePublisher": { + "type": "string", + "metadata": { + "description": "The publisher of the marketplace image." + } + }, + "imageSku": { + "type": "string", + "metadata": { + "description": "The SKU of the marketplace image." + } + }, + "location": { + "type": "string", + "defaultValue": "[deployment().location]", + "metadata": { + "description": "The location for the resources deployed in this solution." + } + }, + "resourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the resource group for the resources." + } + }, + "storageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the storage account for the imaging artifacts." + } + }, + "subnetName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The subnet name of an existing virtual network." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "The key-value pairs of tags for the resources." + } + }, + "time": { + "type": "string", + "defaultValue": "[utcNow('yyyyMMddhhmmss')]", + "metadata": { + "description": "DO NOT MODIFY THIS VALUE! The timestamp is needed to differentiate deployments for certain Azure resources and must be set using a parameter." + } + }, + "userAssignedIdentityName": { + "type": "string", + "metadata": { + "description": "The name for the user assigned identity" + } + } + }, + "variables": { + "varRoles": "[union(if(empty(parameters('existingVirtualNetworkResourceId')), createArray(), createArray(createObject('resourceGroup', split(parameters('existingVirtualNetworkResourceId'), '/')[4], 'name', 'Virtual Network Join', 'description', 'Allow resources to join a subnet', 'permissions', createArray(createObject('actions', createArray('Microsoft.Network/virtualNetworks/read', 'Microsoft.Network/virtualNetworks/subnets/read', 'Microsoft.Network/virtualNetworks/subnets/join/action', 'Microsoft.Network/virtualNetworks/subnets/write')))))), createArray(createObject('resourceGroup', parameters('resourceGroupName'), 'name', 'Image Template Contributor', 'description', 'Allow the creation and management of images', 'permissions', createArray(createObject('actions', createArray('Microsoft.Compute/galleries/read', 'Microsoft.Compute/galleries/images/read', 'Microsoft.Compute/galleries/images/versions/read', 'Microsoft.Compute/galleries/images/versions/write', 'Microsoft.Compute/images/read', 'Microsoft.Compute/images/write', 'Microsoft.Compute/images/delete'))))))]" + }, + "resources": [ + { + "condition": "[not(parameters('existingResourceGroup'))]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2024-03-01", + "name": "[parameters('resourceGroupName')]", + "location": "[parameters('location')]", + "tags": "[coalesce(tryGet(parameters('tags'), 'Microsoft.Resources/resourceGroups'), createObject())]", + "properties": {} + }, + { + "copy": { + "name": "roleDefinitions", + "count": "[length(range(0, length(variables('varRoles'))))]" + }, + "type": "Microsoft.Authorization/roleDefinitions", + "apiVersion": "2015-07-01", + "name": "[guid(variables('varRoles')[range(0, length(variables('varRoles')))[copyIndex()]].name, subscription().id)]", + "properties": { + "roleName": "[format('{0} ({1})', variables('varRoles')[range(0, length(variables('varRoles')))[copyIndex()]].name, subscription().subscriptionId)]", + "description": "[variables('varRoles')[range(0, length(variables('varRoles')))[copyIndex()]].description]", + "permissions": "[variables('varRoles')[range(0, length(variables('varRoles')))[copyIndex()]].permissions]", + "assignableScopes": [ + "[subscription().id]" + ] + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('ID-{0}', parameters('time'))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "name": { + "value": "[parameters('userAssignedIdentityName')]" + }, + "tags": { + "value": "[parameters('tags')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "474725451169467785" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[coalesce(tryGet(parameters('tags'), 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject())]" + } + ], + "outputs": { + "PrincipalId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2023-01-31').principalId]" + }, + "ResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name'))]" + } + } + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" + ] + }, + { + "copy": { + "name": "roleAssignments", + "count": "[length(range(0, length(variables('varRoles'))))]", + "mode": "serial", + "batchSize": 1 + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('Role-Assignment-{0}-{1}', range(0, length(variables('varRoles')))[copyIndex()], parameters('time'))]", + "resourceGroup": "[variables('varRoles')[range(0, length(variables('varRoles')))[copyIndex()]].resourceGroup]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "principalId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('ID-{0}', parameters('time'))), '2022-09-01').outputs.PrincipalId.value]" + }, + "roleDefinitionId": { + "value": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', guid(variables('varRoles')[range(0, length(variables('varRoles')))[range(0, length(variables('varRoles')))[copyIndex()]]].name, subscription().id))]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "6259838335436832030" + } + }, + "parameters": { + "principalId": { + "type": "string" + }, + "roleDefinitionId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('principalId'), parameters('roleDefinitionId'), resourceGroup().id)]", + "properties": { + "roleDefinitionId": "[parameters('roleDefinitionId')]", + "principalId": "[parameters('principalId')]", + "principalType": "ServicePrincipal" + } + } + ] + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]", + "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', guid(variables('varRoles')[range(0, length(variables('varRoles')))[range(0, length(variables('varRoles')))[copyIndex()]]].name, subscription().id))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('ID-{0}', parameters('time')))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('Compute-Gallery-{0}', parameters('time'))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "computeGalleryName": { + "value": "[parameters('computeGalleryName')]" + }, + "imageDefinitionName": { + "value": "[parameters('imageDefinitionName')]" + }, + "imageDefinitionSecurityType": { + "value": "[parameters('imageDefinitionSecurityType')]" + }, + "imageOffer": { + "value": "[parameters('imageOffer')]" + }, + "imagePublisher": { + "value": "[parameters('imagePublisher')]" + }, + "imageSku": { + "value": "[parameters('imageSku')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "imageDefinitionIsAcceleratedNetworkSupported": { + "value": "[parameters('imageDefinitionIsAcceleratedNetworkSupported')]" + }, + "imageDefinitionIsHibernateSupported": { + "value": "[parameters('imageDefinitionIsHibernateSupported')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "17477988232584003343" + } + }, + "parameters": { + "computeGalleryName": { + "type": "string" + }, + "imageDefinitionName": { + "type": "string" + }, + "imageDefinitionIsAcceleratedNetworkSupported": { + "type": "bool" + }, + "imageDefinitionIsHibernateSupported": { + "type": "bool" + }, + "imageDefinitionSecurityType": { + "type": "string" + }, + "imageOffer": { + "type": "string" + }, + "imagePublisher": { + "type": "string" + }, + "imageSku": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + } + }, + "resources": [ + { + "type": "Microsoft.Compute/galleries", + "apiVersion": "2023-07-03", + "name": "[parameters('computeGalleryName')]", + "location": "[parameters('location')]", + "tags": "[coalesce(tryGet(parameters('tags'), 'Microsoft.Compute/galleries'), createObject())]" + }, + { + "type": "Microsoft.Compute/galleries/images", + "apiVersion": "2023-07-03", + "name": "[format('{0}/{1}', parameters('computeGalleryName'), parameters('imageDefinitionName'))]", + "location": "[parameters('location')]", + "tags": "[coalesce(tryGet(parameters('tags'), 'Microsoft.Compute/galleries'), createObject())]", + "properties": { + "osType": "Windows", + "osState": "Generalized", + "hyperVGeneration": "[if(or(contains(parameters('imageSku'), '-g2'), contains(parameters('imageSku'), 'win11-')), 'V2', 'V1')]", + "identifier": { + "publisher": "[parameters('imagePublisher')]", + "offer": "[parameters('imageOffer')]", + "sku": "[parameters('imageSku')]" + }, + "features": "[if(equals(parameters('imageDefinitionSecurityType'), 'Standard'), null(), createArray(createObject('name', 'SecurityType', 'value', parameters('imageDefinitionSecurityType')), createObject('name', 'IsAcceleratedNetworkSupported', 'value', string(parameters('imageDefinitionIsAcceleratedNetworkSupported'))), createObject('name', 'IsHibernateSupported', 'value', string(parameters('imageDefinitionIsHibernateSupported')))))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/galleries', parameters('computeGalleryName'))]" + ] + } + ], + "outputs": { + "imageDefinitionResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.Compute/galleries/images', parameters('computeGalleryName'), parameters('imageDefinitionName'))]" + } + } + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]" + ] + }, + { + "condition": "[and(not(empty(parameters('subnetName'))), not(empty(parameters('existingVirtualNetworkResourceId'))))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('Network-Policy-{0}', parameters('time'))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "deploymentScriptName": { + "value": "[parameters('deploymentScriptName')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "subnetName": { + "value": "[parameters('subnetName')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "timestamp": { + "value": "[parameters('time')]" + }, + "userAssignedIdentityResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('ID-{0}', parameters('time'))), '2022-09-01').outputs.ResourceId.value]" + }, + "virtualNetworkName": { + "value": "[split(parameters('existingVirtualNetworkResourceId'), '/')[8]]" + }, + "virtualNetworkResourceGroupName": { + "value": "[split(parameters('existingVirtualNetworkResourceId'), '/')[4]]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "8044371530674251887" + } + }, + "parameters": { + "deploymentScriptName": { + "type": "string" + }, + "location": { + "type": "string" + }, + "subnetName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "timestamp": { + "type": "string" + }, + "userAssignedIdentityResourceId": { + "type": "string" + }, + "virtualNetworkName": { + "type": "string" + }, + "virtualNetworkResourceGroupName": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('deploymentScriptName')]", + "location": "[parameters('location')]", + "tags": "[coalesce(tryGet(parameters('tags'), 'Microsoft.Resources/deploymentScripts'), createObject())]", + "kind": "AzurePowerShell", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', parameters('userAssignedIdentityResourceId'))]": {} + } + }, + "properties": { + "arguments": "[format('-Subnet {0} -VirtualNetwork {1} -ResourceGroup {2}', parameters('subnetName'), parameters('virtualNetworkName'), parameters('virtualNetworkResourceGroupName'))]", + "azPowerShellVersion": "9.4", + "cleanupPreference": "Always", + "forceUpdateTag": "[parameters('timestamp')]", + "retentionInterval": "PT2H", + "scriptContent": "Param([string]$ResourceGroup, [string]$Subnet, [string]$VirtualNetwork); $VNET = Get-AzVirtualNetwork -Name $VirtualNetwork -ResourceGroupName $ResourceGroup; ($VNET | Select-Object -ExpandProperty \"Subnets\" | Where-Object {$_.Name -eq $Subnet}).privateLinkServiceNetworkPolicies = \"Disabled\"; $VNET | Set-AzVirtualNetwork", + "timeout": "PT30M" + } + } + ] + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]", + "roleAssignments", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('ID-{0}', parameters('time')))]" + ] + }, + { + "condition": "[not(empty(parameters('storageAccountName')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('Storage-Account-{0}', parameters('time'))]", + "resourceGroup": "[parameters('resourceGroupName')]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + }, + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "tags": { + "value": "[parameters('tags')]" + }, + "userAssignedIdentityPrincipalId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('ID-{0}', parameters('time'))), '2022-09-01').outputs.PrincipalId.value]" + }, + "userAssignedIdentityResourceId": { + "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('ID-{0}', parameters('time'))), '2022-09-01').outputs.ResourceId.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "12715486127819516060" + } + }, + "parameters": { + "location": { + "type": "string" + }, + "storageAccountName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "userAssignedIdentityPrincipalId": { + "type": "string" + }, + "userAssignedIdentityResourceId": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-05-01", + "name": "[parameters('storageAccountName')]", + "location": "[parameters('location')]", + "tags": "[coalesce(tryGet(parameters('tags'), 'Microsoft.Storage/storageAccounts'), createObject())]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', parameters('userAssignedIdentityResourceId'))]": {} + } + }, + "kind": "StorageV2", + "sku": { + "name": "Standard_LRS" + }, + "properties": { + "accessTier": "Cool", + "allowBlobPublicAccess": false, + "allowCrossTenantReplication": false, + "allowedCopyScope": "AAD", + "allowSharedKeyAccess": false, + "defaultToOAuthAuthentication": true, + "dnsEndpointType": "Standard", + "minimumTlsVersion": "TLS1_2", + "networkAcls": { + "bypass": "AzureServices", + "virtualNetworkRules": [], + "ipRules": [], + "defaultAction": "Allow" + }, + "publicNetworkAccess": "Enabled", + "supportsHttpsTrafficOnly": true + } + }, + { + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2023-05-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), 'default')]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" + ] + }, + { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2023-05-01", + "name": "[format('{0}/{1}/{2}', parameters('storageAccountName'), 'default', 'artifacts')]", + "properties": { + "publicAccess": "None" + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccountName'), 'default')]" + ] + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(parameters('userAssignedIdentityPrincipalId'), '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1', resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')))]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1')]", + "principalId": "[parameters('userAssignedIdentityPrincipalId')]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" + ] + } + ] + } + }, + "dependsOn": [ + "[subscriptionResourceId('Microsoft.Resources/resourceGroups', parameters('resourceGroupName'))]", + "[extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('resourceGroupName')), 'Microsoft.Resources/deployments', format('ID-{0}', parameters('time')))]" + ] + } + ] +} \ No newline at end of file diff --git a/workload/bicep/brownfield/customImageTemplatesPrerequisites/deploy.bicep b/workload/bicep/brownfield/customImageTemplatesPrerequisites/deploy.bicep new file mode 100644 index 000000000..05f1c0ff3 --- /dev/null +++ b/workload/bicep/brownfield/customImageTemplatesPrerequisites/deploy.bicep @@ -0,0 +1,209 @@ +targetScope = 'subscription' + + +// ========== // +// Parameters // +// ========== // + +@description('The name of the compute gallery for managing the images.') +param computeGalleryName string + +@description('The name of the deployment script for configuring an existing subnet.') +param deploymentScriptName string + +@description('Determine whether to use an existing resource group.') +param existingResourceGroup bool + +@description('The resource ID of an existing virtual network.') +param existingVirtualNetworkResourceId string = '' + +@description('Indicates whether the image definition supports accelerated networking.') +param imageDefinitionIsAcceleratedNetworkSupported bool + +@description('Indicates whether the image definition supports hibernation.') +param imageDefinitionIsHibernateSupported bool + +@description('The name of the Image Definition for the Shared Image Gallery.') +param imageDefinitionName string + +@allowed([ + 'ConfidentialVM' + 'ConfidentialVMSupported' + 'Standard' + 'TrustedLaunch' +]) +@description('The security type for the Image Definition.') +param imageDefinitionSecurityType string + +@description('The offer of the marketplace image.') +param imageOffer string + +@description('The publisher of the marketplace image.') +param imagePublisher string + +@description('The SKU of the marketplace image.') +param imageSku string + +@description('The location for the resources deployed in this solution.') +param location string = deployment().location + +@description('The name of the resource group for the resources.') +param resourceGroupName string = '' + +@description('The name of the storage account for the imaging artifacts.') +param storageAccountName string = '' + +@description('The subnet name of an existing virtual network.') +param subnetName string = '' + +@description('The key-value pairs of tags for the resources.') +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 time string = utcNow('yyyyMMddhhmmss') + +@description('The name for the user assigned identity') +param userAssignedIdentityName string + + +// =========== // +// Variables // +// =========== // + +var varRoles = union(empty(existingVirtualNetworkResourceId) ? [] : [ + { + resourceGroup: split(existingVirtualNetworkResourceId, '/')[4] + name: 'Virtual Network Join' + description: 'Allow resources to join a subnet' + permissions: [ + { + actions: [ + 'Microsoft.Network/virtualNetworks/read' + 'Microsoft.Network/virtualNetworks/subnets/read' + 'Microsoft.Network/virtualNetworks/subnets/join/action' + 'Microsoft.Network/virtualNetworks/subnets/write' // Required to update the private link network policy + ] + } + ] + } +], [ + { + resourceGroup: resourceGroupName + name: 'Image Template Contributor' + description: 'Allow the creation and management of images' + permissions: [ + { + actions: [ + 'Microsoft.Compute/galleries/read' + 'Microsoft.Compute/galleries/images/read' + 'Microsoft.Compute/galleries/images/versions/read' + 'Microsoft.Compute/galleries/images/versions/write' + 'Microsoft.Compute/images/read' + 'Microsoft.Compute/images/write' + 'Microsoft.Compute/images/delete' + ] + } + ] + } +]) + + +// =========== // +// Deployments // +// =========== // + +// Resource group +resource rg 'Microsoft.Resources/resourceGroups@2024-03-01' = if (!existingResourceGroup) { + name: resourceGroupName + location: location + tags: tags[?'Microsoft.Resources/resourceGroups'] ?? {} + properties: {} +} + +// Role definitions +resource roleDefinitions 'Microsoft.Authorization/roleDefinitions@2015-07-01' = [for i in range(0, length(varRoles)): { + name: guid(varRoles[i].name, subscription().id) + properties: { + roleName: '${varRoles[i].name} (${subscription().subscriptionId})' + description: varRoles[i].description + permissions: varRoles[i].permissions + assignableScopes: [ + subscription().id + ] + } +}] + +// User assigned identity +module userAssignedIdentity 'modules/userAssignedIdentity.bicep' = { + name: 'ID-${time}' + scope: rg + params: { + location: location + name: userAssignedIdentityName + tags: tags + } +} + +// Role assignments +@batchSize(1) +module roleAssignments 'modules/roleAssignment.bicep' = [for i in range(0, length(varRoles)): { + name: 'Role-Assignment-${i}-${time}' + scope: resourceGroup(varRoles[i].resourceGroup) + params: { + principalId: userAssignedIdentity.outputs.PrincipalId + roleDefinitionId: roleDefinitions[i].id + } + dependsOn: [ + rg + ] +}] + +// Compute gallery with image definition +module computeGallery 'modules/computeGallery.bicep' = { + name: 'Compute-Gallery-${time}' + scope: rg + params: { + computeGalleryName: computeGalleryName + imageDefinitionName: imageDefinitionName + imageDefinitionSecurityType: imageDefinitionSecurityType + imageOffer: imageOffer + imagePublisher: imagePublisher + imageSku: imageSku + location: location + tags: tags + imageDefinitionIsAcceleratedNetworkSupported: imageDefinitionIsAcceleratedNetworkSupported + imageDefinitionIsHibernateSupported: imageDefinitionIsHibernateSupported + } +} + +// Disables the network policy for the subnet +module networkPolicy 'modules/networkPolicy.bicep' = if (!(empty(subnetName)) && !(empty(existingVirtualNetworkResourceId))) { + name: 'Network-Policy-${time}' + scope: rg + params: { + deploymentScriptName: deploymentScriptName + location: location + subnetName: subnetName + tags: tags + timestamp: time + userAssignedIdentityResourceId: userAssignedIdentity.outputs.ResourceId + virtualNetworkName: split(existingVirtualNetworkResourceId, '/')[8] + virtualNetworkResourceGroupName: split(existingVirtualNetworkResourceId, '/')[4] + } + dependsOn: [ + roleAssignments + ] +} + +// Storage account with blob container +module storage 'modules/storageAccount.bicep' = if (!empty(storageAccountName)) { + name: 'Storage-Account-${time}' + scope: rg + params: { + location: location + storageAccountName: storageAccountName + tags: tags + userAssignedIdentityPrincipalId: userAssignedIdentity.outputs.PrincipalId + userAssignedIdentityResourceId: userAssignedIdentity.outputs.ResourceId + } +} diff --git a/workload/bicep/brownfield/customImageTemplatesPrerequisites/modules/computeGallery.bicep b/workload/bicep/brownfield/customImageTemplatesPrerequisites/modules/computeGallery.bicep new file mode 100644 index 000000000..be9ea723d --- /dev/null +++ b/workload/bicep/brownfield/customImageTemplatesPrerequisites/modules/computeGallery.bicep @@ -0,0 +1,67 @@ + + +// ========== // +// Parameters // +// ========== // + +param computeGalleryName string +param imageDefinitionName string +param imageDefinitionIsAcceleratedNetworkSupported bool +param imageDefinitionIsHibernateSupported bool +param imageDefinitionSecurityType string +param imageOffer string +param imagePublisher string +param imageSku string +param location string +param tags object + + +// =========== // +// Deployments // +// =========== // + +resource gallery 'Microsoft.Compute/galleries@2023-07-03' = { + name: computeGalleryName + location: location + tags: tags[?'Microsoft.Compute/galleries'] ?? {} +} + +resource image 'Microsoft.Compute/galleries/images@2023-07-03' = { + parent: gallery + name: imageDefinitionName + location: location + tags: tags[?'Microsoft.Compute/galleries'] ?? {} + properties: { + osType: 'Windows' + osState: 'Generalized' + hyperVGeneration: contains(imageSku, '-g2') || contains(imageSku, 'win11-') ? 'V2' : 'V1' + identifier: { + publisher: imagePublisher + offer: imageOffer + sku: imageSku + } + features: imageDefinitionSecurityType == 'Standard' + ? null + : [ + { + name: 'SecurityType' + value: imageDefinitionSecurityType + } + { + name: 'IsAcceleratedNetworkSupported' + value: string(imageDefinitionIsAcceleratedNetworkSupported) + } + { + name: 'IsHibernateSupported' + value: string(imageDefinitionIsHibernateSupported) + } + ] + } +} + + +// =========== // +// Outputs // +// =========== // + +output imageDefinitionResourceId string = image.id diff --git a/workload/bicep/brownfield/customImageTemplatesPrerequisites/modules/networkPolicy.bicep b/workload/bicep/brownfield/customImageTemplatesPrerequisites/modules/networkPolicy.bicep new file mode 100644 index 000000000..4b2eb6735 --- /dev/null +++ b/workload/bicep/brownfield/customImageTemplatesPrerequisites/modules/networkPolicy.bicep @@ -0,0 +1,41 @@ + + +// ========== // +// Parameters // +// ========== // + +param deploymentScriptName string +param location string +param subnetName string +param tags object +param timestamp string +param userAssignedIdentityResourceId string +param virtualNetworkName string +param virtualNetworkResourceGroupName string + + +// =========== // +// Deployments // +// =========== // + +resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = { + name: deploymentScriptName + location: location + tags: tags[?'Microsoft.Resources/deploymentScripts'] ?? {} + kind: 'AzurePowerShell' + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${userAssignedIdentityResourceId}': {} + } + } + properties: { + arguments: '-Subnet ${subnetName} -VirtualNetwork ${virtualNetworkName} -ResourceGroup ${virtualNetworkResourceGroupName}' + azPowerShellVersion: '9.4' + cleanupPreference: 'Always' + forceUpdateTag: timestamp + retentionInterval: 'PT2H' + scriptContent: 'Param([string]$ResourceGroup, [string]$Subnet, [string]$VirtualNetwork); $VNET = Get-AzVirtualNetwork -Name $VirtualNetwork -ResourceGroupName $ResourceGroup; ($VNET | Select-Object -ExpandProperty "Subnets" | Where-Object {$_.Name -eq $Subnet}).privateLinkServiceNetworkPolicies = "Disabled"; $VNET | Set-AzVirtualNetwork' + timeout: 'PT30M' + } +} diff --git a/workload/bicep/brownfield/customImageTemplatesPrerequisites/modules/roleAssignment.bicep b/workload/bicep/brownfield/customImageTemplatesPrerequisites/modules/roleAssignment.bicep new file mode 100644 index 000000000..2ea21971f --- /dev/null +++ b/workload/bicep/brownfield/customImageTemplatesPrerequisites/modules/roleAssignment.bicep @@ -0,0 +1,22 @@ + + +// ========== // +// Parameters // +// ========== // + +param principalId string +param roleDefinitionId string + + +// =========== // +// Deployments // +// =========== // + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(principalId, roleDefinitionId, resourceGroup().id) + properties: { + roleDefinitionId: roleDefinitionId + principalId: principalId + principalType: 'ServicePrincipal' + } +} diff --git a/workload/bicep/brownfield/customImageTemplatesPrerequisites/modules/storageAccount.bicep b/workload/bicep/brownfield/customImageTemplatesPrerequisites/modules/storageAccount.bicep new file mode 100644 index 000000000..a8e944518 --- /dev/null +++ b/workload/bicep/brownfield/customImageTemplatesPrerequisites/modules/storageAccount.bicep @@ -0,0 +1,73 @@ + + +// ========== // +// Parameters // +// ========== // + +param location string +param storageAccountName string +param tags object +param userAssignedIdentityPrincipalId string +param userAssignedIdentityResourceId string + + +// =========== // +// Deployments // +// =========== // + +resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = { + name: storageAccountName + location: location + tags: tags[?'Microsoft.Storage/storageAccounts'] ?? {} + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${userAssignedIdentityResourceId}': {} + } + } + kind: 'StorageV2' + sku: { + name: 'Standard_LRS' + } + properties: { + accessTier: 'Cool' + allowBlobPublicAccess: false + allowCrossTenantReplication: false + allowedCopyScope: 'AAD' + allowSharedKeyAccess: false + defaultToOAuthAuthentication: true + dnsEndpointType: 'Standard' + minimumTlsVersion: 'TLS1_2' + networkAcls: { + bypass: 'AzureServices' + virtualNetworkRules: [] + ipRules: [] + defaultAction: 'Allow' + } + publicNetworkAccess: 'Enabled' + supportsHttpsTrafficOnly: true + } +} + +resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2023-05-01' = { + parent: storageAccount + name: 'default' +} + +resource container 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-05-01' = { + parent: blobService + name: 'artifacts' + properties: { + publicAccess: 'None' + } +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + name: guid(userAssignedIdentityPrincipalId, '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1', storageAccount.id) + scope: storageAccount + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', '2a2b9908-6ea1-4ae2-8e65-a410df84e7d1') // Storage Blob Data Reader + principalId: userAssignedIdentityPrincipalId + principalType: 'ServicePrincipal' + } +} diff --git a/workload/bicep/brownfield/customImageTemplatesPrerequisites/modules/userAssignedIdentity.bicep b/workload/bicep/brownfield/customImageTemplatesPrerequisites/modules/userAssignedIdentity.bicep new file mode 100644 index 000000000..9339a41c3 --- /dev/null +++ b/workload/bicep/brownfield/customImageTemplatesPrerequisites/modules/userAssignedIdentity.bicep @@ -0,0 +1,28 @@ + + +// ========== // +// Parameters // +// ========== // + +param location string +param name string +param tags object + + +// =========== // +// Deployments // +// =========== // + +resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: name + location: location + tags: tags[?'Microsoft.ManagedIdentity/userAssignedIdentities'] ?? {} +} + + +// =========== // +// Outputs // +// =========== // + +output PrincipalId string = userAssignedIdentity.properties.principalId +output ResourceId string = userAssignedIdentity.id diff --git a/workload/bicep/brownfield/customImageTemplatesPrerequisites/parameters/custom-image-templates-prerequisites.parameters.all.json b/workload/bicep/brownfield/customImageTemplatesPrerequisites/parameters/custom-image-templates-prerequisites.parameters.all.json new file mode 100644 index 000000000..cb73091e8 --- /dev/null +++ b/workload/bicep/brownfield/customImageTemplatesPrerequisites/parameters/custom-image-templates-prerequisites.parameters.all.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "computeGalleryName": { + "value": "gal_cit_avd" + }, + "deploymentScriptName": { + "value": "ds-cit-avd" + }, + "existingResourceGroup": { + "value": false + }, + "existingVirtualNetworkResourceId": { + "value": "<>" + }, + "imageDefinitionIsAcceleratedNetworkSupported": { + "value": false + }, + "imageDefinitionIsHibernateSupported": { + "value": false + }, + "imageDefinitionName": { + "value": "win11-23h2-avd" + }, + "imageDefinitionSecurityType": { + "value": "TrustedLaunch" + }, + "imageOffer": { + "value": "Windows-11" + }, + "imagePublisher": { + "value": "MicrosoftWindowsDesktop" + }, + "imageSku": { + "value": "win11-23h2-avd" + }, + "location": { + "value": "eastus" + }, + "resourceGroupName": { + "value": "rg-cit-avd" + }, + "storageAccountName": { + "value": "sacitavd" + }, + "subnetName": { + "value": "imaging" + }, + "tags": { + "value": {} + }, + "userAssignedIdentityName": { + "value": "id-cit-avd" + } + } +} \ No newline at end of file diff --git a/workload/bicep/brownfield/customImageTemplatesPrerequisites/parameters/custom-image-templates-prerequisites.parameters.min.json b/workload/bicep/brownfield/customImageTemplatesPrerequisites/parameters/custom-image-templates-prerequisites.parameters.min.json new file mode 100644 index 000000000..2504035a2 --- /dev/null +++ b/workload/bicep/brownfield/customImageTemplatesPrerequisites/parameters/custom-image-templates-prerequisites.parameters.min.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "computeGalleryName": { + "value": "gal_cit_avd" + }, + "deploymentScriptName": { + "value": "ds-cit-avd" + }, + "existingResourceGroup": { + "value": false + }, + "imageDefinitionIsAcceleratedNetworkSupported": { + "value": false + }, + "imageDefinitionIsHibernateSupported": { + "value": false + }, + "imageDefinitionName": { + "value": "win11-23h2-avd" + }, + "imageDefinitionSecurityType": { + "value": "TrustedLaunch" + }, + "imageOffer": { + "value": "Windows-11" + }, + "imagePublisher": { + "value": "MicrosoftWindowsDesktop" + }, + "imageSku": { + "value": "win11-23h2-avd" + }, + "userAssignedIdentityName": { + "value": "id-cit-avd" + } + } +} \ No newline at end of file diff --git a/workload/bicep/brownfield/customImageTemplatesPrerequisites/readme.md b/workload/bicep/brownfield/customImageTemplatesPrerequisites/readme.md new file mode 100644 index 000000000..bb73a3e66 --- /dev/null +++ b/workload/bicep/brownfield/customImageTemplatesPrerequisites/readme.md @@ -0,0 +1,34 @@ +# Custom Image Templates (CIT) Prerequisites + +This solution will deploy the prerequisites for AVD Custom Image Templates as described in the following article: + +[Use custom image templates to create custom images in Azure Virtual Desktop | Microsoft Learn](https://learn.microsoft.com/en-us/azure/virtual-desktop/create-custom-image-templates) + +## Requirements + +- Permissions: below are the minimum required roles on the target subscription to deploy this solution. + - Owner + +## Deployment Options + +### Azure Portal + +[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#blade/Microsoft_Azure_CreateUIDef/CustomDeploymentBlade/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fjamasten%2Favdaccelerator%2Fmain%2Fworkload%2Farm%2Fbrownfield%2FdeployCustomImageTemplatesPrerequisites.json/uiFormDefinitionUri/https%3A%2F%2Fraw.githubusercontent.com%2Fjamasten%2Favdaccelerator%2Fmain%2Fworkload%2Fportal-ui%2Fbrownfield%2FportalUiCustomImageTemplatesPrerequisites.json) +[![Deploy to Azure Gov](https://aka.ms/deploytoazuregovbutton)](https://portal.azure.us/#blade/Microsoft_Azure_CreateUIDef/CustomDeploymentBlade/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fjamasten%2Favdaccelerator%2Fmain%2Fworkload%2Farm%2Fbrownfield%2FdeployCustomImageTemplatesPrerequisites.json/uiFormDefinitionUri/https%3A%2F%2Fraw.githubusercontent.com%2Fjamasten%2Favdaccelerator%2Fmain%2Fworkload%2Fportal-ui%2Fbrownfield%2FportalUiCustomImageTemplatesPrerequisites.json) + +### PowerShell + +````powershell +New-AzDeployment ` + -Location '' ` + -TemplateFile 'https://raw.githubusercontent.com/jamasten/avdaccelerator/main/workload/arm/brownfield/deployCustomImageTemplatesPrerequisites.json' ` + -Verbose +```` + +### Azure CLI + +````cli +az deployment sub create \ + --location '' \ + --template-uri 'https://raw.githubusercontent.com/jamasten/avdaccelerator/main/workload/arm/brownfield/deployCustomImageTemplatesPrerequisites.json' +```` diff --git a/workload/portal-ui/brownfield/portalUiCustomImageTemplatesPrerequisites.json b/workload/portal-ui/brownfield/portalUiCustomImageTemplatesPrerequisites.json new file mode 100644 index 000000000..9dc2979c1 --- /dev/null +++ b/workload/portal-ui/brownfield/portalUiCustomImageTemplatesPrerequisites.json @@ -0,0 +1,382 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2021-09-09/uiFormDefinition.schema.json", + "view": { + "kind": "Form", + "properties": { + "title": "Azure Virtual Desktop LZA: Custom Image Templates Prerequisites", + "steps": [ + { + "name": "basics", + "label": "Basics", + "elements": [ + { + "name": "resourceScope", + "type": "Microsoft.Common.ResourceScope", + "location": { + "resourceTypes": [ + "Microsoft.Compute/galleries", + "Microsoft.ManagedIdentity/userAssignedIdentities", + "Microsoft.Resources/deploymentScripts", + "Microsoft.Resources/resourceGroups", + "Microsoft.Storage/storageAccounts" + ], + "allowedValues": [] + } + }, + { + "name": "resourceGroupsApi", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(steps('basics').resourceScope.subscription.id, '/resourceGroups?api-version=2021-04-01')]" + } + }, + { + "name": "existingResourceGroup", + "type": "Microsoft.Common.CheckBox", + "visible": true, + "label": "Use existing resource group?", + "defaultValue": false, + "toolTip": "Determine whether to use an existing resource group for the CIT prerequisite resources." + }, + { + "name": "resourceGroup", + "type": "Microsoft.Common.DropDown", + "label": "Resource Group", + "multiselect": false, + "defaultValue": "", + "toolTip": "Select the name of the existing resource group.", + "filterPlaceholder": "", + "defaultDescription": "", + "constraints": { + "allowedValues": "[map(steps('basics').resourceGroupsApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]", + "required": true + }, + "infoMessages": [], + "visible": "[steps('basics').existingResourceGroup]" + }, + { + "name": "deployStorage", + "type": "Microsoft.Common.CheckBox", + "visible": true, + "label": "Deploy a storage account for build artifacts?", + "defaultValue": false, + "toolTip": "Determine whether to deploy a storage account to store build artifacts for the image build." + }, + { + "name": "names", + "type": "Microsoft.Common.Section", + "label": "Resource Names", + "elements": [ + { + "name": "resourceGroup", + "type": "Microsoft.Common.TextBox", + "label": "Resource group", + "visible": "[not(steps('basics').existingResourceGroup)]", + "toolTip": "Input a custom name for the shared services resource group.", + "placeholder": "Example: rg-cit-dev-use", + "constraints": { + "required": true, + "regex": "^(?!.*[\\.]$)[a-zA-Z0-9_\\.()-]{1,90}$", + "validationMessage": "The value must be alphanumerics, underscores, hyphens, periods, and parentheses. The value must not end with a period. The length must be 1 to 90 characters." + } + }, + { + "name": "computeGallery", + "type": "Microsoft.Common.TextBox", + "label": "Compute gallery", + "toolTip": "Input a custom name for the Azure Compute Gallery.", + "placeholder": "Example: gal_dev_use", + "constraints": { + "required": true, + "regex": "^(?:[a-zA-Z0-9]|[a-z0-9A-Z][a-z0-9A-Z_\\.]{0,78}[a-z0-9A-Z])$", + "validationMessage": "The value must be alphanumerics, underscores, and periods. The value must begin and end with alphanumerics. The length must be 1 to 80 characters." + } + }, + { + "name": "desploymentScript", + "type": "Microsoft.Common.TextBox", + "label": "Deployment script", + "toolTip": "Input a custom name for the Deployment Script.", + "placeholder": "Example: ds-cit-dev-use", + "constraints": { + "required": true, + "regex": "^[a-zA-Z0-9][a-zA-Z0-9_-]{2,127}$", + "validationMessage": "The value must contain alphanumerics, hyphens, and underscores. The value must start with a letter or number. The length must be 3 to 128 characters." + } + }, + { + "name": "imageDefinition", + "type": "Microsoft.Common.TextBox", + "label": "Image definition", + "toolTip": "Input a custom name for the Image Definition in the Compute Gallery.", + "placeholder": "Example: win11-21h2-m365-avd-officeworkers", + "constraints": { + "required": true, + "regex": "^(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9_\\.-]{0,78}[a-zA-Z0-9])$", + "validationMessage": "The value must be alphanumerics, underscores, hyphens, and periods. The value must start and end with an alphanumeric. The length must be 1 to 80 characters." + } + }, + { + "name": "storageAccountName_checkNameAvailability", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "POST", + "path": "[concat(steps('basics').resourceScope.subscription.id,'/providers/Microsoft.Storage/checkNameAvailability?api-version=2016-01-01')]", + "body": { + "name": "[steps('basics').names.storageAccountName]", + "type": "Microsoft.Storage/storageAccounts" + } + } + }, + { + "name": "storageAccountName_rePUT", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(steps('basics').resourceScope.subscription.id,'/resourceGroups/',steps('basics').resourceScope.resourceGroup.name,'/providers/Microsoft.Storage/storageAccounts/',steps('basics').names.storageAccountName,'?api-version=2016-01-01')]", + "body": {} + } + }, + { + "name": "storageAccountName", + "type": "Microsoft.Common.TextBox", + "label": "Storage account", + "defaultValue": "", + "placeholder": "Example: sacitdevuse", + "toolTip": "The name of the storage account to store the build artifacts (optional). The value must be 24 characters or less. Special characters are not allowed. The value must be in lowercase.", + "constraints": { + "required": true, + "validations": [ + { + "isValid": "[or(steps('basics').names.storageAccountName_checkNameAvailability.nameAvailable,equals(steps('basics').names.storageAccountName_checkNameAvailability.reason,'AlreadyExists'))]", + "message": "[steps('basics').names.storageAccountName_checkNameAvailability.message]" + }, + { + "isValid": "[or(steps('basics').names.storageAccountName_checkNameAvailability.nameAvailable,equals(steps('basics').names.storageAccountName_rePUT.id,concat(steps('basics').resourceScope.subscription.id,'/resourceGroups/',steps('basics').resourceScope.resourceGroup.name,'/providers/Microsoft.Storage/storageAccounts/',steps('basics').names.storageAccountName)),not(equals(steps('basics').names.storageAccountName_checkNameAvailability.reason,'AlreadyExists')))]", + "message": "[steps('basics').names.storageAccountName_checkNameAvailability.message]" + } + ] + }, + "visible": "[steps('basics').deployStorage]" + }, + { + "name": "userAssignedIdentity", + "type": "Microsoft.Common.TextBox", + "label": "User assigned identity", + "toolTip": "Input a custom name for the User Assigned Identity.", + "placeholder": "Example: id-cit-dev-use", + "constraints": { + "required": true, + "regex": "^[a-zA-Z0-9][a-zA-Z0-9_-]{2,127}$", + "validationMessage": "The value must contain alphanumerics, hyphens, and underscores. The value must start with a letter or number. The length must be 3 to 128 characters." + } + } + ] + } + ] + }, + { + "name": "networking", + "type": "Microsoft.Common.Section", + "label": "Networking", + "elements": [ + { + "name": "enable", + "type": "Microsoft.Common.CheckBox", + "visible": true, + "label": "Enable custom virtual network", + "defaultValue": false, + "toolTip": "Determine whether to deploy the build virtual machine in a custom virtual network." + }, + { + "name": "virtualNetwork", + "type": "Microsoft.Solutions.ResourceSelector", + "visible": "[steps('networking').enable]", + "label": "Virtual network", + "resourceType": "Microsoft.Network/virtualNetworks", + "options": { + "filter": { + "subscription": "onBasics", + "location": "onBasics" + } + } + }, + { + "name": "subnetsApi", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(steps('networking').virtualNetwork.id, '/subnets?api-version=2022-05-01')]" + } + }, + { + "name": "subnet", + "type": "Microsoft.Common.DropDown", + "visible": "[steps('networking').enable]", + "label": "Subnet", + "defaultValue": "", + "toolTip": "Select an existing subnet for AIB build virtual machines. This enables the use of Private Link and prevents the deployment of public IP addresses.", + "constraints": { + "allowedValues": "[map(steps('networking').subnetsApi.value, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]" + } + } + ] + }, + { + "name": "imageDefinition", + "type": "Microsoft.Common.Section", + "label": "Image Definition", + "elements": [ + { + "name": "supportsAcceleratedNetworking", + "type": "Microsoft.Common.CheckBox", + "visible": true, + "label": "Supports network acceleration.", + "defaultValue": true, + "toolTip": "Accelerated networking enables single root I/O virtualization (SR-IOV) to a VM, greatly improving its networking performance. This high-performance path bypasses the host from the data path, which reduces latency, jitter, and CPU utilization for the most demanding network workloads on supported VM types." + }, + { + "name": "supportsHibernation", + "type": "Microsoft.Common.CheckBox", + "visible": true, + "label": "Supports hibernation.", + "defaultValue": false, + "toolTip": "This is currently a preview feature and is only supported on Dsv5 series VMs." + }, + { + "name": "securityType", + "type": "Microsoft.Common.DropDown", + "visible": true, + "label": "Security type", + "defaultValue": "Standard", + "toolTip": "Security type refers to the different security features available for a virtual machine. Security features like Trusted launch and Confidential Virtual Machines help to improve the security of Azure generation 2 virtual machines. However, additional security features have some limitations, which include not supporting back up, managed disks, and ephemeral OS disks. Learn more about Trusted launch virtual machines at https://learn.microsoft.com/en-us/azure/virtual-machines/trusted-launch and Confidential virtual machines at https://learn.microsoft.com/en-us/azure/confidential-computing/confidential-vm-overview.", + "constraints": { + "required": true, + "allowedValues": [ + { + "label": "Standard", + "value": "Standard" + }, + { + "label": "Trusted Launch", + "value": "TrustedLaunch" + }, + { + "label": "Confidential Virtual Machine", + "value": "ConfidentialVM" + }, + { + "label": "Confidential Virtual Machine Supported", + "value": "ConfidentialVMSupported" + } + ] + } + }, + { + "name": "publisher", + "type": "Microsoft.Common.OptionsGroup", + "label": "Publisher", + "defaultValue": "Microsoft Windows Desktop", + "toolTip": "Select the desired marketplace image publisher.", + "constraints": { + "allowedValues": [ + { + "label": "Microsoft Windows Desktop", + "value": "MicrosoftWindowsDesktop" + }, + { + "label": "Microsoft Windows Server", + "value": "MicrosoftWindowsServer" + } + ], + "required": true + }, + "visible": true + }, + { + "name": "offersApi", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(steps('basics').resourceScope.subscription.id, '/providers/Microsoft.Compute/locations/', steps('basics').resourceScope.location.name, '/publishers/', steps('imageDefinition').publisher, '/artifacttypes/vmimage/offers?api-version=2023-07-01')]" + } + }, + { + "name": "offer", + "type": "Microsoft.Common.DropDown", + "label": "Offer", + "defaultValue": "", + "toolTip": "Select the desired marketplace image offer.", + "constraints": { + "allowedValues": "[map(steps('imageDefinition').offersApi, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]", + "required": true + }, + "visible": true + }, + { + "name": "skusApi", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(steps('basics').resourceScope.subscription.id, '/providers/Microsoft.Compute/locations/', steps('basics').resourceScope.location.name, '/publishers/', steps('imageDefinition').publisher, '/artifacttypes/vmimage/offers/', steps('imageDefinition').offer, '/skus?api-version=2023-07-01')]" + } + }, + { + "name": "sku", + "type": "Microsoft.Common.DropDown", + "label": "SKU", + "defaultValue": "win11-22h2-avd", + "toolTip": "Select the desired marketplace image SKU.", + "constraints": { + "allowedValues": "[map(steps('imageDefinition').skusApi, (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]", + "required": true + }, + "visible": true + } + ] + }, + { + "name": "tags", + "label": "Tags", + "elements": [ + { + "name": "tags", + "type": "Microsoft.Common.TagsByResource", + "resources": [ + "Microsoft.Compute/galleries", + "Microsoft.ManagedIdentity/userAssignedIdentities", + "Microsoft.Resources/deploymentScripts", + "Microsoft.Resources/resourceGroups", + "Microsoft.Storage/storageAccounts" + ] + } + ] + } + ] + }, + "outputs": { + "parameters": { + "computeGalleryName": "[steps('basics').names.computeGallery]", + "deploymentScriptName": "[steps('basics').names.desploymentScript]", + "existingResourceGroup": "[steps('basics').existingResourceGroup]", + "existingVirtualNetworkResourceId": "[steps('networking').virtualNetwork.id]", + "imageDefinitionIsAcceleratedNetworkSupported": "[steps('imageDefinition').supportsAcceleratedNetworking]", + "imageDefinitionIsHibernateSupported": "[steps('imageDefinition').supportsHibernation]", + "imageDefinitionName": "[steps('basics').names.imageDefinition]", + "imageDefinitionSecurityType": "[steps('imageDefinition').securityType]", + "imageOffer": "[steps('imageDefinition').offer]", + "imagePublisher": "[steps('imageDefinition').publisher]", + "imageSku": "[steps('imageDefinition').sku]", + "resourceGroupName": "[if(steps('basics').existingResourceGroup, steps('basics').resourceGroup, steps('basics').names.resourceGroup)]", + "storageAccountName": "[steps('basics').names.storageAccountName]", + "subnetName": "[steps('networking').subnet]", + "tags": "[steps('tags').tags]", + "userAssignedIdentityName": "[steps('basics').names.userAssignedIdentity]" + }, + "kind": "Subscription", + "location": "[steps('basics').resourceScope.location.name]", + "subscriptionId": "[steps('basics').resourceScope.subscription.id]" + } + } +}