From a3674754bf800362cf33de9c31e0cd2709898464 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Mon, 19 Aug 2024 16:22:31 +0200 Subject: [PATCH 01/83] Update portal-ui.json --- deploy/portal-ui/portal-ui.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index 83d6e5e..fcfbc60 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -32,8 +32,7 @@ }, "options": { "filter": { - "subscription": "onBasics", - "location": "onBasics" + "subscription": "onBasics" } } }, From c492d1365270775b758f5d9532c23f9db098c02f Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Mon, 19 Aug 2024 16:52:13 +0200 Subject: [PATCH 02/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index c829954..8626e8c 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -11,7 +11,7 @@ "parameters": { "Location": { "type": "string", - "defaultValue": "[resourceGroup().location]", + "defaultValue": "", "metadata": { "description": "Required: No | Region of the Function App. This does not need to be the same as the location of the Azure Virtual Desktop Host Pool. | Default: Location of the resource group." } @@ -1395,4 +1395,4 @@ ] } ] -} \ No newline at end of file +} From 4d4e6b540b06e2c17151248da3580739a7e6f401 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Mon, 19 Aug 2024 21:26:48 +0200 Subject: [PATCH 03/83] Update portal-ui.json --- deploy/portal-ui/portal-ui.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index fcfbc60..1fff066 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -13,7 +13,8 @@ "name": "resourceScope", "type": "Microsoft.Common.ResourceScope", "location": { - "resourceTypes": [] + "resourceTypes": ["Microsoft.DesktopVirtualization/HostPools" + ] } }, { From 4c8a040f497ec6c43dc7b23448433132df37a5f1 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:22:44 +0200 Subject: [PATCH 04/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 8626e8c..6f5c9ce 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -11,7 +11,7 @@ "parameters": { "Location": { "type": "string", - "defaultValue": "", + "defaultValue": "[resourceGroup().location]", "metadata": { "description": "Required: No | Region of the Function App. This does not need to be the same as the location of the Azure Virtual Desktop Host Pool. | Default: Location of the resource group." } From ad9f5e81d1834e7fee8076a3327d308e1a4d9e4d Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:01:33 +0200 Subject: [PATCH 05/83] Add files via upload --- deploy/arm/DeployAVDSessionHostReplacer.json | 74 ++++++++++++++------ 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 6f5c9ce..41223bb 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "7416287688351186923" + "templateHash": "16749693425937561368" } }, "parameters": { @@ -31,18 +31,25 @@ "description": "Required: Yes | Name of the Log Analytics Workspace used by the Function App Insights." } }, + "UseStandardTemplate": { + "type": "bool", + "defaultValue": true + }, "SessionHostsRegion": { - "type": "string" + "type": "string", + "defaultValue": "" }, "AvailabilityZones": { "type": "array", "defaultValue": [] }, "SessionHostSize": { - "type": "string" + "type": "string", + "defaultValue": "" }, "AcceleratedNetworking": { - "type": "bool" + "type": "bool", + "defaultValue": false }, "SessionHostDiskType": { "type": "string", @@ -55,6 +62,7 @@ }, "MarketPlaceOrCustomImage": { "type": "string", + "defaultValue": "Marketplace", "allowedValues": [ "Marketplace", "Gallery" @@ -102,10 +110,12 @@ "defaultValue": true }, "SubnetId": { - "type": "string" + "type": "string", + "defaultValue": "" }, "IdentityServiceProvider": { "type": "string", + "defaultValue": "EntraID", "allowedValues": [ "EntraID", "ActiveDirectory", @@ -133,7 +143,23 @@ "defaultValue": "" }, "LocalAdminUsername": { - "type": "string" + "type": "string", + "defaultValue": "" + }, + "CustomTemplateSpecResourceId": { + "type": "string", + "defaultValue": "" + }, + "VMNamesTemplateParameterName": { + "type": "string", + "defaultValue": "VMNames", + "metadata": { + "description": "Required: No | The name of the parameter in the template that specifies the VM Names array." + } + }, + "CustomTemplateSpecParameters": { + "type": "object", + "defaultValue": {} }, "HostPoolResourceGroupName": { "type": "string", @@ -279,13 +305,6 @@ "description": "Required: No | Delay in days before replacing session hosts when a new image version is detected. | Default: 0 (no delay)." } }, - "VMNamesTemplateParameterName": { - "type": "string", - "defaultValue": "VMNames", - "metadata": { - "description": "Required: No | The name of the parameter in the template that specifies the VM Names array." - } - }, "SessionHostResourceGroupName": { "type": "string", "defaultValue": "", @@ -443,7 +462,7 @@ }, { "name": "_SessionHostParameters", - "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'tags', createObject()))]" + "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" }, { "name": "_SubscriptionId", @@ -459,11 +478,11 @@ }, { "name": "_ClientId", - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').clientId]" + "value": "[if(parameters('UseUserAssignedManagedIdentity'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').clientId, '')]" }, { "name": "_TenantId", - "value": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').tenantId]" + "value": "[if(parameters('UseUserAssignedManagedIdentity'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').tenantId, '')]" }, { "name": "_GraphEnvironmentName", @@ -542,7 +561,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "17223373059197527263" + "templateHash": "3734734378229800954" } }, "parameters": { @@ -576,7 +595,7 @@ }, "FunctionAppZipUrl": { "type": "string", - "defaultValue": "https://github.com/Azure/AVDSessionHostReplacer/releases/download/v0.3.0/FunctionApp.zip", + "defaultValue": "https://github.com/Azure/AVDSessionHostReplacer/releases/download/v0.3.1-beta.3/FunctionApp.zip", "metadata": { "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." } @@ -823,7 +842,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "8573904393442968176" + "templateHash": "10208603161915711494" } }, "parameters": { @@ -843,7 +862,7 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "11784787085459809456" + "templateHash": "8994538122455151797" } }, "parameters": { @@ -858,6 +877,9 @@ "VMNames": { "type": "array" }, + "VMNamePrefixLength": { + "type": "int" + }, "VMSize": { "type": "string" }, @@ -942,6 +964,9 @@ "VMName": { "value": "[[parameters('VMNames')[copyIndex()]]" }, + "VMNamePrefixLength": { + "value": "[[parameters('VMNamePrefixLength')]" + }, "VMSize": { "value": "[[parameters('VMSize')]" }, @@ -974,13 +999,16 @@ "_generator": { "name": "bicep", "version": "0.29.47.4906", - "templateHash": "10911582647457079552" + "templateHash": "7267962610869920549" } }, "parameters": { "VMName": { "type": "string" }, + "VMNamePrefixLength": { + "type": "int" + }, "VMSize": { "type": "string" }, @@ -1038,7 +1066,7 @@ }, "variables": { "varRequireNvidiaGPU": "[[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", - "varVMNumber": "[[int(substring(parameters('VMName'), add(lastIndexOf(parameters('VMName'), '-'), 1), sub(sub(length(parameters('VMName')), lastIndexOf(parameters('VMName'), '-')), 1)))]", + "varVMNumber": "[[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", "varAvailabilityZone": "[[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" }, "resources": [ @@ -1395,4 +1423,4 @@ ] } ] -} +} \ No newline at end of file From 149b45e2c0f685bd034f7b8599ed704ea75058cb Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:03:45 +0200 Subject: [PATCH 06/83] Add files via upload --- FunctionApp/profile.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FunctionApp/profile.ps1 b/FunctionApp/profile.ps1 index 02309b7..5b1750e 100644 --- a/FunctionApp/profile.ps1 +++ b/FunctionApp/profile.ps1 @@ -16,7 +16,7 @@ Set-PSFConfig -FullName PSFramework.Message.style.NoColor -Value $true #This is ## Version Banner ## Updated by Build\Build-Zip-File.ps1 -Write-PSFMessage -Level Host -Message "This is SessionHostReplacer version {0}" -StringValues 'v0.3.0' +Write-PSFMessage -Level Host -Message "This is SessionHostReplacer version {0}" -StringValues 'v0.3.1-beta.3' # Import Function Parameters From b87ac269b11fb5b15ea08a8bca4c1ee7ad8ec2b4 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:05:25 +0200 Subject: [PATCH 07/83] Update README.md Point to forked repo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de73d10..1896b8e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ the AVD Session Host Replacer helps you manage the task of replacing old session | Deployment Type | Link | | :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Azure Portal UI | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#blade/Microsoft_Azure_CreateUIDef/CustomDeploymentBlade/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FAVDSessionHostReplacer%2Fv0.3.0%2Fdeploy%2Farm%2FDeployAVDSessionHostReplacer.json/uiFormDefinitionUri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FAVDSessionHostReplacer%2Fv0.3.0%2Fdeploy%2Fportal-ui%2Fportal-ui.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%2FAzure%2FAVDSessionHostReplacer%2Fv0.3.0%2Fdeploy%2Farm%2FDeployAVDSessionHostReplacer.json/uiFormDefinitionUri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FAVDSessionHostReplacer%2Fv0.3.0%2Fdeploy%2Fportal-ui%2Fportal-ui.json) [![Deploy to Azure China](https://aka.ms/deploytoazurechinabutton)](https://portal.azure.cn/#blade/Microsoft_Azure_CreateUIDef/CustomDeploymentBlade/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FAVDSessionHostReplacer%2Fv0.3.0%2Fdeploy%2Farm%2FDeployAVDSessionHostReplacer.json/uiFormDefinitionUri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FAVDSessionHostReplacer%2Fv0.3.0%2Fdeploy%2Fportal-ui%2Fportal-ui.json) | +| Azure Portal UI | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#blade/Microsoft_Azure_CreateUIDef/CustomDeploymentBlade/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fstefze%2FAVDSessionHostReplacer%2Fv0.3.0%2Fdeploy%2Farm%2FDeployAVDSessionHostReplacer.json/uiFormDefinitionUri/https%3A%2F%2Fraw.githubusercontent.com%2Fstefze%2FAVDSessionHostReplacer%2Fv0.3.0%2Fdeploy%2Fportal-ui%2Fportal-ui.json) | Command line (Bicep/ARM) | [![Powershell/Azure CLI](./docs/icons/powershell.png)](./docs/CodeDeploy.md) ## Pre-requisites From d184d3961188e977ba2a19bc1397f7a391fe7430 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:35:18 +0200 Subject: [PATCH 08/83] Update DeployAVDSessionHostReplacer.json Reconfigured to use part of the RG for naming keyvault and function --- deploy/arm/DeployAVDSessionHostReplacer.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 41223bb..a926432 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -400,8 +400,8 @@ ], "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", - "varUniqueString": "[uniqueString(resourceGroup().id, parameters('HostPoolName'))]", - "varFunctionAppName": "[format('AVDSessionHostReplacer-{0}', uniqueString(resourceGroup().id, parameters('HostPoolName')))]", + "varUniqueString": "[substring(resourceGroup().name, lastIndexOf(resourceGroup().name, '-') + 3)]", + "varFunctionAppName": "[format('AVDSessionHostReplacer-{0}', '-', variables('varUniqueString'))]", "varFunctionAppIdentity": "[if(parameters('UseUserAssignedManagedIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', parameters('UserAssignedManagedIdentityResourceId')), createObject())), createObject('type', 'SystemAssigned'))]" }, "resources": [ @@ -628,7 +628,8 @@ } }, "variables": { - "varStorageAccountName": "[format('stavdrpfunc{0}', uniqueString(parameters('FunctionAppName')))]", + "rgpattern": "[substring(resourceGroup().name, lastIndexOf(resourceGroup().name, '-') + 3)]", + "varStorageAccountName": "[format('stavdrpfunc{0}','-', variables('rgpattern')]", "varLogAnalyticsWorkspaceName": "[format('{0}-law', parameters('FunctionAppName'))]", "varAppServicePlanName": "[format('{0}-asp', parameters('FunctionAppName'))]" }, @@ -1423,4 +1424,4 @@ ] } ] -} \ No newline at end of file +} From 82839329fa0f3052319b0d80581f9f3d072540e5 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:39:08 +0200 Subject: [PATCH 09/83] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1896b8e..ecfe0b0 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ the AVD Session Host Replacer helps you manage the task of replacing old session | Deployment Type | Link | | :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Azure Portal UI | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#blade/Microsoft_Azure_CreateUIDef/CustomDeploymentBlade/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fstefze%2FAVDSessionHostReplacer%2Fv0.3.0%2Fdeploy%2Farm%2FDeployAVDSessionHostReplacer.json/uiFormDefinitionUri/https%3A%2F%2Fraw.githubusercontent.com%2Fstefze%2FAVDSessionHostReplacer%2Fv0.3.0%2Fdeploy%2Fportal-ui%2Fportal-ui.json) +| Azure Portal UI | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#blade/Microsoft_Azure_CreateUIDef/CustomDeploymentBlade/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fstefze%2FAVDSessionHostReplacer%2Fmain%2Fdeploy%2Farm%2FDeployAVDSessionHostReplacer.json/uiFormDefinitionUri/https%3A%2F%2Fraw.githubusercontent.com%2Fstefze%2FAVDSessionHostReplacer%2Fmain%2Fdeploy%2Fportal-ui%2Fportal-ui.json) | Command line (Bicep/ARM) | [![Powershell/Azure CLI](./docs/icons/powershell.png)](./docs/CodeDeploy.md) ## Pre-requisites From de05c30891d92e95b993f84d2ebd5908e418cc14 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:55:09 +0200 Subject: [PATCH 10/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index a926432..a580fde 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -400,7 +400,7 @@ ], "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", - "varUniqueString": "[substring(resourceGroup().name, lastIndexOf(resourceGroup().name, '-') + 3)]", + "varUniqueString": "[substring(resourceGroup().name, 14,11)]", "varFunctionAppName": "[format('AVDSessionHostReplacer-{0}', '-', variables('varUniqueString'))]", "varFunctionAppIdentity": "[if(parameters('UseUserAssignedManagedIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', parameters('UserAssignedManagedIdentityResourceId')), createObject())), createObject('type', 'SystemAssigned'))]" }, @@ -628,8 +628,8 @@ } }, "variables": { - "rgpattern": "[substring(resourceGroup().name, lastIndexOf(resourceGroup().name, '-') + 3)]", - "varStorageAccountName": "[format('stavdrpfunc{0}','-', variables('rgpattern')]", + "rgpattern": "[substring(resourceGroup().name, 14,11)]", + "varStorageAccountName": "[format('stavdrpfunc{0}','-', variables('rgpattern'))]", "varLogAnalyticsWorkspaceName": "[format('{0}-law', parameters('FunctionAppName'))]", "varAppServicePlanName": "[format('{0}-asp', parameters('FunctionAppName'))]" }, From 8ba321d79c6bdadeb3d2ac09f0b243bc6d7b8588 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Thu, 5 Sep 2024 23:15:23 +0200 Subject: [PATCH 11/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index a580fde..74f7298 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -398,9 +398,11 @@ "AzureUSGovernment", "AzureChinaCloud" ], + "resourceGroupName": "[resourceGroup().name]", + "splitParts": "[split(variables('resourceGroupName'), '-')]", + "varUniqueString": "[concat(variables('splitParts'), '-', variables('splitParts'), '-', variables('splitParts'))]", "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", - "varUniqueString": "[substring(resourceGroup().name, 14,11)]", "varFunctionAppName": "[format('AVDSessionHostReplacer-{0}', '-', variables('varUniqueString'))]", "varFunctionAppIdentity": "[if(parameters('UseUserAssignedManagedIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', parameters('UserAssignedManagedIdentityResourceId')), createObject())), createObject('type', 'SystemAssigned'))]" }, @@ -628,7 +630,9 @@ } }, "variables": { - "rgpattern": "[substring(resourceGroup().name, 14,11)]", + "resourceGroupName": "[resourceGroup().name]", + "splitParts": "[split(variables('resourceGroupName'), '-')]", + "rgpattern": "[concat(variables('splitParts'), '-', variables('splitParts'), '-', variables('splitParts'))]", "varStorageAccountName": "[format('stavdrpfunc{0}','-', variables('rgpattern'))]", "varLogAnalyticsWorkspaceName": "[format('{0}-law', parameters('FunctionAppName'))]", "varAppServicePlanName": "[format('{0}-asp', parameters('FunctionAppName'))]" From 326d239974585803ff107490385ccd2d223384fa Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Thu, 5 Sep 2024 23:25:43 +0200 Subject: [PATCH 12/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 74f7298..88cc3d9 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -400,7 +400,7 @@ ], "resourceGroupName": "[resourceGroup().name]", "splitParts": "[split(variables('resourceGroupName'), '-')]", - "varUniqueString": "[concat(variables('splitParts'), '-', variables('splitParts'), '-', variables('splitParts'))]", + "varUniqueString": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4])]", "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", "varFunctionAppName": "[format('AVDSessionHostReplacer-{0}', '-', variables('varUniqueString'))]", @@ -632,7 +632,7 @@ "variables": { "resourceGroupName": "[resourceGroup().name]", "splitParts": "[split(variables('resourceGroupName'), '-')]", - "rgpattern": "[concat(variables('splitParts'), '-', variables('splitParts'), '-', variables('splitParts'))]", + "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4])]", "varStorageAccountName": "[format('stavdrpfunc{0}','-', variables('rgpattern'))]", "varLogAnalyticsWorkspaceName": "[format('{0}-law', parameters('FunctionAppName'))]", "varAppServicePlanName": "[format('{0}-asp', parameters('FunctionAppName'))]" From 1b6407a7d3a46fcc125d7d024943ac9381429464 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Thu, 5 Sep 2024 23:36:57 +0200 Subject: [PATCH 13/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 88cc3d9..dc44e60 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -403,7 +403,7 @@ "varUniqueString": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4])]", "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", - "varFunctionAppName": "[format('AVDSessionHostReplacer-{0}', '-', variables('varUniqueString'))]", + "varFunctionAppName": "[format('AVDSessionHostReplacer', variables('varUniqueString'))]", "varFunctionAppIdentity": "[if(parameters('UseUserAssignedManagedIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', parameters('UserAssignedManagedIdentityResourceId')), createObject())), createObject('type', 'SystemAssigned'))]" }, "resources": [ @@ -758,7 +758,7 @@ "value": "[parameters('Location')]" }, "KeyVaultName": { - "value": "[format('kv-AVDSHR-{0}', variables('varUniqueString'))]" + "value": "[format('kv-AVDSHR', variables('varUniqueString'))]" }, "DomainJoinPassword": { "value": "[parameters('ADJoinUserPassword')]" From 874984c949e8758012ba4637aad6621c1c4fcc8e Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Sun, 8 Sep 2024 19:43:49 +0200 Subject: [PATCH 14/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 24 ++++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index dc44e60..436158f 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -398,12 +398,11 @@ "AzureUSGovernment", "AzureChinaCloud" ], - "resourceGroupName": "[resourceGroup().name]", - "splitParts": "[split(variables('resourceGroupName'), '-')]", - "varUniqueString": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4])]", + "splitParts": "[split(parameters('HostPoolResourceGroupName'), '-')]", + "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4])]", "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", - "varFunctionAppName": "[format('AVDSessionHostReplacer', variables('varUniqueString'))]", + "varFunctionAppName": "[concat('AVDSessionHostReplacer', variables('rgpattern'))]", "varFunctionAppIdentity": "[if(parameters('UseUserAssignedManagedIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', parameters('UserAssignedManagedIdentityResourceId')), createObject())), createObject('type', 'SystemAssigned'))]" }, "resources": [ @@ -630,12 +629,13 @@ } }, "variables": { - "resourceGroupName": "[resourceGroup().name]", - "splitParts": "[split(variables('resourceGroupName'), '-')]", - "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4])]", - "varStorageAccountName": "[format('stavdrpfunc{0}','-', variables('rgpattern'))]", - "varLogAnalyticsWorkspaceName": "[format('{0}-law', parameters('FunctionAppName'))]", - "varAppServicePlanName": "[format('{0}-asp', parameters('FunctionAppName'))]" + "resourceGroupName": "[resourceGroup().name]", + "splitParts": "[split(variables('resourceGroupName'), '-')]", + "varUniqueString": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4])]", + "varStorageAccountName": "[format('stavdrpfunc', parameters('FunctionAppName'))]", + "varLogAnalyticsWorkspaceName": "[concat('law-', parameters('FunctionAppName'))]", + "varAppServicePlanName": "[concat('Asp-', parameters('FunctionAppName'))]", + "varGraphEnvironmentName": "your_value_here" }, "resources": [ { @@ -734,6 +734,10 @@ "functionAppPrincipalId": { "type": "string", "value": "[if(equals(parameters('FunctionAppIdentity').type, 'SystemAssigned'), reference(resourceId('Microsoft.Web/sites', parameters('FunctionAppName')), '2023-01-01', 'full').identity.principalId, '')]" + }, + "varUniqueString": { + "type": "string", + "value": "[variables('varUniqueString')]" } } } From 05d93f09c182adfa33dedc53ee230b22cb23c3e3 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Sun, 8 Sep 2024 19:54:06 +0200 Subject: [PATCH 15/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 436158f..7bcbe14 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -400,6 +400,7 @@ ], "splitParts": "[split(parameters('HostPoolResourceGroupName'), '-')]", "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4])]", + "varUniqueString": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4])]", "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", "varFunctionAppName": "[concat('AVDSessionHostReplacer', variables('rgpattern'))]", From 06006fb534260004348f8e6164d3fd53add23c62 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Sun, 8 Sep 2024 20:02:31 +0200 Subject: [PATCH 16/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 7bcbe14..25d5aae 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -632,7 +632,7 @@ "variables": { "resourceGroupName": "[resourceGroup().name]", "splitParts": "[split(variables('resourceGroupName'), '-')]", - "varUniqueString": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4])]", + "varUniqueString": "[concat('-',parameters('FunctionAppName'))]", "varStorageAccountName": "[format('stavdrpfunc', parameters('FunctionAppName'))]", "varLogAnalyticsWorkspaceName": "[concat('law-', parameters('FunctionAppName'))]", "varAppServicePlanName": "[concat('Asp-', parameters('FunctionAppName'))]", From 2063cd1f785e8915c68971a0bf54e4913bd2215b Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Sun, 8 Sep 2024 20:11:05 +0200 Subject: [PATCH 17/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 25d5aae..7fced3e 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -400,7 +400,6 @@ ], "splitParts": "[split(parameters('HostPoolResourceGroupName'), '-')]", "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4])]", - "varUniqueString": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4])]", "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", "varFunctionAppName": "[concat('AVDSessionHostReplacer', variables('rgpattern'))]", @@ -630,9 +629,6 @@ } }, "variables": { - "resourceGroupName": "[resourceGroup().name]", - "splitParts": "[split(variables('resourceGroupName'), '-')]", - "varUniqueString": "[concat('-',parameters('FunctionAppName'))]", "varStorageAccountName": "[format('stavdrpfunc', parameters('FunctionAppName'))]", "varLogAnalyticsWorkspaceName": "[concat('law-', parameters('FunctionAppName'))]", "varAppServicePlanName": "[concat('Asp-', parameters('FunctionAppName'))]", @@ -735,10 +731,6 @@ "functionAppPrincipalId": { "type": "string", "value": "[if(equals(parameters('FunctionAppIdentity').type, 'SystemAssigned'), reference(resourceId('Microsoft.Web/sites', parameters('FunctionAppName')), '2023-01-01', 'full').identity.principalId, '')]" - }, - "varUniqueString": { - "type": "string", - "value": "[variables('varUniqueString')]" } } } @@ -763,7 +755,7 @@ "value": "[parameters('Location')]" }, "KeyVaultName": { - "value": "[format('kv-AVDSHR', variables('varUniqueString'))]" + "value": "[concat('kv-AVDSHR', variables('rgpattern'))]" }, "DomainJoinPassword": { "value": "[parameters('ADJoinUserPassword')]" From 84712dee31ecdb719b034eaa4f8cb41a287774a4 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Sun, 8 Sep 2024 20:31:28 +0200 Subject: [PATCH 18/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 7fced3e..a7f36b8 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -399,10 +399,12 @@ "AzureChinaCloud" ], "splitParts": "[split(parameters('HostPoolResourceGroupName'), '-')]", - "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4])]", + "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4], variables('splitParts')[5])]", + "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4])]", "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", "varFunctionAppName": "[concat('AVDSessionHostReplacer', variables('rgpattern'))]", + "varStorageAccountName": "[concat('stavdrpfunc', variables('rgpattern2'))]", "varFunctionAppIdentity": "[if(parameters('UseUserAssignedManagedIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', parameters('UserAssignedManagedIdentityResourceId')), createObject())), createObject('type', 'SystemAssigned'))]" }, "resources": [ @@ -629,7 +631,9 @@ } }, "variables": { - "varStorageAccountName": "[format('stavdrpfunc', parameters('FunctionAppName'))]", + "splitParts": "[split(resourceGroup().name, '-')]", + "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", + "varStorageAccountName": "[concat('stavdrpfunc', variables('rgpattern2'))]", "varLogAnalyticsWorkspaceName": "[concat('law-', parameters('FunctionAppName'))]", "varAppServicePlanName": "[concat('Asp-', parameters('FunctionAppName'))]", "varGraphEnvironmentName": "your_value_here" From 6c7948c7c4e8ef28a2558f321915c22a8f2d9731 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Sun, 8 Sep 2024 20:35:53 +0200 Subject: [PATCH 19/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index a7f36b8..0bd9a66 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -399,7 +399,7 @@ "AzureChinaCloud" ], "splitParts": "[split(parameters('HostPoolResourceGroupName'), '-')]", - "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4], variables('splitParts')[5])]", + "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4], '-', variables('splitParts')[5])]", "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4])]", "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", @@ -759,7 +759,7 @@ "value": "[parameters('Location')]" }, "KeyVaultName": { - "value": "[concat('kv-AVDSHR', variables('rgpattern'))]" + "value": "[concat('kv-SHR', variables('rgpattern'))]" }, "DomainJoinPassword": { "value": "[parameters('ADJoinUserPassword')]" From 9e7eaa5ca737b351e6f3a863f1b80ff634621311 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Sun, 8 Sep 2024 20:39:55 +0200 Subject: [PATCH 20/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 0bd9a66..d2f5ed4 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -400,11 +400,9 @@ ], "splitParts": "[split(parameters('HostPoolResourceGroupName'), '-')]", "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4], '-', variables('splitParts')[5])]", - "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4])]", "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", "varFunctionAppName": "[concat('AVDSessionHostReplacer', variables('rgpattern'))]", - "varStorageAccountName": "[concat('stavdrpfunc', variables('rgpattern2'))]", "varFunctionAppIdentity": "[if(parameters('UseUserAssignedManagedIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', parameters('UserAssignedManagedIdentityResourceId')), createObject())), createObject('type', 'SystemAssigned'))]" }, "resources": [ @@ -633,7 +631,7 @@ "variables": { "splitParts": "[split(resourceGroup().name, '-')]", "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", - "varStorageAccountName": "[concat('stavdrpfunc', variables('rgpattern2'))]", + "varStorageAccountName": "[toLower(concat('stavdrpfunc', variables('rgpattern2')))]", "varLogAnalyticsWorkspaceName": "[concat('law-', parameters('FunctionAppName'))]", "varAppServicePlanName": "[concat('Asp-', parameters('FunctionAppName'))]", "varGraphEnvironmentName": "your_value_here" From 0be05e1d156f1a53208cbeb5ac330b8d670832ae Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Sun, 8 Sep 2024 20:44:20 +0200 Subject: [PATCH 21/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index d2f5ed4..b524464 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -631,7 +631,7 @@ "variables": { "splitParts": "[split(resourceGroup().name, '-')]", "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", - "varStorageAccountName": "[toLower(concat('stavdrpfunc', variables('rgpattern2')))]", + "varStorageAccountName": "[concat('stavdrpfunc', variables('rgpattern2'))]", "varLogAnalyticsWorkspaceName": "[concat('law-', parameters('FunctionAppName'))]", "varAppServicePlanName": "[concat('Asp-', parameters('FunctionAppName'))]", "varGraphEnvironmentName": "your_value_here" @@ -651,7 +651,7 @@ { "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2022-05-01", - "name": "[variables('varStorageAccountName')]", + "name": "[toLower(variables('varStorageAccountName'))]", "location": "[parameters('Location')]", "kind": "StorageV2", "sku": { From 019b6e930ed72f145b6748f419b43fde99899982 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Sun, 8 Sep 2024 20:58:59 +0200 Subject: [PATCH 22/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index b524464..d6f65ac 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -402,7 +402,7 @@ "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4], '-', variables('splitParts')[5])]", "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", - "varFunctionAppName": "[concat('AVDSessionHostReplacer', variables('rgpattern'))]", + "varFunctionAppName": "[concat('AVDReplacer', variables('rgpattern'))]", "varFunctionAppIdentity": "[if(parameters('UseUserAssignedManagedIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', parameters('UserAssignedManagedIdentityResourceId')), createObject())), createObject('type', 'SystemAssigned'))]" }, "resources": [ From 3fd72a4fef235ba3b477920e4e22057c04c07f47 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Sun, 8 Sep 2024 21:06:35 +0200 Subject: [PATCH 23/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index d6f65ac..0b6bd1b 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -629,7 +629,8 @@ } }, "variables": { - "splitParts": "[split(resourceGroup().name, '-')]", + "rgname": "[toLower(resourceGroup().name)]", + "splitParts": "[split(variables('rgname'), '-')]", "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", "varStorageAccountName": "[concat('stavdrpfunc', variables('rgpattern2'))]", "varLogAnalyticsWorkspaceName": "[concat('law-', parameters('FunctionAppName'))]", @@ -651,7 +652,7 @@ { "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2022-05-01", - "name": "[toLower(variables('varStorageAccountName'))]", + "name": "[variables('varStorageAccountName')]", "location": "[parameters('Location')]", "kind": "StorageV2", "sku": { From cb7796b9fe70476fb581dc688cbadfba69755939 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Sun, 8 Sep 2024 22:30:17 +0200 Subject: [PATCH 24/83] Update portal-ui.json --- deploy/portal-ui/portal-ui.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index 1fff066..9a0936c 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -193,10 +193,10 @@ "type": "Microsoft.Common.TextBlock", "visible": true, "options": { - "text": "AVD session host replacer Portal UI Version: v0.3.0", + "text": "AVD session host replacer Portal UI Version: v0.3.2betasz", "link": { "label": "GitHub Repository", - "uri": "https://github.com/Azure/AVDSessionHostReplacer" + "uri": "https://github.com/stefze/AVDSessionHostReplacer" } } } From b3ee5b23965ba9ff58227e1ac400e01a82629ad8 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Mon, 9 Sep 2024 09:26:56 +0200 Subject: [PATCH 25/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 0b6bd1b..b3c76d2 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -400,6 +400,7 @@ ], "splitParts": "[split(parameters('HostPoolResourceGroupName'), '-')]", "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4], '-', variables('splitParts')[5])]", + "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", "varFunctionAppName": "[concat('AVDReplacer', variables('rgpattern'))]", @@ -758,7 +759,7 @@ "value": "[parameters('Location')]" }, "KeyVaultName": { - "value": "[concat('kv-SHR', variables('rgpattern'))]" + "value": "[concat('kvSHR', variables('rgpattern2'))]" }, "DomainJoinPassword": { "value": "[parameters('ADJoinUserPassword')]" From c6ce020a39cb04c5d38c137574e490f99cc8e13a Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 17 Sep 2024 10:37:06 +0200 Subject: [PATCH 26/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index b3c76d2..24c542f 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -633,7 +633,7 @@ "rgname": "[toLower(resourceGroup().name)]", "splitParts": "[split(variables('rgname'), '-')]", "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", - "varStorageAccountName": "[concat('stavdrpfunc', variables('rgpattern2'))]", + "varStorageAccountName": "[concat('st', variables('rgpattern2'))]", "varLogAnalyticsWorkspaceName": "[concat('law-', parameters('FunctionAppName'))]", "varAppServicePlanName": "[concat('Asp-', parameters('FunctionAppName'))]", "varGraphEnvironmentName": "your_value_here" From 0be8ab144163a8b1e661f38f038439908afc0e7b Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Fri, 9 May 2025 14:49:51 +0200 Subject: [PATCH 27/83] Update DeploySessionHosts.json --- .../DeploySessionHosts.json | 759 +++++++++--------- 1 file changed, 369 insertions(+), 390 deletions(-) diff --git a/StandardSessionHostTemplate/DeploySessionHosts.json b/StandardSessionHostTemplate/DeploySessionHosts.json index ef8baab..ee59af5 100644 --- a/StandardSessionHostTemplate/DeploySessionHosts.json +++ b/StandardSessionHostTemplate/DeploySessionHosts.json @@ -1,401 +1,380 @@ { - "$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": "11784787085459809456" - } - }, - "parameters": { - "Location": { - "type": "string", - "defaultValue": "[resourceGroup().location]" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "VMNames": { - "type": "array" - }, - "VMSize": { - "type": "string" - }, - "SubnetID": { - "type": "string" - }, - "AdminUsername": { - "type": "string" - }, - "AcceleratedNetworking": { - "type": "bool" - }, - "DiskType": { - "type": "string" - }, - "Tags": { - "type": "object", - "defaultValue": {} - }, - "ImageReference": { - "type": "object" - }, - "SecurityProfile": { - "type": "object", - "defaultValue": {} - }, - "HostPoolName": { - "type": "string" - }, - "HostPoolToken": { - "type": "securestring" - }, - "WVDArtifactsURL": { - "type": "string", - "defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" - }, - "DomainJoinObject": { - "type": "object", - "defaultValue": {} + "$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": "8994538122455151797" + } }, - "DomainJoinPassword": { - "type": "securestring", - "defaultValue": "" - } - }, - "resources": [ - { - "copy": { - "name": "deploySessionHosts", - "count": "[length(parameters('VMNames'))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "parameters": { + "Location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" }, - "mode": "Incremental", - "parameters": { - "AcceleratedNetworking": { - "value": "[parameters('AcceleratedNetworking')]" - }, - "AdminUsername": { - "value": "[parameters('AdminUsername')]" - }, - "HostPoolName": { - "value": "[parameters('HostPoolName')]" - }, - "HostPoolToken": { - "value": "[parameters('HostPoolToken')]" - }, - "ImageReference": { - "value": "[parameters('ImageReference')]" - }, - "SecurityProfile": { - "value": "[parameters('SecurityProfile')]" - }, - "SubnetID": { - "value": "[parameters('SubnetID')]" - }, - "VMName": { - "value": "[parameters('VMNames')[copyIndex()]]" - }, - "VMSize": { - "value": "[parameters('VMSize')]" - }, - "DiskType": { - "value": "[parameters('DiskType')]" - }, - "WVDArtifactsURL": { - "value": "[parameters('WVDArtifactsURL')]" - }, - "DomainJoinObject": { - "value": "[parameters('DomainJoinObject')]" - }, - "DomainJoinPassword": { - "value": "[parameters('DomainJoinPassword')]" - }, - "Location": { - "value": "[parameters('Location')]" - }, - "AvailabilityZones": { - "value": "[parameters('AvailabilityZones')]" - }, - "Tags": { - "value": "[parameters('Tags')]" - } + "AvailabilityZones": { + "type": "array", + "defaultValue": [] }, - "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": "10911582647457079552" - } - }, - "parameters": { - "VMName": { - "type": "string" - }, - "VMSize": { - "type": "string" - }, - "DiskType": { - "type": "string" - }, - "Location": { - "type": "string", - "defaultValue": "[resourceGroup().location]" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "SubnetID": { - "type": "string" - }, - "AdminUsername": { - "type": "string" - }, - "AdminPassword": { - "type": "securestring", - "defaultValue": "[newGuid()]" - }, - "AcceleratedNetworking": { - "type": "bool" - }, - "Tags": { - "type": "object", - "defaultValue": {} - }, - "ImageReference": { - "type": "object" - }, - "SecurityProfile": { - "type": "object" - }, - "HostPoolName": { - "type": "string" - }, - "HostPoolToken": { - "type": "securestring" - }, - "WVDArtifactsURL": { - "type": "string" - }, - "DomainJoinObject": { - "type": "object", - "defaultValue": {} - }, - "DomainJoinPassword": { - "type": "securestring", - "defaultValue": "" - } - }, - "variables": { - "varRequireNvidiaGPU": "[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", - "varVMNumber": "[int(substring(parameters('VMName'), add(lastIndexOf(parameters('VMName'), '-'), 1), sub(sub(length(parameters('VMName')), lastIndexOf(parameters('VMName'), '-')), 1)))]", - "varAvailabilityZone": "[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" - }, - "resources": [ - { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[format('{0}/{1}', parameters('VMName'), 'deployIntegrityMonitoring')]", - "location": "[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Azure.Security.WindowsAttestation", - "type": "GuestAttestation", - "typeHandlerVersion": "1.0", - "autoUpgradeMinorVersion": true, - "settings": { - "AttestationConfig": { - "MaaSettings": { - "maaEndpoint": "", - "maaTenantName": "Guest Attestation" + "VMNames": { + "type": "array" + }, + "VMNamePrefixLength": { + "type": "int" + }, + "VMSize": { + "type": "string" + }, + "SubnetID": { + "type": "string" + }, + "AdminUsername": { + "type": "string" + }, + "AcceleratedNetworking": { + "type": "bool" + }, + "DiskType": { + "type": "string" + }, + "Tags": { + "type": "object", + "defaultValue": {} + }, + "ImageReference": { + "type": "object" + }, + "SecurityProfile": { + "type": "object", + "defaultValue": {} + }, + "HostPoolName": { + "type": "string" + }, + "HostPoolToken": { + "type": "securestring" + }, + "WVDArtifactsURL": { + "type": "string", + "defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" + }, + "DomainJoinObject": { + "type": "object", + "defaultValue": {} + }, + "DomainJoinPassword": { + "type": "securestring", + "defaultValue": "" + } + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "AcceleratedNetworking": { + "value": "[parameters('AcceleratedNetworking')]" }, - "AscSettings": { - "ascReportingEndpoint": "", - "ascReportingFrequency": "" + "AdminUsername": { + "value": "[parameters('AdminUsername')]" }, - "useCustomToken": "false", - "disableAlerts": "false" - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[variables('varRequireNvidiaGPU')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", - "location": "[parameters('Location')]", - "properties": { - "publisher": "Microsoft.HpcCompute", - "type": "NvidiaGpuDriverWindows", - "typeHandlerVersion": "1.6", - "autoUpgradeMinorVersion": true - }, - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployIntegrityMonitoring')]", - "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[not(equals(parameters('HostPoolName'), ''))]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", - "location": "[parameters('Location')]", - "properties": { - "publisher": "Microsoft.PowerShell", - "type": "DSC", - "typeHandlerVersion": "2.77", - "autoUpgradeMinorVersion": true, - "settings": { - "modulesUrl": "[parameters('WVDArtifactsURL')]", - "configurationFunction": "Configuration.ps1\\AddSessionHost", - "properties": { - "hostPoolName": "[parameters('HostPoolName')]", - "registrationInfoToken": "[parameters('HostPoolToken')]", - "aadJoin": "[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", - "useAgentDownloadEndpoint": true, - "mdmId": "[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", - "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", - "location": "[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Azure.ActiveDirectory", - "type": "AADLoginForWindows", - "typeHandlerVersion": "2.0", - "autoUpgradeMinorVersion": true, - "settings": "[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" - }, - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", - "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", - "location": "[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Compute", - "type": "JSonADDomainExtension", - "typeHandlerVersion": "1.3", - "autoUpgradeMinorVersion": true, - "settings": { - "Name": "[parameters('DomainJoinObject').DomainName]", - "OUPath": "[parameters('DomainJoinObject').ADOUPath]", - "User": "[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", - "Restart": "true", - "Options": 3 + "HostPoolName": { + "value": "[parameters('HostPoolName')]" + }, + "HostPoolToken": { + "value": "[parameters('HostPoolToken')]" + }, + "ImageReference": { + "value": "[parameters('ImageReference')]" + }, + "SecurityProfile": { + "value": "[parameters('SecurityProfile')]" + }, + "SubnetID": { + "value": "[parameters('SubnetID')]" + }, + "VMName": { + "value": "[parameters('VMNames')[copyIndex()]]" + }, + "VMNamePrefixLength": { + "value": "[parameters('VMNamePrefixLength')]" + }, + "VMSize": { + "value": "[parameters('VMSize')]" + }, + "DiskType": { + "value": "[parameters('DiskType')]" + }, + "WVDArtifactsURL": { + "value": "[parameters('WVDArtifactsURL')]" + }, + "DomainJoinObject": { + "value": "[parameters('DomainJoinObject')]" + }, + "DomainJoinPassword": { + "value": "[parameters('DomainJoinPassword')]" + }, + "Location": { + "value": "[parameters('Location')]" + }, + "AvailabilityZones": { + "value": "[parameters('AvailabilityZones')]" + }, + "Tags": { + "value": "[parameters('Tags')]" + } }, - "protectedSettings": { - "Password": "[parameters('DomainJoinPassword')]" + "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": "7267962610869920549" + } + }, + "parameters": { + "VMName": { + "type": "string" + }, + "VMNamePrefixLength": { + "type": "int" + }, + "VMSize": { + "type": "string" + }, + "DiskType": { + "type": "string" + }, + "Location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "AvailabilityZones": { + "type": "array", + "defaultValue": [] + }, + "SubnetID": { + "type": "string" + }, + "AdminUsername": { + "type": "string" + }, + "AdminPassword": { + "type": "securestring", + "defaultValue": "[newGuid()]" + }, + "AcceleratedNetworking": { + "type": "bool" + }, + "Tags": { + "type": "object", + "defaultValue": {} + }, + "ImageReference": { + "type": "object" + }, + "SecurityProfile": { + "type": "object" + }, + "HostPoolName": { + "type": "string" + }, + "HostPoolToken": { + "type": "securestring" + }, + "WVDArtifactsURL": { + "type": "string" + }, + "DomainJoinObject": { + "type": "object", + "defaultValue": {} + }, + "DomainJoinPassword": { + "type": "securestring", + "defaultValue": "" + } + }, + "variables": { + "varRequireNvidiaGPU": "[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", + "varVMNumber": "[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", + "varAvailabilityZone": "[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" + }, + "resources": [ + { + "condition": "[variables('varRequireNvidiaGPU')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", + "location": "[parameters('Location')]", + "properties": { + "publisher": "Microsoft.HpcCompute", + "type": "NvidiaGpuDriverWindows", + "typeHandlerVersion": "1.6", + "autoUpgradeMinorVersion": true + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[not(equals(parameters('HostPoolName'), ''))]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", + "location": "[parameters('Location')]", + "properties": { + "publisher": "Microsoft.PowerShell", + "type": "DSC", + "typeHandlerVersion": "2.77", + "autoUpgradeMinorVersion": true, + "settings": { + "modulesUrl": "[parameters('WVDArtifactsURL')]", + "configurationFunction": "Configuration.ps1\\AddSessionHost", + "properties": { + "hostPoolName": "[parameters('HostPoolName')]", + "registrationInfoToken": "[parameters('HostPoolToken')]", + "aadJoin": "[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", + "useAgentDownloadEndpoint": true, + "mdmId": "[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", + "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", + "location": "[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Azure.ActiveDirectory", + "type": "AADLoginForWindows", + "typeHandlerVersion": "2.0", + "autoUpgradeMinorVersion": true, + "settings": "[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", + "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", + "location": "[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Compute", + "type": "JSonADDomainExtension", + "typeHandlerVersion": "1.3", + "autoUpgradeMinorVersion": true, + "settings": { + "Name": "[parameters('DomainJoinObject').DomainName]", + "OUPath": "[parameters('DomainJoinObject').ADOUPath]", + "User": "[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", + "Restart": "true", + "Options": 3 + }, + "protectedSettings": { + "Password": "[parameters('DomainJoinPassword')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", + "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2023-09-01", + "name": "[format('{0}-vNIC', parameters('VMName'))]", + "location": "[parameters('Location')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[parameters('SubnetID')]" + } + } + } + ], + "enableAcceleratedNetworking": "[parameters('AcceleratedNetworking')]" + }, + "tags": "[parameters('Tags')]" + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2023-09-01", + "name": "[parameters('VMName')]", + "location": "[parameters('Location')]", + "zones": "[variables('varAvailabilityZone')]", + "identity": "[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", + "properties": { + "osProfile": { + "computerName": "[parameters('VMName')]", + "adminUsername": "[parameters('AdminUsername')]", + "adminPassword": "[parameters('AdminPassword')]" + }, + "hardwareProfile": { + "vmSize": "[parameters('VMSize')]" + }, + "storageProfile": { + "osDisk": { + "name": "[format('{0}-OSDisk', parameters('VMName'))]", + "createOption": "FromImage", + "deleteOption": "Delete", + "managedDisk": { + "storageAccountType": "[parameters('DiskType')]" + } + }, + "ImageReference": "[parameters('ImageReference')]" + }, + "securityProfile": "[parameters('SecurityProfile')]", + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": true + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", + "properties": { + "deleteOption": "Delete" + } + } + ] + }, + "licenseType": "Windows_Client" + }, + "tags": "[parameters('Tags')]", + "dependsOn": [ + "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" + ] + } + ] } - }, - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", - "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2023-09-01", - "name": "[format('{0}-vNIC', parameters('VMName'))]", - "location": "[parameters('Location')]", - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "subnet": { - "id": "[parameters('SubnetID')]" - } - } - } - ], - "enableAcceleratedNetworking": "[parameters('AcceleratedNetworking')]" - }, - "tags": "[parameters('Tags')]" }, - { - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2023-09-01", - "name": "[parameters('VMName')]", - "location": "[parameters('Location')]", - "zones": "[variables('varAvailabilityZone')]", - "identity": "[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", - "properties": { - "osProfile": { - "computerName": "[parameters('VMName')]", - "adminUsername": "[parameters('AdminUsername')]", - "adminPassword": "[parameters('AdminPassword')]" - }, - "hardwareProfile": { - "vmSize": "[parameters('VMSize')]" - }, - "storageProfile": { - "osDisk": { - "name": "[format('{0}-OSDisk', parameters('VMName'))]", - "createOption": "FromImage", - "deleteOption": "Delete", - "managedDisk": { - "storageAccountType": "[parameters('DiskType')]" - } - }, - "ImageReference": "[parameters('ImageReference')]" - }, - "securityProfile": "[parameters('SecurityProfile')]", - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": true - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", - "properties": { - "deleteOption": "Delete" - } - } - ] - }, - "licenseType": "Windows_Client" - }, - "tags": "[parameters('Tags')]", - "dependsOn": [ - "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" - ] + "copy": { + "name": "deploySessionHosts", + "count": "[length(parameters('VMNames'))]" } - ] } - } - } - ] -} \ No newline at end of file + ] +} From 96bc414f8909ec7337ee46eb9a568572c632d93e Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:08:44 +0100 Subject: [PATCH 28/83] Updated bundle version --- FunctionApp/host.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FunctionApp/host.json b/FunctionApp/host.json index 6744b7b..70c29a0 100644 --- a/FunctionApp/host.json +++ b/FunctionApp/host.json @@ -10,10 +10,10 @@ }, "extensionBundle": { "id": "Microsoft.Azure.Functions.ExtensionBundle", - "version": "[3.*, 4.0.0)" + "version": "[4.*, 5.0.0)" }, "managedDependency": { "enabled": true }, "functionTimeout": "00:10:00" -} \ No newline at end of file +} From a137d81000963fea0532433a4892ace5b9196fce Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:14:32 +0100 Subject: [PATCH 29/83] updated base image selector --- deploy/bicep/DeployAVDSessionHostReplacer.bicep | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/bicep/DeployAVDSessionHostReplacer.bicep b/deploy/bicep/DeployAVDSessionHostReplacer.bicep index 3a8bd47..cb100ae 100644 --- a/deploy/bicep/DeployAVDSessionHostReplacer.bicep +++ b/deploy/bicep/DeployAVDSessionHostReplacer.bicep @@ -38,12 +38,12 @@ param MarketPlaceOrCustomImage string '2022-datacenter-core-smalldisk-g2' 'win11-21h2-avd-m365' 'win11-23h2-avd' - 'win11-23h2-avd-m365' + 'win11-25h2-avd-m365' 'win11-22h2-avd' '2022-datacenter-g2' 'win10-22h2-avd-g2' ]) -param MarketPlaceImage string = 'win11-23h2-avd-m365' +param MarketPlaceImage string = 'win11-25h2-avd-m365' param GalleryImageId string = '' @allowed([ @@ -214,10 +214,10 @@ var varMarketPlaceImages = { offer: 'windows-11' sku: 'win11-23h2-avd' } - 'win11-23h2-avd-m365': { + 'win11-25h2-avd-m365': { publisher: 'MicrosoftWindowsDesktop' offer: 'office-365' - sku: 'win11-23h2-avd-m365' + sku: 'win11-25h2-avd-m365' } } var varImageReference = MarketPlaceOrCustomImage == 'Marketplace' From 4ea1deadc3056860cb91c635ccb8847925bc1126 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:15:55 +0100 Subject: [PATCH 30/83] updated base image selector --- deploy/arm/DeployAVDSessionHostReplacer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 24c542f..74ccd2b 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -82,7 +82,7 @@ "2022-datacenter-core-smalldisk-g2", "win11-21h2-avd-m365", "win11-23h2-avd", - "win11-23h2-avd-m365", + "win11-25h2-avd-m365", "win11-22h2-avd", "2022-datacenter-g2", "win10-22h2-avd-g2" @@ -384,10 +384,10 @@ "offer": "windows-11", "sku": "win11-23h2-avd" }, - "win11-23h2-avd-m365": { + "win11-25h2-avd-m365": { "publisher": "MicrosoftWindowsDesktop", "offer": "office-365", - "sku": "win11-23h2-avd-m365" + "sku": "win11-25h2-avd-m365" } }, "varImageReference": "[if(equals(parameters('MarketPlaceOrCustomImage'), 'Marketplace'), createObject('publisher', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].publisher, 'offer', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].offer, 'sku', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].sku, 'version', 'latest'), createObject('Id', parameters('GalleryImageId')))]", From 49489143d1b5eda978bb1000bef9e290ff90e1ca Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:18:52 +0100 Subject: [PATCH 31/83] Update PowerShell version to 7.4 --- Build/Bicep/FunctionApps.bicep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build/Bicep/FunctionApps.bicep b/Build/Bicep/FunctionApps.bicep index 150a298..23e18bf 100644 --- a/Build/Bicep/FunctionApps.bicep +++ b/Build/Bicep/FunctionApps.bicep @@ -286,7 +286,7 @@ resource functionApp 'Microsoft.Web/sites@2022-03-01' = { serverFarmId: appServicePlan.id siteConfig: { use32BitWorkerProcess: false - powerShellVersion: '7.2' + powerShellVersion: '7.4' netFrameworkVersion: 'v6.0' appSettings: varFunctionAppSettings ftpsState: 'Disabled' From 1df05b1e9f98b5f343c4c3dfcd057dac83f1dddb Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Mon, 5 Jan 2026 16:31:33 +0100 Subject: [PATCH 32/83] Updated-hibernation-disksize-dns --- .../DeploySessionHosts.json | 806 ++++++++++-------- 1 file changed, 439 insertions(+), 367 deletions(-) diff --git a/StandardSessionHostTemplate/DeploySessionHosts.json b/StandardSessionHostTemplate/DeploySessionHosts.json index ee59af5..4cfd8b0 100644 --- a/StandardSessionHostTemplate/DeploySessionHosts.json +++ b/StandardSessionHostTemplate/DeploySessionHosts.json @@ -1,380 +1,452 @@ { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.32.4.45862", + "templateHash": "13792602582245425536" + } + }, + "parameters": { + "Location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "AvailabilityZones": { + "type": "array", + "defaultValue": [] + }, + "VMNames": { + "type": "array" + }, + "VMNamePrefixLength": { + "type": "int" + }, + "VMSize": { + "type": "string" + }, + "SubnetID": { + "type": "string" + }, + "AdminUsername": { + "type": "string" + }, + "AcceleratedNetworking": { + "type": "bool" + }, + "DiskType": { + "type": "string" + }, + "DiskSizeGB": { + "type": "int", + "defaultValue": 128, + "metadata": { + "description": "OS disk size in GB" + } + }, + "DnsServers": { + "type": "array", "metadata": { - "_generator": { - "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "8994538122455151797" - } + "description": "List of DNS server IP addresses to set on the NIC" + } + }, + "Tags": { + "type": "object", + "defaultValue": {} }, - "parameters": { - "Location": { - "type": "string", - "defaultValue": "[resourceGroup().location]" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "VMNames": { - "type": "array" - }, - "VMNamePrefixLength": { - "type": "int" - }, - "VMSize": { - "type": "string" - }, - "SubnetID": { - "type": "string" - }, - "AdminUsername": { - "type": "string" - }, - "AcceleratedNetworking": { - "type": "bool" - }, - "DiskType": { - "type": "string" - }, - "Tags": { - "type": "object", - "defaultValue": {} - }, - "ImageReference": { - "type": "object" - }, - "SecurityProfile": { - "type": "object", - "defaultValue": {} - }, - "HostPoolName": { - "type": "string" - }, - "HostPoolToken": { - "type": "securestring" - }, - "WVDArtifactsURL": { - "type": "string", - "defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" + "ImageReference": { + "type": "object" + }, + "SecurityProfile": { + "type": "object", + "defaultValue": {} + }, + "HostPoolName": { + "type": "string" + }, + "HostPoolToken": { + "type": "securestring" + }, + "WVDArtifactsURL": { + "type": "string", + "defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" + }, + "DomainJoinObject": { + "type": "object", + "defaultValue": {} + }, + "DomainJoinPassword": { + "type": "securestring", + "defaultValue": "" + } + }, + "resources": [ + { + "copy": { + "name": "deploySessionHosts", + "count": "[length(parameters('VMNames'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" }, - "DomainJoinObject": { - "type": "object", - "defaultValue": {} + "mode": "Incremental", + "parameters": { + "AcceleratedNetworking": { + "value": "[parameters('AcceleratedNetworking')]" + }, + "AdminUsername": { + "value": "[parameters('AdminUsername')]" + }, + "HostPoolName": { + "value": "[parameters('HostPoolName')]" + }, + "HostPoolToken": { + "value": "[parameters('HostPoolToken')]" + }, + "ImageReference": { + "value": "[parameters('ImageReference')]" + }, + "SecurityProfile": { + "value": "[parameters('SecurityProfile')]" + }, + "SubnetID": { + "value": "[parameters('SubnetID')]" + }, + "VMName": { + "value": "[parameters('VMNames')[copyIndex()]]" + }, + "VMNamePrefixLength": { + "value": "[parameters('VMNamePrefixLength')]" + }, + "VMSize": { + "value": "[parameters('VMSize')]" + }, + "DiskType": { + "value": "[parameters('DiskType')]" + }, + "DiskSizeGB": { + "value": "[parameters('DiskSizeGB')]" + }, + "DnsServers": { + "value": "[parameters('DnsServers')]" + }, + "WVDArtifactsURL": { + "value": "[parameters('WVDArtifactsURL')]" + }, + "DomainJoinObject": { + "value": "[parameters('DomainJoinObject')]" + }, + "DomainJoinPassword": { + "value": "[parameters('DomainJoinPassword')]" + }, + "Location": { + "value": "[parameters('Location')]" + }, + "AvailabilityZones": { + "value": "[parameters('AvailabilityZones')]" + }, + "Tags": { + "value": "[parameters('Tags')]" + } }, - "DomainJoinPassword": { - "type": "securestring", - "defaultValue": "" - } - }, - "resources": [ - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "AcceleratedNetworking": { - "value": "[parameters('AcceleratedNetworking')]" - }, - "AdminUsername": { - "value": "[parameters('AdminUsername')]" - }, - "HostPoolName": { - "value": "[parameters('HostPoolName')]" - }, - "HostPoolToken": { - "value": "[parameters('HostPoolToken')]" - }, - "ImageReference": { - "value": "[parameters('ImageReference')]" - }, - "SecurityProfile": { - "value": "[parameters('SecurityProfile')]" - }, - "SubnetID": { - "value": "[parameters('SubnetID')]" - }, - "VMName": { - "value": "[parameters('VMNames')[copyIndex()]]" - }, - "VMNamePrefixLength": { - "value": "[parameters('VMNamePrefixLength')]" - }, - "VMSize": { - "value": "[parameters('VMSize')]" - }, - "DiskType": { - "value": "[parameters('DiskType')]" - }, - "WVDArtifactsURL": { - "value": "[parameters('WVDArtifactsURL')]" - }, - "DomainJoinObject": { - "value": "[parameters('DomainJoinObject')]" - }, - "DomainJoinPassword": { - "value": "[parameters('DomainJoinPassword')]" - }, - "Location": { - "value": "[parameters('Location')]" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.32.4.45862", + "templateHash": "9402242463429439832" + } + }, + "parameters": { + "VMName": { + "type": "string" + }, + "VMNamePrefixLength": { + "type": "int" + }, + "VMSize": { + "type": "string" + }, + "DiskType": { + "type": "string" + }, + "DiskSizeGB": { + "type": "int", + "defaultValue": 128, + "metadata": { + "description": "OS disk size in GB" + } + }, + "DnsServers": { + "type": "array", + "metadata": { + "description": "List of DNS server IP addresses to set on the NIC" + } + }, + "Location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "AvailabilityZones": { + "type": "array", + "defaultValue": [] + }, + "SubnetID": { + "type": "string" + }, + "AdminUsername": { + "type": "string" + }, + "AdminPassword": { + "type": "securestring", + "defaultValue": "[newGuid()]" + }, + "AcceleratedNetworking": { + "type": "bool" + }, + "Tags": { + "type": "object", + "defaultValue": {} + }, + "ImageReference": { + "type": "object" + }, + "SecurityProfile": { + "type": "object" + }, + "HostPoolName": { + "type": "string" + }, + "HostPoolToken": { + "type": "securestring" + }, + "WVDArtifactsURL": { + "type": "string" + }, + "DomainJoinObject": { + "type": "object", + "defaultValue": {} + }, + "DomainJoinPassword": { + "type": "securestring", + "defaultValue": "" + } + }, + "variables": { + "varRequireNvidiaGPU": "[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", + "varVMNumber": "[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", + "varAvailabilityZone": "[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" + }, + "resources": [ + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[format('{0}/{1}', parameters('VMName'), 'deployIntegrityMonitoring')]", + "location": "[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Azure.Security.WindowsAttestation", + "type": "GuestAttestation", + "typeHandlerVersion": "1.0", + "autoUpgradeMinorVersion": true, + "settings": { + "AttestationConfig": { + "MaaSettings": { + "maaEndpoint": "", + "maaTenantName": "Guest Attestation" }, - "AvailabilityZones": { - "value": "[parameters('AvailabilityZones')]" + "AscSettings": { + "ascReportingEndpoint": "", + "ascReportingFrequency": "" }, - "Tags": { - "value": "[parameters('Tags')]" - } + "useCustomToken": "false", + "disableAlerts": "false" + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[variables('varRequireNvidiaGPU')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", + "location": "[parameters('Location')]", + "properties": { + "publisher": "Microsoft.HpcCompute", + "type": "NvidiaGpuDriverWindows", + "typeHandlerVersion": "1.6", + "autoUpgradeMinorVersion": true + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployIntegrityMonitoring')]", + "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[not(equals(parameters('HostPoolName'), ''))]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", + "location": "[parameters('Location')]", + "properties": { + "publisher": "Microsoft.PowerShell", + "type": "DSC", + "typeHandlerVersion": "2.77", + "autoUpgradeMinorVersion": true, + "settings": { + "modulesUrl": "[parameters('WVDArtifactsURL')]", + "configurationFunction": "Configuration.ps1\\AddSessionHost", + "properties": { + "hostPoolName": "[parameters('HostPoolName')]", + "registrationInfoToken": "[parameters('HostPoolToken')]", + "aadJoin": "[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", + "useAgentDownloadEndpoint": true, + "mdmId": "[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", + "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", + "location": "[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Azure.ActiveDirectory", + "type": "AADLoginForWindows", + "typeHandlerVersion": "2.0", + "autoUpgradeMinorVersion": true, + "settings": "[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", + "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", + "location": "[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Compute", + "type": "JSonADDomainExtension", + "typeHandlerVersion": "1.3", + "autoUpgradeMinorVersion": true, + "settings": { + "Name": "[parameters('DomainJoinObject').DomainName]", + "OUPath": "[parameters('DomainJoinObject').ADOUPath]", + "User": "[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", + "Restart": "true", + "Options": 3 }, - "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": "7267962610869920549" - } - }, - "parameters": { - "VMName": { - "type": "string" - }, - "VMNamePrefixLength": { - "type": "int" - }, - "VMSize": { - "type": "string" - }, - "DiskType": { - "type": "string" - }, - "Location": { - "type": "string", - "defaultValue": "[resourceGroup().location]" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "SubnetID": { - "type": "string" - }, - "AdminUsername": { - "type": "string" - }, - "AdminPassword": { - "type": "securestring", - "defaultValue": "[newGuid()]" - }, - "AcceleratedNetworking": { - "type": "bool" - }, - "Tags": { - "type": "object", - "defaultValue": {} - }, - "ImageReference": { - "type": "object" - }, - "SecurityProfile": { - "type": "object" - }, - "HostPoolName": { - "type": "string" - }, - "HostPoolToken": { - "type": "securestring" - }, - "WVDArtifactsURL": { - "type": "string" - }, - "DomainJoinObject": { - "type": "object", - "defaultValue": {} - }, - "DomainJoinPassword": { - "type": "securestring", - "defaultValue": "" - } - }, - "variables": { - "varRequireNvidiaGPU": "[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", - "varVMNumber": "[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", - "varAvailabilityZone": "[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" - }, - "resources": [ - { - "condition": "[variables('varRequireNvidiaGPU')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", - "location": "[parameters('Location')]", - "properties": { - "publisher": "Microsoft.HpcCompute", - "type": "NvidiaGpuDriverWindows", - "typeHandlerVersion": "1.6", - "autoUpgradeMinorVersion": true - }, - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[not(equals(parameters('HostPoolName'), ''))]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", - "location": "[parameters('Location')]", - "properties": { - "publisher": "Microsoft.PowerShell", - "type": "DSC", - "typeHandlerVersion": "2.77", - "autoUpgradeMinorVersion": true, - "settings": { - "modulesUrl": "[parameters('WVDArtifactsURL')]", - "configurationFunction": "Configuration.ps1\\AddSessionHost", - "properties": { - "hostPoolName": "[parameters('HostPoolName')]", - "registrationInfoToken": "[parameters('HostPoolToken')]", - "aadJoin": "[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", - "useAgentDownloadEndpoint": true, - "mdmId": "[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", - "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", - "location": "[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Azure.ActiveDirectory", - "type": "AADLoginForWindows", - "typeHandlerVersion": "2.0", - "autoUpgradeMinorVersion": true, - "settings": "[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" - }, - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", - "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", - "location": "[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Compute", - "type": "JSonADDomainExtension", - "typeHandlerVersion": "1.3", - "autoUpgradeMinorVersion": true, - "settings": { - "Name": "[parameters('DomainJoinObject').DomainName]", - "OUPath": "[parameters('DomainJoinObject').ADOUPath]", - "User": "[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", - "Restart": "true", - "Options": 3 - }, - "protectedSettings": { - "Password": "[parameters('DomainJoinPassword')]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", - "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2023-09-01", - "name": "[format('{0}-vNIC', parameters('VMName'))]", - "location": "[parameters('Location')]", - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "subnet": { - "id": "[parameters('SubnetID')]" - } - } - } - ], - "enableAcceleratedNetworking": "[parameters('AcceleratedNetworking')]" - }, - "tags": "[parameters('Tags')]" - }, - { - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2023-09-01", - "name": "[parameters('VMName')]", - "location": "[parameters('Location')]", - "zones": "[variables('varAvailabilityZone')]", - "identity": "[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", - "properties": { - "osProfile": { - "computerName": "[parameters('VMName')]", - "adminUsername": "[parameters('AdminUsername')]", - "adminPassword": "[parameters('AdminPassword')]" - }, - "hardwareProfile": { - "vmSize": "[parameters('VMSize')]" - }, - "storageProfile": { - "osDisk": { - "name": "[format('{0}-OSDisk', parameters('VMName'))]", - "createOption": "FromImage", - "deleteOption": "Delete", - "managedDisk": { - "storageAccountType": "[parameters('DiskType')]" - } - }, - "ImageReference": "[parameters('ImageReference')]" - }, - "securityProfile": "[parameters('SecurityProfile')]", - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": true - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", - "properties": { - "deleteOption": "Delete" - } - } - ] - }, - "licenseType": "Windows_Client" - }, - "tags": "[parameters('Tags')]", - "dependsOn": [ - "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" - ] - } - ] + "protectedSettings": { + "Password": "[parameters('DomainJoinPassword')]" } + }, + "dependsOn": [ + "[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", + "[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] }, - "copy": { - "name": "deploySessionHosts", - "count": "[length(parameters('VMNames'))]" + +{ + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2023-09-01", + "name": "[format('{0}-vNIC', parameters('VMName'))]", + "location": "[parameters('Location')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[parameters('SubnetID')]" + } + } + } + ], + +"dnsSettings": { + "dnsServers": "[parameters('DnsServers')]" +}, + "enableAcceleratedNetworking": "[parameters('AcceleratedNetworking')]" + }, + "tags": "[parameters('Tags')]" +}, + + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2023-09-01", + "name": "[parameters('VMName')]", + "location": "[parameters('Location')]", + "zones": "[variables('varAvailabilityZone')]", + "identity": "[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", + "properties": { + "osProfile": { + "computerName": "[parameters('VMName')]", + "adminUsername": "[parameters('AdminUsername')]", + "adminPassword": "[parameters('AdminPassword')]" + }, + "hardwareProfile": { + "vmSize": "[parameters('VMSize')]" + }, + "additionalCapabilities": { + "hibernationEnabled": true + }, + "storageProfile": { + "osDisk": { + "name": "[format('{0}-OSDisk', parameters('VMName'))]", + "createOption": "FromImage", + "deleteOption": "Delete", + "diskSizeGB": "[parameters('DiskSizeGB')]", + "managedDisk": { + "storageAccountType": "[parameters('DiskType')]" + } + }, + "ImageReference": "[parameters('ImageReference')]" + }, + "securityProfile": "[parameters('SecurityProfile')]", + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": true + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", + "properties": { + "deleteOption": "Delete" + } + } + ] + }, + "licenseType": "Windows_Client" + }, + "tags": "[parameters('Tags')]", + "dependsOn": [ + "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" + ] } + ] } - ] + } + } + ] } From 066b1b700a71d64d892d947181d6abb323ce8c1c Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Mon, 5 Jan 2026 18:01:19 +0100 Subject: [PATCH 33/83] fix image --- deploy/bicep/DeployAVDSessionHostReplacer.bicep | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/bicep/DeployAVDSessionHostReplacer.bicep b/deploy/bicep/DeployAVDSessionHostReplacer.bicep index cb100ae..3a8bd47 100644 --- a/deploy/bicep/DeployAVDSessionHostReplacer.bicep +++ b/deploy/bicep/DeployAVDSessionHostReplacer.bicep @@ -38,12 +38,12 @@ param MarketPlaceOrCustomImage string '2022-datacenter-core-smalldisk-g2' 'win11-21h2-avd-m365' 'win11-23h2-avd' - 'win11-25h2-avd-m365' + 'win11-23h2-avd-m365' 'win11-22h2-avd' '2022-datacenter-g2' 'win10-22h2-avd-g2' ]) -param MarketPlaceImage string = 'win11-25h2-avd-m365' +param MarketPlaceImage string = 'win11-23h2-avd-m365' param GalleryImageId string = '' @allowed([ @@ -214,10 +214,10 @@ var varMarketPlaceImages = { offer: 'windows-11' sku: 'win11-23h2-avd' } - 'win11-25h2-avd-m365': { + 'win11-23h2-avd-m365': { publisher: 'MicrosoftWindowsDesktop' offer: 'office-365' - sku: 'win11-25h2-avd-m365' + sku: 'win11-23h2-avd-m365' } } var varImageReference = MarketPlaceOrCustomImage == 'Marketplace' From 03da969fcbbc4153cdd32ea245bc3c96e3d18e14 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Mon, 5 Jan 2026 18:02:10 +0100 Subject: [PATCH 34/83] Replace win11-25h2-avd-m365 with win11-23h2-avd-m365 --- deploy/arm/DeployAVDSessionHostReplacer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 74ccd2b..24c542f 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -82,7 +82,7 @@ "2022-datacenter-core-smalldisk-g2", "win11-21h2-avd-m365", "win11-23h2-avd", - "win11-25h2-avd-m365", + "win11-23h2-avd-m365", "win11-22h2-avd", "2022-datacenter-g2", "win10-22h2-avd-g2" @@ -384,10 +384,10 @@ "offer": "windows-11", "sku": "win11-23h2-avd" }, - "win11-25h2-avd-m365": { + "win11-23h2-avd-m365": { "publisher": "MicrosoftWindowsDesktop", "offer": "office-365", - "sku": "win11-25h2-avd-m365" + "sku": "win11-23h2-avd-m365" } }, "varImageReference": "[if(equals(parameters('MarketPlaceOrCustomImage'), 'Marketplace'), createObject('publisher', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].publisher, 'offer', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].offer, 'sku', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].sku, 'version', 'latest'), createObject('Id', parameters('GalleryImageId')))]", From 4bb8f3c60d804931f1a1fed0e1a107a6d616519d Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Mon, 5 Jan 2026 22:57:54 +0100 Subject: [PATCH 35/83] updated function app url --- deploy/arm/DeployAVDSessionHostReplacer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 24c542f..4f43146 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -597,7 +597,7 @@ }, "FunctionAppZipUrl": { "type": "string", - "defaultValue": "https://github.com/Azure/AVDSessionHostReplacer/releases/download/v0.3.1-beta.3/FunctionApp.zip", + "defaultValue": "https://github.com/Azure/AVDSessionHostReplacer/releases/download/v0.3.3/FunctionApp.zip", "metadata": { "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." } From fdebe9eb655c7d9cdd04c14034cb0c360fef6daf Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Mon, 5 Jan 2026 23:59:09 +0100 Subject: [PATCH 36/83] Add files via upload --- FunctionApp/Function.zip | Bin 0 -> 50597 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 FunctionApp/Function.zip diff --git a/FunctionApp/Function.zip b/FunctionApp/Function.zip new file mode 100644 index 0000000000000000000000000000000000000000..335593ee28dba212f61bf35ac8918de5ca8dc10d GIT binary patch literal 50597 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 MArL%4!UTf<14#>4sQ>@~ literal 0 HcmV?d00001 From 30e36624e1962676f60e9f9921c7a65c1087ea27 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 00:00:53 +0100 Subject: [PATCH 37/83] Update FunctionAppZipUrl to new GitHub location --- deploy/arm/DeployAVDSessionHostReplacer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 4f43146..03ff4a7 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -597,7 +597,7 @@ }, "FunctionAppZipUrl": { "type": "string", - "defaultValue": "https://github.com/Azure/AVDSessionHostReplacer/releases/download/v0.3.3/FunctionApp.zip", + "defaultValue": "https://github.com/stefze/AVDSessionHostReplacer/blob/main/FunctionApp/Function.zip", "metadata": { "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." } From bdee8a205fbd2e7fe9e84e78d22d20ba2c6339b1 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 00:06:40 +0100 Subject: [PATCH 38/83] Update FunctionAppZipUrl to point to latest release --- deploy/arm/DeployAVDSessionHostReplacer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 03ff4a7..4f43146 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -597,7 +597,7 @@ }, "FunctionAppZipUrl": { "type": "string", - "defaultValue": "https://github.com/stefze/AVDSessionHostReplacer/blob/main/FunctionApp/Function.zip", + "defaultValue": "https://github.com/Azure/AVDSessionHostReplacer/releases/download/v0.3.3/FunctionApp.zip", "metadata": { "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." } From 1bd297219dc8a874acac8544f558b39ca7a7ecef Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 12:20:42 +0100 Subject: [PATCH 39/83] Add files via upload --- FunctionApp/FunctionApp.zip | Bin 0 -> 49223 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 FunctionApp/FunctionApp.zip diff --git a/FunctionApp/FunctionApp.zip b/FunctionApp/FunctionApp.zip new file mode 100644 index 0000000000000000000000000000000000000000..6b6f372b7441b8ec614fba59bf431bfad125fa69 GIT binary patch literal 49223 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
y_QXnF>z%g$~P;sWdazy9kM=|A{ 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 MuM4{6+eZBB7aYmRfB*mh literal 0 HcmV?d00001 From f43c3d7e1b278fd776ff6a454d00e5dfc8566d70 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 12:21:34 +0100 Subject: [PATCH 40/83] Update DeployAVDSessionHostReplacer.json --- deploy/arm/DeployAVDSessionHostReplacer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 4f43146..09086b3 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -595,9 +595,9 @@ "description": "Required: Yes | Name of the Function App." } }, - "FunctionAppZipUrl": { + "FunctionAppUrl": { "type": "string", - "defaultValue": "https://github.com/Azure/AVDSessionHostReplacer/releases/download/v0.3.3/FunctionApp.zip", + "defaultValue": "https://github.com/stefze/AVDSessionHostReplacer/blob/main/FunctionApp/FunctionApp.zip", "metadata": { "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." } From 30e82fdb1249fad9980477278800d2cc8f47657f Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 12:32:59 +0100 Subject: [PATCH 41/83] Updated template specs --- deploy/arm/DeployAVDSessionHostReplacer.json | 835 ++++++++++--------- 1 file changed, 439 insertions(+), 396 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 09086b3..e6e30cf 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -862,415 +862,458 @@ }, "variables": { "$fxv#0": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.4.45862", + "templateHash": "13792602582245425536" + } + }, + "parameters": { + "Location": { + "type": "string", + "defaultValue": "[[resourceGroup().location]" + }, + "AvailabilityZones": { + "type": "array", + "defaultValue": [] + }, + "VMNames": { + "type": "array" + }, + "VMNamePrefixLength": { + "type": "int" + }, + "VMSize": { + "type": "string" + }, + "SubnetID": { + "type": "string" + }, + "AdminUsername": { + "type": "string" + }, + "AcceleratedNetworking": { + "type": "bool" + }, + "DiskType": { + "type": "string" + }, + "DiskSizeGB": { + "type": "int", + "defaultValue": 128, + "metadata": { + "description": "OS disk size in GB" + } + }, + "DnsServers": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "List of DNS server IP addresses to set on the NIC" + } + }, + "Tags": { + "type": "object", + "defaultValue": {} + }, + "ImageReference": { + "type": "object" + }, + "SecurityProfile": { + "type": "object", + "defaultValue": {} + }, + "HostPoolName": { + "type": "string" + }, + "HostPoolToken": { + "type": "securestring" + }, + "WVDArtifactsURL": { + "type": "string", + "defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" + }, + "DomainJoinObject": { + "type": "object", + "defaultValue": {} + }, + "DomainJoinPassword": { + "type": "securestring", + "defaultValue": "" + } + }, + "resources": [ + { + "copy": { + "name": "deploySessionHosts", + "count": "[[length(parameters('VMNames'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "AcceleratedNetworking": { + "value": "[[parameters('AcceleratedNetworking')]" + }, + "AdminUsername": { + "value": "[[parameters('AdminUsername')]" + }, + "HostPoolName": { + "value": "[[parameters('HostPoolName')]" + }, + "HostPoolToken": { + "value": "[[parameters('HostPoolToken')]" + }, + "ImageReference": { + "value": "[[parameters('ImageReference')]" + }, + "SecurityProfile": { + "value": "[[parameters('SecurityProfile')]" + }, + "SubnetID": { + "value": "[[parameters('SubnetID')]" + }, + "VMName": { + "value": "[[parameters('VMNames')[copyIndex()]]" + }, + "VMNamePrefixLength": { + "value": "[[parameters('VMNamePrefixLength')]" + }, + "VMSize": { + "value": "[[parameters('VMSize')]" + }, + "DiskType": { + "value": "[[parameters('DiskType')]" + }, + "DiskSizeGB": { + "value": "[[parameters('DiskSizeGB')]" + }, + "DnsServers": { + "value": "[[parameters('DnsServers')]" + }, + "WVDArtifactsURL": { + "value": "[[parameters('WVDArtifactsURL')]" + }, + "DomainJoinObject": { + "value": "[[parameters('DomainJoinObject')]" + }, + "DomainJoinPassword": { + "value": "[[parameters('DomainJoinPassword')]" + }, + "Location": { + "value": "[[parameters('Location')]" + }, + "AvailabilityZones": { + "value": "[[parameters('AvailabilityZones')]" + }, + "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.32.4.45862", + "templateHash": "9402242463429439832" + } + }, + "parameters": { + "VMName": { + "type": "string" + }, + "VMNamePrefixLength": { + "type": "int" + }, + "VMSize": { + "type": "string" + }, + "DiskType": { + "type": "string" + }, + "DiskSizeGB": { + "type": "int", + "defaultValue": 128, "metadata": { - "_generator": { - "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "8994538122455151797" + "description": "OS disk size in GB" + } + }, + "DnsServers": { + "type": "array", + "metadata": { + "description": "List of DNS server IP addresses to set on the NIC" + } + }, + "Location": { + "type": "string", + "defaultValue": "[[resourceGroup().location]" + }, + "AvailabilityZones": { + "type": "array", + "defaultValue": [] + }, + "SubnetID": { + "type": "string" + }, + "AdminUsername": { + "type": "string" + }, + "AdminPassword": { + "type": "securestring", + "defaultValue": "[[newGuid()]" + }, + "AcceleratedNetworking": { + "type": "bool" + }, + "Tags": { + "type": "object", + "defaultValue": {} + }, + "ImageReference": { + "type": "object" + }, + "SecurityProfile": { + "type": "object" + }, + "HostPoolName": { + "type": "string" + }, + "HostPoolToken": { + "type": "securestring" + }, + "WVDArtifactsURL": { + "type": "string" + }, + "DomainJoinObject": { + "type": "object", + "defaultValue": {} + }, + "DomainJoinPassword": { + "type": "securestring", + "defaultValue": "" + } + }, + "variables": { + "varRequireNvidiaGPU": "[[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", + "varVMNumber": "[[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", + "varAvailabilityZone": "[[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" + }, + "resources": [ + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'deployIntegrityMonitoring')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Azure.Security.WindowsAttestation", + "type": "GuestAttestation", + "typeHandlerVersion": "1.0", + "autoUpgradeMinorVersion": true, + "settings": { + "AttestationConfig": { + "MaaSettings": { + "maaEndpoint": "", + "maaTenantName": "Guest Attestation" + }, + "AscSettings": { + "ascReportingEndpoint": "", + "ascReportingFrequency": "" + }, + "useCustomToken": "false", + "disableAlerts": "false" + } } }, - "parameters": { - "Location": { - "type": "string", - "defaultValue": "[[resourceGroup().location]" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "VMNames": { - "type": "array" - }, - "VMNamePrefixLength": { - "type": "int" - }, - "VMSize": { - "type": "string" - }, - "SubnetID": { - "type": "string" - }, - "AdminUsername": { - "type": "string" - }, - "AcceleratedNetworking": { - "type": "bool" - }, - "DiskType": { - "type": "string" - }, - "Tags": { - "type": "object", - "defaultValue": {} - }, - "ImageReference": { - "type": "object" + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[variables('varRequireNvidiaGPU')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.HpcCompute", + "type": "NvidiaGpuDriverWindows", + "typeHandlerVersion": "1.6", + "autoUpgradeMinorVersion": true + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployIntegrityMonitoring')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[not(equals(parameters('HostPoolName'), ''))]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.PowerShell", + "type": "DSC", + "typeHandlerVersion": "2.77", + "autoUpgradeMinorVersion": true, + "settings": { + "modulesUrl": "[[parameters('WVDArtifactsURL')]", + "configurationFunction": "Configuration.ps1\\AddSessionHost", + "properties": { + "hostPoolName": "[[parameters('HostPoolName')]", + "registrationInfoToken": "[[parameters('HostPoolToken')]", + "aadJoin": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", + "useAgentDownloadEndpoint": true, + "mdmId": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" + } + } + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Azure.ActiveDirectory", + "type": "AADLoginForWindows", + "typeHandlerVersion": "2.0", + "autoUpgradeMinorVersion": true, + "settings": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Compute", + "type": "JSonADDomainExtension", + "typeHandlerVersion": "1.3", + "autoUpgradeMinorVersion": true, + "settings": { + "Name": "[[parameters('DomainJoinObject').DomainName]", + "OUPath": "[[parameters('DomainJoinObject').ADOUPath]", + "User": "[[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", + "Restart": "true", + "Options": 3 }, - "SecurityProfile": { - "type": "object", - "defaultValue": {} + "protectedSettings": { + "Password": "[[parameters('DomainJoinPassword')]" + } + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + +{ + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2023-09-01", + "name": "[[format('{0}-vNIC', parameters('VMName'))]", + "location": "[[parameters('Location')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[[parameters('SubnetID')]" + } + } + } + ], + +"dnsSettings": { + "dnsServers": "[[parameters('DnsServers')]" +}, + "enableAcceleratedNetworking": "[[parameters('AcceleratedNetworking')]" + }, + "tags": "[[parameters('Tags')]" +}, + + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2023-09-01", + "name": "[[parameters('VMName')]", + "location": "[[parameters('Location')]", + "zones": "[[variables('varAvailabilityZone')]", + "identity": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", + "properties": { + "osProfile": { + "computerName": "[[parameters('VMName')]", + "adminUsername": "[[parameters('AdminUsername')]", + "adminPassword": "[[parameters('AdminPassword')]" }, - "HostPoolName": { - "type": "string" + "hardwareProfile": { + "vmSize": "[[parameters('VMSize')]" }, - "HostPoolToken": { - "type": "securestring" + "additionalCapabilities": { + "hibernationEnabled": true }, - "WVDArtifactsURL": { - "type": "string", - "defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" + "storageProfile": { + "osDisk": { + "name": "[[format('{0}-OSDisk', parameters('VMName'))]", + "createOption": "FromImage", + "deleteOption": "Delete", + "diskSizeGB": "[[parameters('DiskSizeGB')]", + "managedDisk": { + "storageAccountType": "[[parameters('DiskType')]" + } + }, + "ImageReference": "[[parameters('ImageReference')]" }, - "DomainJoinObject": { - "type": "object", - "defaultValue": {} + "securityProfile": "[[parameters('SecurityProfile')]", + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": true + } }, - "DomainJoinPassword": { - "type": "securestring", - "defaultValue": "" - } - }, - "resources": [ - { - "[string('copy')]": { - "name": "deploySessionHosts", - "count": "[[length(parameters('VMNames'))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "AcceleratedNetworking": { - "value": "[[parameters('AcceleratedNetworking')]" - }, - "AdminUsername": { - "value": "[[parameters('AdminUsername')]" - }, - "HostPoolName": { - "value": "[[parameters('HostPoolName')]" - }, - "HostPoolToken": { - "value": "[[parameters('HostPoolToken')]" - }, - "ImageReference": { - "value": "[[parameters('ImageReference')]" - }, - "SecurityProfile": { - "value": "[[parameters('SecurityProfile')]" - }, - "SubnetID": { - "value": "[[parameters('SubnetID')]" - }, - "VMName": { - "value": "[[parameters('VMNames')[copyIndex()]]" - }, - "VMNamePrefixLength": { - "value": "[[parameters('VMNamePrefixLength')]" - }, - "VMSize": { - "value": "[[parameters('VMSize')]" - }, - "DiskType": { - "value": "[[parameters('DiskType')]" - }, - "WVDArtifactsURL": { - "value": "[[parameters('WVDArtifactsURL')]" - }, - "DomainJoinObject": { - "value": "[[parameters('DomainJoinObject')]" - }, - "DomainJoinPassword": { - "value": "[[parameters('DomainJoinPassword')]" - }, - "Location": { - "value": "[[parameters('Location')]" - }, - "AvailabilityZones": { - "value": "[[parameters('AvailabilityZones')]" - }, - "Tags": { - "value": "[[parameters('Tags')]" + "networkProfile": { + "networkInterfaces": [ + { + "id": "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", + "properties": { + "deleteOption": "Delete" } - }, - "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": "7267962610869920549" - } - }, - "parameters": { - "VMName": { - "type": "string" - }, - "VMNamePrefixLength": { - "type": "int" - }, - "VMSize": { - "type": "string" - }, - "DiskType": { - "type": "string" - }, - "Location": { - "type": "string", - "defaultValue": "[[resourceGroup().location]" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "SubnetID": { - "type": "string" - }, - "AdminUsername": { - "type": "string" - }, - "AdminPassword": { - "type": "securestring", - "defaultValue": "[[newGuid()]" - }, - "AcceleratedNetworking": { - "type": "bool" - }, - "Tags": { - "type": "object", - "defaultValue": {} - }, - "ImageReference": { - "type": "object" - }, - "SecurityProfile": { - "type": "object" - }, - "HostPoolName": { - "type": "string" - }, - "HostPoolToken": { - "type": "securestring" - }, - "WVDArtifactsURL": { - "type": "string" - }, - "DomainJoinObject": { - "type": "object", - "defaultValue": {} - }, - "DomainJoinPassword": { - "type": "securestring", - "defaultValue": "" - } - }, - "variables": { - "varRequireNvidiaGPU": "[[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", - "varVMNumber": "[[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", - "varAvailabilityZone": "[[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" - }, - "resources": [ - { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'deployIntegrityMonitoring')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Azure.Security.WindowsAttestation", - "type": "GuestAttestation", - "typeHandlerVersion": "1.0", - "autoUpgradeMinorVersion": true, - "settings": { - "AttestationConfig": { - "MaaSettings": { - "maaEndpoint": "", - "maaTenantName": "Guest Attestation" - }, - "AscSettings": { - "ascReportingEndpoint": "", - "ascReportingFrequency": "" - }, - "useCustomToken": "false", - "disableAlerts": "false" - } - } - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[variables('varRequireNvidiaGPU')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.HpcCompute", - "type": "NvidiaGpuDriverWindows", - "typeHandlerVersion": "1.6", - "autoUpgradeMinorVersion": true - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployIntegrityMonitoring')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[not(equals(parameters('HostPoolName'), ''))]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.PowerShell", - "type": "DSC", - "typeHandlerVersion": "2.77", - "autoUpgradeMinorVersion": true, - "settings": { - "modulesUrl": "[[parameters('WVDArtifactsURL')]", - "configurationFunction": "Configuration.ps1\\AddSessionHost", - "properties": { - "hostPoolName": "[[parameters('HostPoolName')]", - "registrationInfoToken": "[[parameters('HostPoolToken')]", - "aadJoin": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", - "useAgentDownloadEndpoint": true, - "mdmId": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" - } - } - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Azure.ActiveDirectory", - "type": "AADLoginForWindows", - "typeHandlerVersion": "2.0", - "autoUpgradeMinorVersion": true, - "settings": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Compute", - "type": "JSonADDomainExtension", - "typeHandlerVersion": "1.3", - "autoUpgradeMinorVersion": true, - "settings": { - "Name": "[[parameters('DomainJoinObject').DomainName]", - "OUPath": "[[parameters('DomainJoinObject').ADOUPath]", - "User": "[[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", - "Restart": "true", - "Options": 3 - }, - "protectedSettings": { - "Password": "[[parameters('DomainJoinPassword')]" - } - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2023-09-01", - "name": "[[format('{0}-vNIC', parameters('VMName'))]", - "location": "[[parameters('Location')]", - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "subnet": { - "id": "[[parameters('SubnetID')]" - } - } - } - ], - "enableAcceleratedNetworking": "[[parameters('AcceleratedNetworking')]" - }, - "tags": "[[parameters('Tags')]" - }, - { - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2023-09-01", - "name": "[[parameters('VMName')]", - "location": "[[parameters('Location')]", - "zones": "[[variables('varAvailabilityZone')]", - "identity": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", - "properties": { - "osProfile": { - "computerName": "[[parameters('VMName')]", - "adminUsername": "[[parameters('AdminUsername')]", - "adminPassword": "[[parameters('AdminPassword')]" - }, - "hardwareProfile": { - "vmSize": "[[parameters('VMSize')]" - }, - "storageProfile": { - "osDisk": { - "name": "[[format('{0}-OSDisk', parameters('VMName'))]", - "createOption": "FromImage", - "deleteOption": "Delete", - "managedDisk": { - "storageAccountType": "[[parameters('DiskType')]" - } - }, - "ImageReference": "[[parameters('ImageReference')]" - }, - "securityProfile": "[[parameters('SecurityProfile')]", - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": true - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", - "properties": { - "deleteOption": "Delete" - } - } - ] - }, - "licenseType": "Windows_Client" - }, - "tags": "[[parameters('Tags')]", - "dependsOn": [ - "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" - ] - } - ] } - } - } + ] + }, + "licenseType": "Windows_Client" + }, + "tags": "[[parameters('Tags')]", + "dependsOn": [ + "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" ] } + ] + } + } + } + ] +} }, "resources": [ { From eb10695f521de11e519215a2f19fd3cfcdf4f249 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 12:38:13 +0100 Subject: [PATCH 42/83] Update DNS Servers --- deploy/arm/DeployAVDSessionHostReplacer.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index e6e30cf..80c3c11 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -113,6 +113,13 @@ "type": "string", "defaultValue": "" }, + "DnsServers": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional: List of DNS server IP addresses to configure on the network interface. Leave empty to use Azure DNS." + } + }, "IdentityServiceProvider": { "type": "string", "defaultValue": "EntraID", @@ -464,7 +471,7 @@ }, { "name": "_SessionHostParameters", - "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" + "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'DnsServers', parameters('DnsServers'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" }, { "name": "_SubscriptionId", From d1510ee531ad67623db660d4275a07031e2a6a01 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 12:42:29 +0100 Subject: [PATCH 43/83] Add DNS text box --- deploy/portal-ui/portal-ui.json | 35 ++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index 9a0936c..3f56994 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -486,6 +486,38 @@ ] } }, + +{ + "name": "DnsSettings", + "type": "Microsoft.Common.Section", + "label": "DNS settings", + "visible": true, + "elements": [ + { + "name": "DnsServers", + "type": "Microsoft.Common.Array", + "label": "DNS servers", + "toolTip": "Add up to two IPv4 DNS servers.", + "constraints": { + "required": false, + "minLength": 0, + "maxLength": 2 + }, + "defaultValue": [], + "element": { + "type": "Microsoft.Common.TextBox", + "label": "IPv4 address", + "placeholder": "e.g., 10.0.0.4", + "constraints": { + "required": true, + "regex": "^((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$", + "validationMessage": "Enter a valid IPv4 address (0–255 in each octet)." + } + } + } + ] +} + { "name": "DomainJoinSection", "type": "Microsoft.Common.Section", @@ -799,7 +831,8 @@ "ReplaceSessionHostOnNewImageVersion": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersion]", "ReplaceSessionHostOnNewImageVersionDelayDays": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersionDelayDays]", "VMNamesTemplateParameterName": "VMNames", - "SessionHostResourceGroupName": "[steps('optionalParametersStep')._SessionHostResourceGroupName]" + "SessionHostResourceGroupName": "[steps('optionalParametersStep')._SessionHostResourceGroupName]", + "DnsServers": "[steps('SessionHostsTemplate').DnsSettings.DnsServers]" } } } From 84b05fdc0257ecfdbee407c1dc35857783d944cf Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 12:44:04 +0100 Subject: [PATCH 44/83] Fix JSON formatting in portal-ui.json --- deploy/portal-ui/portal-ui.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index 3f56994..0cf927a 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -485,8 +485,7 @@ } ] } - }, - + }, { "name": "DnsSettings", "type": "Microsoft.Common.Section", @@ -516,8 +515,7 @@ } } ] -} - +}, { "name": "DomainJoinSection", "type": "Microsoft.Common.Section", From 0202fb6fb388261becd7fb9d5e9533e643c1b707 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:43:41 +0100 Subject: [PATCH 45/83] Add DiskSizeGB input field and update DnsSettings --- deploy/portal-ui/portal-ui.json | 75 +++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index 0cf927a..6d81f71 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -303,6 +303,18 @@ ] } }, + { + "name": "DiskSizeGB", + "type": "Microsoft.Common.TextBox", + "label": "OS Disk Size (GB)", + "defaultValue": "64", + "toolTip": "Size of the OS disk in GB. Must be between 30 and 4095 GB.", + "constraints": { + "required": true, + "regex": "^([3-9][0-9]|[1-9][0-9]{2,3}|40[0-8][0-9]|409[0-5])$", + "validationMessage": "Please enter a valid disk size between 30 and 4095 GB." + } + }, { "name": "optionMarketPlaceOrCustomImage", "type": "Microsoft.Common.OptionsGroup", @@ -485,37 +497,37 @@ } ] } - }, -{ - "name": "DnsSettings", - "type": "Microsoft.Common.Section", - "label": "DNS settings", - "visible": true, - "elements": [ - { - "name": "DnsServers", - "type": "Microsoft.Common.Array", - "label": "DNS servers", - "toolTip": "Add up to two IPv4 DNS servers.", - "constraints": { - "required": false, - "minLength": 0, - "maxLength": 2 - }, - "defaultValue": [], - "element": { - "type": "Microsoft.Common.TextBox", - "label": "IPv4 address", - "placeholder": "e.g., 10.0.0.4", - "constraints": { - "required": true, - "regex": "^((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$", - "validationMessage": "Enter a valid IPv4 address (0–255 in each octet)." - } - } - } - ] -}, + }, + { + "name": "DnsSettings", + "type": "Microsoft.Common.Section", + "label": "DNS settings", + "visible": true, + "elements": [ + { + "name": "DnsServers", + "type": "Microsoft.Common.Array", + "label": "DNS servers", + "toolTip": "Add up to two IPv4 DNS servers.", + "constraints": { + "required": false, + "minLength": 0, + "maxLength": 2 + }, + "defaultValue": [], + "element": { + "type": "Microsoft.Common.TextBox", + "label": "IPv4 address", + "placeholder": "e.g., 10.0.0.4", + "constraints": { + "required": true, + "regex": "^((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$", + "validationMessage": "Enter a valid IPv4 address (0–255 in each octet)." + } + } + } + ] + }, { "name": "DomainJoinSection", "type": "Microsoft.Common.Section", @@ -801,6 +813,7 @@ "SessionHostSize": "[steps('SessionHostsTemplate').SessionHostSize]", "AcceleratedNetworking": "[steps('SessionHostsTemplate').AcceleratedNetworking]", "SessionHostDiskType": "[steps('SessionHostsTemplate').SessionHostDiskType]", + "DiskSizeGB": "[int(steps('SessionHostsTemplate').DiskSizeGB)]", "MarketPlaceOrCustomImage": "[steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage]", "MarketPlaceImage": "[steps('SessionHostsTemplate').dropDownMarketPlaceImage]", "GalleryImageId": "[steps('SessionHostsTemplate').resourceSelectorSessionHostGalleryImageId.id]", From 722dafeea4e0372f5e0f8dfe3bdd6ed282f9734f Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:47:43 +0100 Subject: [PATCH 46/83] Change DiskSizeGB to Slider with new constraints --- deploy/portal-ui/portal-ui.json | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index 6d81f71..5aaef9e 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -305,14 +305,16 @@ }, { "name": "DiskSizeGB", - "type": "Microsoft.Common.TextBox", - "label": "OS Disk Size (GB)", - "defaultValue": "64", - "toolTip": "Size of the OS disk in GB. Must be between 30 and 4095 GB.", + "type": "Microsoft.Common.Slider", + "min": 64, + "max": 512, + "label": "OS Disk Size", + "subLabel": "GB", + "defaultValue": 128, + "showStepMarkers": false, + "toolTip": "Size of the OS disk in GB.", "constraints": { - "required": true, - "regex": "^([3-9][0-9]|[1-9][0-9]{2,3}|40[0-8][0-9]|409[0-5])$", - "validationMessage": "Please enter a valid disk size between 30 and 4095 GB." + "required": true } }, { From 8cc75fb00d8dbd25632245844ea9bfca38e352e0 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:52:37 +0100 Subject: [PATCH 47/83] Change default OS Disk Size from 128 to 64 GB --- deploy/portal-ui/portal-ui.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index 5aaef9e..df974c1 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -310,7 +310,7 @@ "max": 512, "label": "OS Disk Size", "subLabel": "GB", - "defaultValue": 128, + "defaultValue": 64, "showStepMarkers": false, "toolTip": "Size of the OS disk in GB.", "constraints": { From cd97dd283a0a4742741789dfc84ff0af233d38e2 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:54:21 +0100 Subject: [PATCH 48/83] Change default OS disk size to 128 GB --- deploy/portal-ui/portal-ui.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index df974c1..b5e3add 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -310,12 +310,13 @@ "max": 512, "label": "OS Disk Size", "subLabel": "GB", - "defaultValue": 64, + "defaultValue": 128, "showStepMarkers": false, "toolTip": "Size of the OS disk in GB.", "constraints": { "required": true - } + }, + "visible": true }, { "name": "optionMarketPlaceOrCustomImage", From f8307443bf7478d565d685d67423c5fd0c174b76 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:55:59 +0100 Subject: [PATCH 49/83] Remove OS Disk Size configuration Removed OS Disk Size slider configuration from portal-ui.json. --- deploy/portal-ui/portal-ui.json | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index b5e3add..a788fc3 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -303,21 +303,6 @@ ] } }, - { - "name": "DiskSizeGB", - "type": "Microsoft.Common.Slider", - "min": 64, - "max": 512, - "label": "OS Disk Size", - "subLabel": "GB", - "defaultValue": 128, - "showStepMarkers": false, - "toolTip": "Size of the OS disk in GB.", - "constraints": { - "required": true - }, - "visible": true - }, { "name": "optionMarketPlaceOrCustomImage", "type": "Microsoft.Common.OptionsGroup", From 75a9e18c9d2d69f416a89d57edcb56ea7b9d65ab Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:00:10 +0100 Subject: [PATCH 50/83] Add OS Disk Size parameter to portal-ui.json --- deploy/portal-ui/portal-ui.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index a788fc3..dc04d91 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -303,6 +303,21 @@ ] } }, + { + "name": "DiskSizeGB", + "type": "Microsoft.Common.Slider", + "min": 64, + "max": 512, + "label": "OS Disk Size", + "subLabel": "GB", + "defaultValue": 128, + "showStepMarkers": false, + "toolTip": "Size of the OS disk in GB.", + "constraints": { + "required": true + }, + "visible": true + }, { "name": "optionMarketPlaceOrCustomImage", "type": "Microsoft.Common.OptionsGroup", @@ -824,7 +839,7 @@ "DrainGracePeriodHours": "[steps('optionalParametersStep')._DrainGracePeriodHours]", "FixSessionHostTags": "[steps('optionalParametersStep')._FixSessionHostTags]", "IncludePreExistingSessionHosts": "[steps('basics').HostPoolSettingsSection.IncludePreExistingSessionHosts]", - "DeploymentPrefix": "[steps('optionalParametersStep')._SHRDeploymentPrefix]", + "SHRDeploymentPrefix": "[steps('optionalParametersStep')._SHRDeploymentPrefix]", "SessionHostInstanceNumberPadding": "[steps('optionalParametersStep')._SessionHostInstanceNumberPadding]", "SessionHostNameSeparator": "[if(steps('optionalParametersStep')._SessionHostNameSeparator,'-','')]", "ReplaceSessionHostOnNewImageVersion": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersion]", From 772302370afbeffbb6965b12b0eb0f385d8ad953 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:37:03 +0100 Subject: [PATCH 51/83] Update SessionHostParameters to include DiskSizeGB --- deploy/arm/DeployAVDSessionHostReplacer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 80c3c11..5fd9ff3 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -471,7 +471,7 @@ }, { "name": "_SessionHostParameters", - "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'DnsServers', parameters('DnsServers'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" + "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'DnsServers', parameters('DnsServers'), 'DiskSizeGB', parameters('DiskSizeGB'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" }, { "name": "_SubscriptionId", From f0bd2e25fda426841dfb890942f1db6872981f45 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:40:35 +0100 Subject: [PATCH 52/83] Add OS disk size parameter to AVD session host Added configuration for OS disk size in GB. --- deploy/arm/DeployAVDSessionHostReplacer.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 5fd9ff3..36c9d95 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -59,6 +59,13 @@ "StandardSSD_LRS", "Premium_LRS" ] + }, + "DiskSizeGB": { + "type": "int", + "defaultValue": 128, + "metadata": { + "description": "OS disk size in GB" + } }, "MarketPlaceOrCustomImage": { "type": "string", From 9b1cb72aa157f74a31878ec7a14b546a52c8ada2 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:48:04 +0100 Subject: [PATCH 53/83] Remove DNS settings from portal-ui.json Removed DNS settings section from portal UI configuration. --- deploy/portal-ui/portal-ui.json | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index dc04d91..3f20baf 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -501,36 +501,6 @@ ] } }, - { - "name": "DnsSettings", - "type": "Microsoft.Common.Section", - "label": "DNS settings", - "visible": true, - "elements": [ - { - "name": "DnsServers", - "type": "Microsoft.Common.Array", - "label": "DNS servers", - "toolTip": "Add up to two IPv4 DNS servers.", - "constraints": { - "required": false, - "minLength": 0, - "maxLength": 2 - }, - "defaultValue": [], - "element": { - "type": "Microsoft.Common.TextBox", - "label": "IPv4 address", - "placeholder": "e.g., 10.0.0.4", - "constraints": { - "required": true, - "regex": "^((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$", - "validationMessage": "Enter a valid IPv4 address (0–255 in each octet)." - } - } - } - ] - }, { "name": "DomainJoinSection", "type": "Microsoft.Common.Section", From cfd46c0f5c8871802e845f6b01c1014148fb8de2 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 19:55:50 +0100 Subject: [PATCH 54/83] Add DNS settings section to portal UI configuration --- deploy/portal-ui/portal-ui.json | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index 3f20baf..b8a99d1 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -501,6 +501,35 @@ ] } }, + { + "name": "DnsSettings", + "type": "Microsoft.Common.Section", + "label": "DNS settings", + "elements": [ + { + "name": "DnsServer1", + "type": "Microsoft.Common.TextBox", + "label": "Primary DNS (IPv4)", + "placeholder": "e.g., 10.0.0.4", + "constraints": { + "required": true, + "regex": "^((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$", + "validationMessage": "Enter a valid IPv4 address (0-255 in each octet)." + } + }, + { + "name": "DnsServer2", + "type": "Microsoft.Common.TextBox", + "label": "Secondary DNS (IPv4)", + "placeholder": "e.g., 10.0.0.5", + "constraints": { + "required": true, + "regex": "^((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$", + "validationMessage": "Enter a valid IPv4 address (0-255 in each octet)." + } + } + ] + }, { "name": "DomainJoinSection", "type": "Microsoft.Common.Section", @@ -816,7 +845,7 @@ "ReplaceSessionHostOnNewImageVersionDelayDays": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersionDelayDays]", "VMNamesTemplateParameterName": "VMNames", "SessionHostResourceGroupName": "[steps('optionalParametersStep')._SessionHostResourceGroupName]", - "DnsServers": "[steps('SessionHostsTemplate').DnsSettings.DnsServers]" + "DnsServers": "[createArray(steps('SessionHostsTemplate').DnsSettings.DnsServers1, steps('SessionHostsTemplate').DnsSettings.DnsServers2)]" } } } From 06c3bb4062dfeb1fd0c23cf412cae369408d8302 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:02:52 +0100 Subject: [PATCH 55/83] Add subscriptionId to portal-ui.json outputs --- deploy/portal-ui/portal-ui.json | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index b8a99d1..252248a 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -797,6 +797,7 @@ }, "outputs": { "kind": "ResourceGroup", + "subscriptionId": "[steps('basics').resourceScope.subscription.id]", "resourceGroupId": "[steps('basics').resourceScope.resourceGroup.id]", "location": "[steps('basics').resourceScope.location.name]", "parameters": { From 5bc32612308df73952e14f29378449ed2a16aca1 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:12:34 +0100 Subject: [PATCH 56/83] Fix DnsServers array creation in portal-ui.json --- deploy/portal-ui/portal-ui.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index 252248a..bcadb1e 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -797,7 +797,6 @@ }, "outputs": { "kind": "ResourceGroup", - "subscriptionId": "[steps('basics').resourceScope.subscription.id]", "resourceGroupId": "[steps('basics').resourceScope.resourceGroup.id]", "location": "[steps('basics').resourceScope.location.name]", "parameters": { @@ -846,7 +845,7 @@ "ReplaceSessionHostOnNewImageVersionDelayDays": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersionDelayDays]", "VMNamesTemplateParameterName": "VMNames", "SessionHostResourceGroupName": "[steps('optionalParametersStep')._SessionHostResourceGroupName]", - "DnsServers": "[createArray(steps('SessionHostsTemplate').DnsSettings.DnsServers1, steps('SessionHostsTemplate').DnsSettings.DnsServers2)]" + "DnsServers": "[createArray(steps('SessionHostsTemplate').DnsSettings.DnsServer1, steps('SessionHostsTemplate').DnsSettings.DnsServer2)]" } } } From 9ddae1157517a7fea8df05312b184f9488560b06 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:17:43 +0100 Subject: [PATCH 57/83] Update portal-ui.json with subscription and resource group details --- deploy/portal-ui/portal-ui.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index bcadb1e..6fdd5d3 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -797,7 +797,8 @@ }, "outputs": { "kind": "ResourceGroup", - "resourceGroupId": "[steps('basics').resourceScope.resourceGroup.id]", + "subscriptionId": "[steps('basics').resourceScope.subscription.id]", + "resourceGroupName": "[steps('basics').resourceScope.resourceGroup.name]", "location": "[steps('basics').resourceScope.location.name]", "parameters": { "HostPoolResourceGroupName": "[steps('basics').HostPoolSettingsSection.HostPoolSelector.resourceGroup]", From 738bb1da0206715ac7a1b0882bf0deadce9f7fd0 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:24:15 +0100 Subject: [PATCH 58/83] Update resource outputs in portal-ui.json --- deploy/portal-ui/portal-ui.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index 6fdd5d3..4ac83da 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -797,8 +797,7 @@ }, "outputs": { "kind": "ResourceGroup", - "subscriptionId": "[steps('basics').resourceScope.subscription.id]", - "resourceGroupName": "[steps('basics').resourceScope.resourceGroup.name]", + "resourceGroupId": "[steps('basics').resourceScope.resourceGroup.id]", "location": "[steps('basics').resourceScope.location.name]", "parameters": { "HostPoolResourceGroupName": "[steps('basics').HostPoolSettingsSection.HostPoolSelector.resourceGroup]", @@ -839,7 +838,7 @@ "DrainGracePeriodHours": "[steps('optionalParametersStep')._DrainGracePeriodHours]", "FixSessionHostTags": "[steps('optionalParametersStep')._FixSessionHostTags]", "IncludePreExistingSessionHosts": "[steps('basics').HostPoolSettingsSection.IncludePreExistingSessionHosts]", - "SHRDeploymentPrefix": "[steps('optionalParametersStep')._SHRDeploymentPrefix]", + "DeploymentPrefix": "[steps('optionalParametersStep')._SHRDeploymentPrefix]", "SessionHostInstanceNumberPadding": "[steps('optionalParametersStep')._SessionHostInstanceNumberPadding]", "SessionHostNameSeparator": "[if(steps('optionalParametersStep')._SessionHostNameSeparator,'-','')]", "ReplaceSessionHostOnNewImageVersion": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersion]", From cf8995815534fd9cc5cd760736ad1fb3825a3dba Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:27:55 +0100 Subject: [PATCH 59/83] Update print statement from 'Hello' to 'Goodbye' --- deploy/portal-ui/portal-ui.json | 1652 +++++++++++++++---------------- 1 file changed, 804 insertions(+), 848 deletions(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index 4ac83da..4f4c72b 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -1,852 +1,808 @@ { - "$schema": "https://schema.management.azure.com/schemas/2021-09-09/uiFormDefinition.schema.json", - "view": { - "kind": "Form", - "properties": { - "title": "Azure Virtual Desktop - Session Host Replacer Deployment", - "steps": [ - { - "name": "basics", - "label": "Basics", - "elements": [ - { - "name": "resourceScope", - "type": "Microsoft.Common.ResourceScope", - "location": { - "resourceTypes": ["Microsoft.DesktopVirtualization/HostPools" - ] - } - }, - { - "name": "HostPoolSettingsSection", - "type": "Microsoft.Common.Section", - "label": "Host Pool Settings", - "visible": true, - "elements": [ - { - "name": "HostPoolSelector", - "type": "Microsoft.Solutions.ResourceSelector", - "label": "Select target Host Pool", - "resourceType": "Microsoft.DesktopVirtualization/HostPools", - "constraints": { - "required": true - }, - "options": { - "filter": { - "subscription": "onBasics" - } - } - }, - { - "name": "TargetSessionHostCount", - "type": "Microsoft.Common.Slider", - "min": 1, - "max": 5000, - "label": "Target Number of Session Hosts", - "subLabel": "VMs", - "defaultValue": 10, - "showStepMarkers": false, - "toolTip": "The target number of session hosts in the host pool.", - "constraints": { - "required": true - }, - "visible": true - }, - { - "name": "TargetSessionHostBuffer", - "type": "Microsoft.Common.Slider", - "min": 1, - "max": "[steps('basics').HostPoolSettingsSection.TargetSessionHostCount]", - "label": "Session Hosts Buffer", - "subLabel": "VMs", - "defaultValue": 5, - "showStepMarkers": false, - "toolTip": "

The maximum number of session hosts to add during a replacement process.

Example:

Target is 10, buffer is 2, and we need to replace all VMs

Then SHR will add 2 new VMs, delete 2 old ones, and repeat until all hosts are replaced. This is useful to avoid exhausting subnets, capacity limits, and reduce costs.

", - "constraints": { - "required": true - }, - "visible": true - }, - { - "name": "IncludePreExistingSessionHosts", - "type": "Microsoft.Common.CheckBox", - "label": "Include pre-existing session hosts", - "toolTip": "
When enabled, the Session Host Replacer will automatically consider pre-existing VMs for replacement if they meet the criteria by setting the IncludeInAutomation tag to True during the first run.
 
When disabled, the session hosts are not counted as part of the target number of VMs.
 
You can manually include a VM after deployment by updating its tag.
", - "constraints": { - "required": false - } - }, - { - "name": "sessionHostNamePrefix", - "type": "Microsoft.Common.TextBox", - "label": "Session Host Name Prefix", - "toolTip": "

The prefix for the session host names.

Make sure this produces a unique value across all device names in your environment.

Try selecting a prefix that helps you identify the host pool such as AVDHP01

", - "constraints": { - "required": true, - "validationMessage": "Must be a valid name less than 12 characters long to allow for the 3 character suffix (eg. prefix-01)." - } - }, - { - "name": "SessionHostExampleNameInfoBox", - "type": "Microsoft.Common.InfoBox", - "visible": "[not(empty(steps('basics').HostPoolSettingsSection.sessionHostNamePrefix))]", - "options": { - "icon": "Info", - "text": "[concat('Example Session Host name: ', steps('basics').HostPoolSettingsSection.sessionHostNamePrefix , if(steps('optionalParametersStep')._SessionHostNameSeparator,'-','') , take('00000000', sub(steps('optionalParametersStep')._SessionHostInstanceNumberPadding,1)) ,'1
You can customize the separator and padding from Optional Parameters.') ]" - } - } - ] - }, - { - "name": "IdentitySection", - "type": "Microsoft.Common.Section", - "label": "Identity", - "visible": true, - "elements": [ - { - "name": "UseUserAssignedManagedIdentity", - "type": "Microsoft.Common.CheckBox", - "label": "Use User Assigned Managed Identity", - "toolTip": "[Recommended] When enabled, The Session Host Replacer will use the selected Identity to take actions. Otherwise System Identity (MSI) is used.", - "defaultValue": true, - "constraints": { - "required": false - } - }, - { - "name": "UserAssignedManagedIdentitySelector", - "type": "Microsoft.Solutions.ResourceSelector", - "visible": "[steps('basics').IdentitySection.UseUserAssignedManagedIdentity]", - "label": "Select User Assigned Managed Identity", - "resourceType": "Microsoft.ManagedIdentity/userAssignedIdentities", - "constraints": { - "required": true - }, - "options": { - "filter": {} - } - }, - { - "name": "UserAssignedManagedIdentityInfoBox", - "type": "Microsoft.Common.InfoBox", - "visible": "[steps('basics').IdentitySection.UseUserAssignedManagedIdentity]", - "options": { - "icon": "Info", - "text": "When using a User Assigned Managed Identity, make sure the identity has the needed permissions in Azure and Entra. Follow the link for more info.", - "uri": "https://github.com/Azure/AVDReplacementPlans/blob/v0.3.0/docs/Permissions.md" - } - } - ] - }, - { - "name": "MonitoringSection", - "type": "Microsoft.Common.Section", - "label": "Monitoring", - "visible": true, - "elements": [ - { - "name": "EnableMonitoring", - "type": "Microsoft.Common.CheckBox", - "label": "Enable Monitoring", - "toolTip": "[Recommended] When enabled, the session host replacer will use App Insights and Log Analytics to collect metrics and logs.", - "defaultValue": true, - "constraints": { - "required": false - } - }, - { - "name": "UseExistingLAW", - "visible": "[steps('basics').MonitoringSection.EnableMonitoring]", - "type": "Microsoft.Common.CheckBox", - "label": "Select existing Log Analytics Workspace", - "toolTip": "When enabled, the session host replacer will use the selected Log Analytics Workspace. If disabled, the session host replacer will create a new Log Analytics Workspace.", - "defaultValue": false, - "constraints": { - "required": false - } - }, - { - "name": "LAWSelector", - "type": "Microsoft.Solutions.ResourceSelector", - "visible": "[and(steps('basics').MonitoringSection.EnableMonitoring, steps('basics').MonitoringSection.UseExistingLAW)]", - "label": "Log Analytics Workspace", - "resourceType": "Microsoft.OperationalInsights/workspaces", - "constraints": { - "required": true - }, - "options": { - "filter": {} - } - } - ] - }, - { - "name": "computeApi", - "type": "Microsoft.Solutions.ArmApiControl", - "request": { - "method": "GET", - "path": "[concat(steps('basics').resourceScope.subscription.id,'/providers/Microsoft.Compute/resourceTypes?api-version=2022-01-01')]" - } - }, - { - "name": "VersionInfo", - "type": "Microsoft.Common.TextBlock", - "visible": true, - "options": { +"$schema": "https://schema.management.azure.com/schemas/2021-09-09/uiFormDefinition.schema.json", +"view": { +"kind": "Form", +"properties": { +"title": "Azure Virtual Desktop - Session Host Replacer Deployment", +"steps": [ +{ +"name": "basics", +"label": "Basics", +"elements": [ +{ +"name": "resourceScope", +"type": "Microsoft.Common.ResourceScope", +"location": { +"resourceTypes": ["Microsoft.DesktopVirtualization/HostPools" +] +} +}, +{ +"name": "HostPoolSettingsSection", +"type": "Microsoft.Common.Section", +"label": "Host Pool Settings", +"visible": true, +"elements": [ +{ +"name": "HostPoolSelector", +"type": "Microsoft.Solutions.ResourceSelector", +"label": "Select target Host Pool", +"resourceType": "Microsoft.DesktopVirtualization/HostPools", +"constraints": { +"required": true +}, +"options": { +"filter": { +"subscription": "onBasics" +} +} +}, +{ +"name": "TargetSessionHostCount", +"type": "Microsoft.Common.Slider", +"min": 1, +"max": 5000, +"label": "Target Number of Session Hosts", +"subLabel": "VMs", +"defaultValue": 10, +"showStepMarkers": false, +"toolTip": "The target number of session hosts in the host pool.", +"constraints": { +"required": true +}, +"visible": true +}, +{ +"name": "TargetSessionHostBuffer", +"type": "Microsoft.Common.Slider", +"min": 1, +"max": "[steps('basics').HostPoolSettingsSection.TargetSessionHostCount]", +"label": "Session Hosts Buffer", +"subLabel": "VMs", +"defaultValue": 5, +"showStepMarkers": false, +"toolTip": "

The maximum number of session hosts to add during a replacement process.

Example:

Target is 10, buffer is 2, and we need to replace all VMs

Then SHR will add 2 new VMs, delete 2 old ones, and repeat until all hosts are replaced. This is useful to avoid exhausting subnets, capacity limits, and reduce costs.

", +"constraints": { +"required": true +}, +"visible": true +}, +{ +"name": "IncludePreExistingSessionHosts", +"type": "Microsoft.Common.CheckBox", +"label": "Include pre-existing session hosts", +"toolTip": "
When enabled, the Session Host Replacer will automatically consider pre-existing VMs for replacement if they meet the criteria by setting the IncludeInAutomation tag to True during the first run.
 
When disabled, the session hosts are not counted as part of the target number of VMs.
 
You can manually include a VM after deployment by updating its tag.
", +"constraints": { +"required": false +} +}, +{ +"name": "sessionHostNamePrefix", +"type": "Microsoft.Common.TextBox", +"label": "Session Host Name Prefix", +"toolTip": "

The prefix for the session host names.

Make sure this produces a unique value across all device names in your environment.

Try selecting a prefix that helps you identify the host pool such as AVDHP01

", +"constraints": { +"required": true, +"validationMessage": "Must be a valid name less than 12 characters long to allow for the 3 character suffix (eg. prefix-01)." +} +}, +{ +"name": "SessionHostExampleNameInfoBox", +"type": "Microsoft.Common.InfoBox", +"visible": "[not(empty(steps('basics').HostPoolSettingsSection.sessionHostNamePrefix))]", +"options": { +"icon": "Info", +"text": "[concat('Example Session Host name: ', steps('basics').HostPoolSettingsSection.sessionHostNamePrefix , if(steps('optionalParametersStep')._SessionHostNameSeparator,'-','') , take('00000000', sub(steps('optionalParametersStep')._SessionHostInstanceNumberPadding,1)) ,'1
You can customize the separator and padding from Optional Parameters.') ]" +} +} +] +}, +{ +"name": "IdentitySection", +"type": "Microsoft.Common.Section", +"label": "Identity", +"visible": true, +"elements": [ +{ +"name": "UseUserAssignedManagedIdentity", +"type": "Microsoft.Common.CheckBox", +"label": "Use User Assigned Managed Identity", +"toolTip": "[Recommended] When enabled, The Session Host Replacer will use the selected Identity to take actions. Otherwise System Identity (MSI) is used.", +"defaultValue": true, +"constraints": { +"required": false +} +}, +{ +"name": "UserAssignedManagedIdentitySelector", +"type": "Microsoft.Solutions.ResourceSelector", +"visible": "[steps('basics').IdentitySection.UseUserAssignedManagedIdentity]", +"label": "Select User Assigned Managed Identity", +"resourceType": "Microsoft.ManagedIdentity/userAssignedIdentities", +"constraints": { +"required": true +}, +"options": { +"filter": {} +} +}, +{ +"name": "UserAssignedManagedIdentityInfoBox", +"type": "Microsoft.Common.InfoBox", +"visible": "[steps('basics').IdentitySection.UseUserAssignedManagedIdentity]", +"options": { +"icon": "Info", +"text": "When using a User Assigned Managed Identity, make sure the identity has the needed permissions in Azure and Entra. Follow the link for more info.", +"uri": "https://github.com/Azure/AVDReplacementPlans/blob/v0.3.0/docs/Permissions.md" +} +} +] +}, +{ +"name": "MonitoringSection", +"type": "Microsoft.Common.Section", +"label": "Monitoring", +"visible": true, +"elements": [ +{ +"name": "EnableMonitoring", +"type": "Microsoft.Common.CheckBox", +"label": "Enable Monitoring", +"toolTip": "[Recommended] When enabled, the session host replacer will use App Insights and Log Analytics to collect metrics and logs.", +"defaultValue": true, +"constraints": { +"required": false +} +}, +{ +"name": "UseExistingLAW", +"visible": "[steps('basics').MonitoringSection.EnableMonitoring]", +"type": "Microsoft.Common.CheckBox", +"label": "Select existing Log Analytics Workspace", +"toolTip": "When enabled, the session host replacer will use the selected Log Analytics Workspace. If disabled, the session host replacer will create a new Log Analytics Workspace.", +"defaultValue": false, +"constraints": { +"required": false +} +}, +{ +"name": "LAWSelector", +"type": "Microsoft.Solutions.ResourceSelector", +"visible": "[and(steps('basics').MonitoringSection.EnableMonitoring, steps('basics').MonitoringSection.UseExistingLAW)]", +"label": "Log Analytics Workspace", +"resourceType": "Microsoft.OperationalInsights/workspaces", +"constraints": { +"required": true +}, +"options": { +"filter": {} +} +} +] +}, +{ +"name": "computeApi", +"type": "Microsoft.Solutions.ArmApiControl", +"request": { +"method": "GET", +"path": "[concat(steps('basics').resourceScope.subscription.id,'/providers/Microsoft.Compute/resourceTypes?api-version=2022-01-01')]" +} +}, +{ +"name": "VersionInfo", +"type": "Microsoft.Common.TextBlock", +"visible": true, +"options": { + "text": "AVD session host replacer Portal UI Version: v0.3.0", "text": "AVD session host replacer Portal UI Version: v0.3.2betasz", - "link": { - "label": "GitHub Repository", +"link": { +"label": "GitHub Repository", + "uri": "https://github.com/Azure/AVDSessionHostReplacer" "uri": "https://github.com/stefze/AVDSessionHostReplacer" - } - } - } - ] - }, - { - "name": "SessionHostsTemplate", - "label": "Session Hosts Template", - "elements": [ - { - "name": "SessionHostsRegion", - "type": "Microsoft.Common.DropDown", - "label": "Session Hosts Region", - "visible": true, - "filter": true, - "multiselect": false, - "selectAll": false, - "toolTip": "Select region to deploy session hosts.", - "constraints": { - "required": true, - "allowedValues": "[map( first( map( filter( steps('basics').computeApi.value, (resourceTypes) => equals(resourceTypes.resourceType, 'virtualMachines') ), (item) => item.locations) ), (item) => parse(concat('{\"label\":\"', item, '\",\"value\":\"', toLower(replace(item, ' ', '')), '\"}')) )]" - } - }, - { - "name": "AvailabilityZones", - "type": "Microsoft.Common.DropDown", - "label": "Availability Zones", - "visible": true, - "filter": false, - "defaultValue": [], - "multiselect": true, - "selectAll": false, - "toolTip": "Select Availability Zones for the session hosts. Make sure the selected size is available in the selected zones. Session hosts will be deployed across the zones.", - "constraints": { - "required": false, - "allowedValues": [ - { - "label": "Zone 1", - "value": "1" - }, - { - "label": "Zone 2", - "value": "2" - }, - { - "label": "Zone 3", - "value": "3" - } - ] - } - }, - { - "name": "SessionHostSize", - "type": "Microsoft.Compute.SizeSelector", - "label": "VM Size", - "toolTip": "", - "recommendedSizes": [ - "Standard_D4ads_v5" - ], - "constraints": { - "allowedSizes": [], - "excludedSizes": [], - "required": true - }, - "options": { - "hideDiskTypeFilter": true - }, - "osPlatform": "Windows", - "imageReference": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "Windows-11", - "sku": "win11-23h2-avd" - } - }, - { - "name": "AcceleratedNetworking", - "type": "Microsoft.Common.CheckBox", - "label": "Enable accelerated networking", - "defaultValue": true, - "toolTip": "Enables low latency and high throughput on the network interface." - }, - { - "name": "SessionHostDiskType", - "type": "Microsoft.Common.DropDown", - "label": "OS Disk type", - "filter": false, - "defaultValue": "Premium SSD", - "toolTip": "Select session host disk type to host the OS.", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "Standard HDD", - "value": "Standard_LRS" - }, - { - "label": "Standard SSD", - "value": "StandardSSD_LRS" - }, - { - "label": "Premium SSD", - "value": "Premium_LRS" - } - ] - } - }, - { - "name": "DiskSizeGB", - "type": "Microsoft.Common.Slider", - "min": 64, - "max": 512, - "label": "OS Disk Size", - "subLabel": "GB", - "defaultValue": 128, - "showStepMarkers": false, - "toolTip": "Size of the OS disk in GB.", - "constraints": { - "required": true - }, - "visible": true - }, - { - "name": "optionMarketPlaceOrCustomImage", - "type": "Microsoft.Common.OptionsGroup", - "label": "Image Source", - "defaultValue": "Marketplace", - "toolTip": "", - "constraints": { - "allowedValues": [ - { - "label": "Marketplace", - "value": "Marketplace" - }, - { - "label": "Gallery Image", - "value": "Gallery" - } - ], - "required": true - }, - "visible": true - }, - { - "name": "dropDownMarketPlaceImage", - "type": "Microsoft.Common.DropDown", - "label": "Select a Marketplace Image", - "placeholder": "", - "defaultValue": "Windows 11 Enterprise 23h2 multi-session + Microsoft 365 Apps", - "toolTip": "Marketplace images are updated on monthly basis. The Session Host Replacer will replace the session hosts when a new image is available.", - "multiselect": false, - "selectAll": false, - "filter": true, - "filterPlaceholder": "Filter items ...", - "multiLine": true, - "constraints": { - "allowedValues": [ - { - "label": "Windows 10 Enterprise 21h2 multi-session", - "value": "win10-21h2-avd" - }, - { - "label": "Windows 10 Enterprise 21h2 multi-session (Gen 2)", - "value": "win10-21h2-avd-g2" - }, - { - "label": "Windows 10 Enterprise 21h2 multi-session + Microsoft 365 Apps", - "value": "win10-21h2-avd-m365" - }, - { - "label": "Windows 10 Enterprise 21h2 multi-session + Microsoft 365 Apps (Gen 2)", - "value": "win10-21h2-avd-m365-g2" - }, - { - "label": "Windows 10 Enterprise 22h2 multi-session", - "value": "win10-22h2-avd" - }, - { - "label": "Windows 10 Enterprise 22h2 multi-session (Gen 2)", - "value": "win10-22h2-avd-g2" - }, - { - "label": "Windows 10 Enterprise 22h2 multi-session + Microsoft 365 Apps", - "value": "win10-22h2-avd-m365" - }, - { - "label": "Windows 10 Enterprise 22h2 multi-session + Microsoft 365 Apps (Gen 2)", - "value": "win10-22h2-avd-m365-g2" - }, - { - "label": "Windows 11 Enterprise 21h2 multi-session", - "value": "win11-21h2-avd" - }, - { - "label": "Windows 11 Enterprise 21h2 multi-session + Microsoft 365 Apps", - "value": "win11-21h2-avd-m365" - }, - { - "label": "Windows 11 Enterprise 22h2 multi-session", - "value": "win11-22h2-avd" - }, - { - "label": "Windows 11 Enterprise 22h2 multi-session + Microsoft 365 Apps", - "value": "win11-22h2-avd-m365" - }, - { - "label": "Windows 11 Enterprise 23h2 multi-session", - "value": "win11-23h2-avd" - }, - { - "label": "Windows 11 Enterprise 23h2 multi-session + Microsoft 365 Apps", - "value": "win11-23h2-avd-m365" - } - ], - "required": true - }, - "visible": "[equals(steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage,'Marketplace')]" - }, - { - "name": "GalleryImageInfoBox", - "type": "Microsoft.Common.InfoBox", - "visible": "[equals(steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage,'Gallery')]", - "options": { - "icon": "Warning", - "text": "The system identity of the Session Host Replacer function is assigned the 'Desktop Virtualization Virtual Machine Contributor' role against the subscription. If the Image Definition is in a different subscription, please make sure you manually assign the permission post deployment." - } - }, - { - "name": "resourceSelectorSessionHostGalleryImageId", - "type": "Microsoft.Solutions.ResourceSelector", - "label": "Select Gallery Image", - "resourceType": "Microsoft.Compute/galleries/images", - "constraints": { - "required": true - }, - "options": { - "filter": {} - }, - "visible": "[equals(steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage,'Gallery')]" - }, - { - "name": "sessionHostsSecuritySection", - "type": "Microsoft.Common.Section", - "visible": true, - "label": "Security profile", - "elements": [ - { - "name": "SecurityType", - "type": "Microsoft.Common.DropDown", - "label": "Security type", - "filter": true, - "defaultValue": "Trusted Launch Virtual Machines", - "toolTip": "Choose a type of security that matches your needs: Trusted launch virtual machines provide additional security features on Gen2 virtual machines to protect against persistent and advanced attacks.", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "Standard", - "value": "Standard" - }, - { - "label": "Trusted Launch Virtual Machines", - "value": "TrustedLaunch" - }, - { - "label": "Confidential Virtual Machines", - "value": "ConfidentialVM" - } - ] - } - }, - { - "name": "SecureBootEnabled", - "type": "Microsoft.Common.CheckBox", - "visible": "[or(equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'TrustedLaunch'), equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'ConfidentialVM'))]", - "label": "Enable secure boot", - "defaultValue": true, - "toolTip": "Secure boot helps protect your VMs against boot kits, rootkits, and kernel-level malware." - }, - { - "name": "TpmEnabled", - "type": "Microsoft.Common.CheckBox", - "visible": "[or(equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'TrustedLaunch'), equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'ConfidentialVM'))]", - "label": "Enable vTPM", - "defaultValue": true, - "toolTip": "Virtual Trusted Platform Module (vTPM) is TPM2.0 compliant and validates your VM boot integrity apart from securely storing keys and secrets." - } - ] - }, - { - "name": "SubnetId", - "type": "Microsoft.Common.TextBox", - "label": "Subnet ID", - "visible": true, - "placeholder": "Add resource id of subnet in the same region as the Session Hosts", - "constraints": { - "required": true, - "validations": [ - { - "regex": ".*/subnets/[A-Za-z0-9_\\.-]+$", - "message": "Invalid Subnet Resource ID. Make sure it ends with /subnets/SubnetName" - } - ] - } - }, - { - "name": "DnsSettings", - "type": "Microsoft.Common.Section", - "label": "DNS settings", - "elements": [ - { - "name": "DnsServer1", - "type": "Microsoft.Common.TextBox", - "label": "Primary DNS (IPv4)", - "placeholder": "e.g., 10.0.0.4", - "constraints": { - "required": true, - "regex": "^((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$", - "validationMessage": "Enter a valid IPv4 address (0-255 in each octet)." - } - }, - { - "name": "DnsServer2", - "type": "Microsoft.Common.TextBox", - "label": "Secondary DNS (IPv4)", - "placeholder": "e.g., 10.0.0.5", - "constraints": { - "required": true, - "regex": "^((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$", - "validationMessage": "Enter a valid IPv4 address (0-255 in each octet)." - } - } - ] - }, - { - "name": "DomainJoinSection", - "type": "Microsoft.Common.Section", - "visible": true, - "label": "Domain Join", - "elements": [ - { - "name": "IdentityServiceProvider", - "type": "Microsoft.Common.OptionsGroup", - "visible": true, - "label": "Identity service provider", - "defaultValue": "Microsoft Entra ID", - "toolTip": "Identity service provider (Active Directory or EntraDS) that already exist and will be used for Azure Virtual Desktop.", - "constraints": { - "required": true, - "allowedValues": [ - { - "label": "Microsoft Entra ID", - "value": "EntraID" - }, - { - "label": "Active Directory (AD DS)", - "value": "ActiveDirectory" - }, - { - "label": "Microsoft Entra Domain Services", - "value": "EntraDS" - } - ] - } - }, - { - "name": "EntraJoinedInfoBox", - "type": "Microsoft.Common.InfoBox", - "visible": "[equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID')]", - "options": { - "icon": "Warning", - "text": "When the VMs are Entra Joined, session host replacer will attempt to delete its device object from Entra ID. This requires additional permissions to be granted to the service principal used by session host replacer. Please refer to the documentation for more information.", - "uri": "https://github.com/Azure/AVDSessionHostReplacer/blob/main/docs/Permissions.md" - } - }, - { - "name": "IntuneEnrollment", - "type": "Microsoft.Common.CheckBox", - "visible": "[equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID')]", - "label": "Intune enrollment", - "defaultValue": false, - "toolTip": "If Intune is configured in your Microsoft Entra ID tenant, you can choose to have the VM automatically enrolled during the deployment by selecting this box. Session Host Replacer will delete the device from Intune during replacement." - }, - { - "name": "ADDomainName", - "type": "Microsoft.Common.TextBox", - "label": "AD Domain name", - "visible": "[or(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'ActiveDirectory'), equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraDS'))]", - "placeholder": "Example: contoso.com", - "constraints": { - "required": true - } - }, - { - "name": "ADDomainJoinUserName", - "type": "Microsoft.Common.TextBox", - "label": "User principal name", - "visible": "[not(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID'))]", - "toolTip": "Provide username with permissions to join session host to the domain.", - "placeholder": "Example: avdadmin@contoso.com", - "defaultValue": "", - "constraints": { - "required": true - } - }, - { - "name": "ADJoinUserPassword", - "type": "Microsoft.Common.PasswordBox", - "label": { - "password": "Password" - }, - "visible": "[not(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID'))]", - "toolTip": "Provide password for domain join account. This will be stored in a new Azure Key Vault.", - "constraints": { - "required": true - }, - "options": { - "hideConfirmation": true - } - }, - { - "name": "ADOUPath", - "type": "Microsoft.Common.TextBox", - "visible": "[not(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID'))]", - "label": "Custom OU path (Optional)", - "toolTip": "Provide OU where to locate session hosts, if not provided session hosts will be placed on the default (computers) OU.", - "placeholder": "Example: OU=session-hosts,OU=avd,DC=contoso,DC=com", - "constraints": {} - } - ] - }, - { - "name": "LocalAdminUsername", - "type": "Microsoft.Common.TextBox", - "label": "Local Administrator Username", - "toolTip": "Provide username for session host local admin account. Administrator can't be used as username, it is reserved by the system. The password is randomly generated at deployment time.", - "placeholder": "Example: avdadmin", - "defaultValue": "", - "constraints": { - "regex": "^(?!.*[aA]dministrator).*$", - "validationMessage": "This username can't be used, it is a reserved word.", - "required": true - } - } - ] - }, - { - "name": "optionalParametersStep", - "label": "Optional Parameters", - "elements": [ - { - "name": "_Tag_IncludeInAutomation", - "type": "Microsoft.Common.TextBox", - "label": "Include in Automation Tag Name", - "toolTip": "The name of the tag to use to determine if an existing session host should be included in the automation. After deployment, if the tag is present and set to 'true', the session host will be included. If the tag is not present or set to 'false', the session host will be excluded.", - "defaultValue": "IncludeInAutoReplace", - "constraints": { - "required": false - } - }, - { - "name": "_Tag_DeployTimestamp", - "type": "Microsoft.Common.TextBox", - "label": "Deploy Timestamp Tag Name", - "toolTip": "The name of the tag to use to determine when the session host was deployed. This is updated by the session host replacer function on new session hosts. After deployment, you can edit the value of this tag to force replace a VM.", - "defaultValue": "AutoReplaceDeployTimestamp", - "constraints": { - "required": false - } - }, - { - "name": "_Tag_PendingDrainTimestamp", - "type": "Microsoft.Common.TextBox", - "label": "Pending Drain Timestamp Tag Name", - "toolTip": "The name of the tag to use to determine when the session host was marked for drain. This is updated by the session host replacer function on hosts pending deletion.", - "defaultValue": "AutoReplacePendingDrainTimestamp", - "constraints": { - "required": false - } - }, - { - "name": "_Tag_ScalingPlanExclusionTag", - "type": "Microsoft.Common.TextBox", - "label": "Scaling Plan Exclusion Tag Name", - "toolTip": "The name of the tag session host replacer will set to exclude a session host from scaling plans actions.", - "defaultValue": "ScalingPlanExclusion", - "constraints": { - "required": false - } - }, - { - "name": "_TargetVMAgeDays", - "type": "Microsoft.Common.TextBox", - "label": "Target VM Age (Days)", - "toolTip": "The maximum age of a VM in days before it is replaced. This is compared to the value of the Deploy Timestamp Tag.", - "defaultValue": 45, - "constraints": { - "required": false - } - }, - { - "name": "_DrainGracePeriodHours", - "type": "Microsoft.Common.TextBox", - "label": "Drain Grace Period (Hours)", - "toolTip": "The number of hours to wait after marking a VM for drain before deleting it. This is to allow users to finish their sessions before the VM is deleted.", - "defaultValue": 24, - "constraints": { - "required": false - } - }, - { - "name": "_FixSessionHostTags", - "type": "Microsoft.Common.CheckBox", - "label": "Fix Existing Session Host Tags", - "toolTip": "If enabled, the session host replacer will fix the tags on existing session hosts or if tags are mistakenly deleted. The tag values will NOT allow deletion of existing session hosts and must be changed post deployment. This is useful if you are deploying a new session host replacer to an existing host pool.", - "defaultValue": true, - "constraints": { - "required": "[steps('basics').HostPoolSettingsSection.IncludePreExistingSessionHosts]", - "validationMessage": "This is required if Include pre-existing session hosts is selected." - } - }, - { - "name": "_SHRDeploymentPrefix", - "type": "Microsoft.Common.TextBox", - "label": "Deployment Prefix", - "toolTip": "The prefix of the deployment created in the session hosts resource group when replacement VMs are deploying. This is used to track running and failed deployments.", - "defaultValue": "AVDSessionHostReplacer", - "constraints": { - "required": false - } - }, - { - "name": "_SessionHostInstanceNumberPadding", - "type": "Microsoft.Common.Slider", - "min": 1, - "max": 4, - "label": "Session Host VM Number Padding", - "defaultValue": 2, - "showStepMarkers": true, - "constraints": { - "required": false - }, - "visible": true - }, - { - "name": "_SessionHostNameSeparator", - "type": "Microsoft.Common.CheckBox", - "label": "Use '-' as separator", - "toolTip": "If enabled, the session host replacer will use '-' as a separator between the prefix and the instance number. If disabled, the session host replacer will not use separator.", - "defaultValue": true, - "constraints": { - "required": false - } - }, - { - "name": "SessionHostExampleNameInfoBox", - "type": "Microsoft.Common.InfoBox", - "visible": "[not(empty(steps('basics').HostPoolSettingsSection.sessionHostNamePrefix))]", - "options": { - "icon": "Info", - "text": "[concat('Example Session Host name: ', steps('basics').HostPoolSettingsSection.sessionHostNamePrefix , if(steps('optionalParametersStep')._SessionHostNameSeparator,'-','') , take('00000000', sub(steps('optionalParametersStep')._SessionHostInstanceNumberPadding,1)) ,'1') ]" - } - }, - { - "name": "_ReplaceSessionHostOnNewImageVersion", - "type": "Microsoft.Common.CheckBox", - "label": "Replace Session Hosts On New Image Version", - "toolTip": "(Recommended) If enabled, the session host replacer will replace session hosts when a new image version is available. This works for both marketplace and custom images. If disabled, the session host replacer will only replace session hosts when the VM age is greater than the target VM age.", - "defaultValue": true, - "constraints": { - "required": false - } - }, - { - "name": "_ReplaceSessionHostOnNewImageVersionDelayDays", - "type": "Microsoft.Common.TextBox", - "visible": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersion]", - "label": "Replace on New Image Version Delay (Days)", - "toolTip": "The number of days to wait after a new image is available before replacing session hosts. This is to allow time for the image to be tested before replacing session hosts.", - "defaultValue": "0", - "constraints": { - "required": false - } - }, - { - "name": "_SessionHostResourceGroupName", - "type": "Microsoft.Common.TextBox", - "label": "Session Hosts Resource Group Name", - "placeholder": "Same As Host Pool Resource Group", - "toolTip": "Leave this empty to deploy to same resource group as the host pool.", - "defaultValue": "", - "constraints": { - "required": false - } - } - ] - } - ] - }, - "outputs": { - "kind": "ResourceGroup", - "resourceGroupId": "[steps('basics').resourceScope.resourceGroup.id]", - "location": "[steps('basics').resourceScope.location.name]", - "parameters": { - "HostPoolResourceGroupName": "[steps('basics').HostPoolSettingsSection.HostPoolSelector.resourceGroup]", - "HostPoolName": "[steps('basics').HostPoolSettingsSection.HostPoolSelector.name]", - "SessionHostNamePrefix": "[steps('basics').HostPoolSettingsSection.sessionHostNamePrefix]", - "UseUserAssignedManagedIdentity": "[if(steps('basics').IdentitySection.UseUserAssignedManagedIdentity, true, false)]", - "UserAssignedManagedIdentityResourceId": "[if(steps('basics').IdentitySection.UseUserAssignedManagedIdentity, steps('basics').IdentitySection.UserAssignedManagedIdentitySelector.id, '')]", - "TargetSessionHostCount": "[steps('basics').HostPoolSettingsSection.TargetSessionHostCount]", - "TargetSessionHostBuffer": "[steps('basics').HostPoolSettingsSection.TargetSessionHostBuffer]", - "EnableMonitoring": "[steps('basics').MonitoringSection.EnableMonitoring]", - "UseExistingLAW": "[steps('basics').MonitoringSection.UseExistingLAW]", - "LogAnalyticsWorkspaceId": "[if(steps('basics').MonitoringSection.UseExistingLAW, steps('basics').MonitoringSection.LAWSelector.id, '')]", - "SessionHostsRegion": "[steps('SessionHostsTemplate').SessionHostsRegion]", - "AvailabilityZones": "[steps('SessionHostsTemplate').AvailabilityZones]", - "SessionHostSize": "[steps('SessionHostsTemplate').SessionHostSize]", - "AcceleratedNetworking": "[steps('SessionHostsTemplate').AcceleratedNetworking]", - "SessionHostDiskType": "[steps('SessionHostsTemplate').SessionHostDiskType]", - "DiskSizeGB": "[int(steps('SessionHostsTemplate').DiskSizeGB)]", - "MarketPlaceOrCustomImage": "[steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage]", - "MarketPlaceImage": "[steps('SessionHostsTemplate').dropDownMarketPlaceImage]", - "GalleryImageId": "[steps('SessionHostsTemplate').resourceSelectorSessionHostGalleryImageId.id]", - "SecurityType": "[steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType]", - "SecureBootEnabled": "[steps('SessionHostsTemplate').sessionHostsSecuritySection.SecureBootEnabled]", - "TpmEnabled": "[steps('SessionHostsTemplate').sessionHostsSecuritySection.TpmEnabled]", - "IdentityServiceProvider": "[steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider]", - "IntuneEnrollment": "[steps('SessionHostsTemplate').DomainJoinSection.IntuneEnrollment]", - "SubnetId": "[steps('SessionHostsTemplate').SubnetId]", - "ADDomainName": "[steps('SessionHostsTemplate').DomainJoinSection.ADDomainName]", - "ADDomainJoinUserName": "[steps('SessionHostsTemplate').DomainJoinSection.ADDomainJoinUserName]", - "ADJoinUserPassword": "[steps('SessionHostsTemplate').DomainJoinSection.ADJoinUserPassword]", - "ADOUPath": "[steps('SessionHostsTemplate').DomainJoinSection.ADOUPath]", - "LocalAdminUsername": "[steps('SessionHostsTemplate').LocalAdminUsername]", - "TagIncludeInAutomation": "[steps('optionalParametersStep')._Tag_IncludeInAutomation]", - "TagDeployTimestamp": "[steps('optionalParametersStep')._Tag_DeployTimestamp]", - "TagPendingDrainTimestamp": "[steps('optionalParametersStep')._Tag_PendingDrainTimestamp]", - "TagScalingPlanExclusionTag": "[steps('optionalParametersStep')._Tag_ScalingPlanExclusionTag]", - "TargetVMAgeDays": "[steps('optionalParametersStep')._TargetVMAgeDays]", - "DrainGracePeriodHours": "[steps('optionalParametersStep')._DrainGracePeriodHours]", - "FixSessionHostTags": "[steps('optionalParametersStep')._FixSessionHostTags]", - "IncludePreExistingSessionHosts": "[steps('basics').HostPoolSettingsSection.IncludePreExistingSessionHosts]", - "DeploymentPrefix": "[steps('optionalParametersStep')._SHRDeploymentPrefix]", - "SessionHostInstanceNumberPadding": "[steps('optionalParametersStep')._SessionHostInstanceNumberPadding]", - "SessionHostNameSeparator": "[if(steps('optionalParametersStep')._SessionHostNameSeparator,'-','')]", - "ReplaceSessionHostOnNewImageVersion": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersion]", - "ReplaceSessionHostOnNewImageVersionDelayDays": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersionDelayDays]", - "VMNamesTemplateParameterName": "VMNames", - "SessionHostResourceGroupName": "[steps('optionalParametersStep')._SessionHostResourceGroupName]", - "DnsServers": "[createArray(steps('SessionHostsTemplate').DnsSettings.DnsServer1, steps('SessionHostsTemplate').DnsSettings.DnsServer2)]" - } - } - } +} +} +} +] +}, +{ +"name": "SessionHostsTemplate", +"label": "Session Hosts Template", +"elements": [ +{ +"name": "SessionHostsRegion", +"type": "Microsoft.Common.DropDown", +"label": "Session Hosts Region", +"visible": true, +"filter": true, +"multiselect": false, +"selectAll": false, +"toolTip": "Select region to deploy session hosts.", +"constraints": { +"required": true, +"allowedValues": "[map( first( map( filter( steps('basics').computeApi.value, (resourceTypes) => equals(resourceTypes.resourceType, 'virtualMachines') ), (item) => item.locations) ), (item) => parse(concat('{\"label\":\"', item, '\",\"value\":\"', toLower(replace(item, ' ', '')), '\"}')) )]" +} +}, +{ +"name": "AvailabilityZones", +"type": "Microsoft.Common.DropDown", +"label": "Availability Zones", +"visible": true, +"filter": false, +"defaultValue": [], +"multiselect": true, +"selectAll": false, +"toolTip": "Select Availability Zones for the session hosts. Make sure the selected size is available in the selected zones. Session hosts will be deployed across the zones.", +"constraints": { +"required": false, +"allowedValues": [ +{ +"label": "Zone 1", +"value": "1" +}, +{ +"label": "Zone 2", +"value": "2" +}, +{ +"label": "Zone 3", +"value": "3" +} +] +} +}, +{ +"name": "SessionHostSize", +"type": "Microsoft.Compute.SizeSelector", +"label": "VM Size", +"toolTip": "", +"recommendedSizes": [ +"Standard_D4ads_v5" +], +"constraints": { +"allowedSizes": [], +"excludedSizes": [], +"required": true +}, +"options": { +"hideDiskTypeFilter": true +}, +"osPlatform": "Windows", +"imageReference": { +"publisher": "MicrosoftWindowsDesktop", +"offer": "Windows-11", +"sku": "win11-23h2-avd" +} +}, +{ +"name": "AcceleratedNetworking", +"type": "Microsoft.Common.CheckBox", +"label": "Enable accelerated networking", +"defaultValue": true, +"toolTip": "Enables low latency and high throughput on the network interface." +}, +{ +"name": "SessionHostDiskType", +"type": "Microsoft.Common.DropDown", +"label": "OS Disk type", +"filter": false, +"defaultValue": "Premium SSD", +"toolTip": "Select session host disk type to host the OS.", +"constraints": { +"required": true, +"allowedValues": [ +{ +"label": "Standard HDD", +"value": "Standard_LRS" +}, +{ +"label": "Standard SSD", +"value": "StandardSSD_LRS" +}, +{ +"label": "Premium SSD", +"value": "Premium_LRS" +} +] +} +}, +{ +"name": "optionMarketPlaceOrCustomImage", +"type": "Microsoft.Common.OptionsGroup", +"label": "Image Source", +"defaultValue": "Marketplace", +"toolTip": "", +"constraints": { +"allowedValues": [ +{ +"label": "Marketplace", +"value": "Marketplace" +}, +{ +"label": "Gallery Image", +"value": "Gallery" +} +], +"required": true +}, +"visible": true +}, +{ +"name": "dropDownMarketPlaceImage", +"type": "Microsoft.Common.DropDown", +"label": "Select a Marketplace Image", +"placeholder": "", +"defaultValue": "Windows 11 Enterprise 23h2 multi-session + Microsoft 365 Apps", +"toolTip": "Marketplace images are updated on monthly basis. The Session Host Replacer will replace the session hosts when a new image is available.", +"multiselect": false, +"selectAll": false, +"filter": true, +"filterPlaceholder": "Filter items ...", +"multiLine": true, +"constraints": { +"allowedValues": [ +{ +"label": "Windows 10 Enterprise 21h2 multi-session", +"value": "win10-21h2-avd" +}, +{ +"label": "Windows 10 Enterprise 21h2 multi-session (Gen 2)", +"value": "win10-21h2-avd-g2" +}, +{ +"label": "Windows 10 Enterprise 21h2 multi-session + Microsoft 365 Apps", +"value": "win10-21h2-avd-m365" +}, +{ +"label": "Windows 10 Enterprise 21h2 multi-session + Microsoft 365 Apps (Gen 2)", +"value": "win10-21h2-avd-m365-g2" +}, +{ +"label": "Windows 10 Enterprise 22h2 multi-session", +"value": "win10-22h2-avd" +}, +{ +"label": "Windows 10 Enterprise 22h2 multi-session (Gen 2)", +"value": "win10-22h2-avd-g2" +}, +{ +"label": "Windows 10 Enterprise 22h2 multi-session + Microsoft 365 Apps", +"value": "win10-22h2-avd-m365" +}, +{ +"label": "Windows 10 Enterprise 22h2 multi-session + Microsoft 365 Apps (Gen 2)", +"value": "win10-22h2-avd-m365-g2" +}, +{ +"label": "Windows 11 Enterprise 21h2 multi-session", +"value": "win11-21h2-avd" +}, +{ +"label": "Windows 11 Enterprise 21h2 multi-session + Microsoft 365 Apps", +"value": "win11-21h2-avd-m365" +}, +{ +"label": "Windows 11 Enterprise 22h2 multi-session", +"value": "win11-22h2-avd" +}, +{ +"label": "Windows 11 Enterprise 22h2 multi-session + Microsoft 365 Apps", +"value": "win11-22h2-avd-m365" +}, +{ +"label": "Windows 11 Enterprise 23h2 multi-session", +"value": "win11-23h2-avd" +}, +{ +"label": "Windows 11 Enterprise 23h2 multi-session + Microsoft 365 Apps", +"value": "win11-23h2-avd-m365" +} +], +"required": true +}, +"visible": "[equals(steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage,'Marketplace')]" +}, +{ +"name": "GalleryImageInfoBox", +"type": "Microsoft.Common.InfoBox", +"visible": "[equals(steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage,'Gallery')]", +"options": { +"icon": "Warning", +"text": "The system identity of the Session Host Replacer function is assigned the 'Desktop Virtualization Virtual Machine Contributor' role against the subscription. If the Image Definition is in a different subscription, please make sure you manually assign the permission post deployment." +} +}, +{ +"name": "resourceSelectorSessionHostGalleryImageId", +"type": "Microsoft.Solutions.ResourceSelector", +"label": "Select Gallery Image", +"resourceType": "Microsoft.Compute/galleries/images", +"constraints": { +"required": true +}, +"options": { +"filter": {} +}, +"visible": "[equals(steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage,'Gallery')]" +}, +{ +"name": "sessionHostsSecuritySection", +"type": "Microsoft.Common.Section", +"visible": true, +"label": "Security profile", +"elements": [ +{ +"name": "SecurityType", +"type": "Microsoft.Common.DropDown", +"label": "Security type", +"filter": true, +"defaultValue": "Trusted Launch Virtual Machines", +"toolTip": "Choose a type of security that matches your needs: Trusted launch virtual machines provide additional security features on Gen2 virtual machines to protect against persistent and advanced attacks.", +"constraints": { +"required": true, +"allowedValues": [ +{ +"label": "Standard", +"value": "Standard" +}, +{ +"label": "Trusted Launch Virtual Machines", +"value": "TrustedLaunch" +}, +{ +"label": "Confidential Virtual Machines", +"value": "ConfidentialVM" +} +] +} +}, +{ +"name": "SecureBootEnabled", +"type": "Microsoft.Common.CheckBox", +"visible": "[or(equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'TrustedLaunch'), equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'ConfidentialVM'))]", +"label": "Enable secure boot", +"defaultValue": true, +"toolTip": "Secure boot helps protect your VMs against boot kits, rootkits, and kernel-level malware." +}, +{ +"name": "TpmEnabled", +"type": "Microsoft.Common.CheckBox", +"visible": "[or(equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'TrustedLaunch'), equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'ConfidentialVM'))]", +"label": "Enable vTPM", +"defaultValue": true, +"toolTip": "Virtual Trusted Platform Module (vTPM) is TPM2.0 compliant and validates your VM boot integrity apart from securely storing keys and secrets." +} +] +}, +{ +"name": "SubnetId", +"type": "Microsoft.Common.TextBox", +"label": "Subnet ID", +"visible": true, +"placeholder": "Add resource id of subnet in the same region as the Session Hosts", +"constraints": { +"required": true, +"validations": [ +{ +"regex": ".*/subnets/[A-Za-z0-9_\\.-]+$", +"message": "Invalid Subnet Resource ID. Make sure it ends with /subnets/SubnetName" +} +] +} +}, +{ +"name": "DomainJoinSection", +"type": "Microsoft.Common.Section", +"visible": true, +"label": "Domain Join", +"elements": [ +{ +"name": "IdentityServiceProvider", +"type": "Microsoft.Common.OptionsGroup", +"visible": true, +"label": "Identity service provider", +"defaultValue": "Microsoft Entra ID", +"toolTip": "Identity service provider (Active Directory or EntraDS) that already exist and will be used for Azure Virtual Desktop.", +"constraints": { +"required": true, +"allowedValues": [ +{ +"label": "Microsoft Entra ID", +"value": "EntraID" +}, +{ +"label": "Active Directory (AD DS)", +"value": "ActiveDirectory" +}, +{ +"label": "Microsoft Entra Domain Services", +"value": "EntraDS" +} +] +} +}, +{ +"name": "EntraJoinedInfoBox", +"type": "Microsoft.Common.InfoBox", +"visible": "[equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID')]", +"options": { +"icon": "Warning", +"text": "When the VMs are Entra Joined, session host replacer will attempt to delete its device object from Entra ID. This requires additional permissions to be granted to the service principal used by session host replacer. Please refer to the documentation for more information.", +"uri": "https://github.com/Azure/AVDSessionHostReplacer/blob/main/docs/Permissions.md" +} +}, +{ +"name": "IntuneEnrollment", +"type": "Microsoft.Common.CheckBox", +"visible": "[equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID')]", +"label": "Intune enrollment", +"defaultValue": false, +"toolTip": "If Intune is configured in your Microsoft Entra ID tenant, you can choose to have the VM automatically enrolled during the deployment by selecting this box. Session Host Replacer will delete the device from Intune during replacement." +}, +{ +"name": "ADDomainName", +"type": "Microsoft.Common.TextBox", +"label": "AD Domain name", +"visible": "[or(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'ActiveDirectory'), equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraDS'))]", +"placeholder": "Example: contoso.com", +"constraints": { +"required": true +} +}, +{ +"name": "ADDomainJoinUserName", +"type": "Microsoft.Common.TextBox", +"label": "User principal name", +"visible": "[not(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID'))]", +"toolTip": "Provide username with permissions to join session host to the domain.", +"placeholder": "Example: avdadmin@contoso.com", +"defaultValue": "", +"constraints": { +"required": true +} +}, +{ +"name": "ADJoinUserPassword", +"type": "Microsoft.Common.PasswordBox", +"label": { +"password": "Password" +}, +"visible": "[not(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID'))]", +"toolTip": "Provide password for domain join account. This will be stored in a new Azure Key Vault.", +"constraints": { +"required": true +}, +"options": { +"hideConfirmation": true +} +}, +{ +"name": "ADOUPath", +"type": "Microsoft.Common.TextBox", +"visible": "[not(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID'))]", +"label": "Custom OU path (Optional)", +"toolTip": "Provide OU where to locate session hosts, if not provided session hosts will be placed on the default (computers) OU.", +"placeholder": "Example: OU=session-hosts,OU=avd,DC=contoso,DC=com", +"constraints": {} +} +] +}, +{ +"name": "LocalAdminUsername", +"type": "Microsoft.Common.TextBox", +"label": "Local Administrator Username", +"toolTip": "Provide username for session host local admin account. Administrator can't be used as username, it is reserved by the system. The password is randomly generated at deployment time.", +"placeholder": "Example: avdadmin", +"defaultValue": "", +"constraints": { +"regex": "^(?!.*[aA]dministrator).*$", +"validationMessage": "This username can't be used, it is a reserved word.", +"required": true +} +} +] +}, +{ +"name": "optionalParametersStep", +"label": "Optional Parameters", +"elements": [ +{ +"name": "_Tag_IncludeInAutomation", +"type": "Microsoft.Common.TextBox", +"label": "Include in Automation Tag Name", +"toolTip": "The name of the tag to use to determine if an existing session host should be included in the automation. After deployment, if the tag is present and set to 'true', the session host will be included. If the tag is not present or set to 'false', the session host will be excluded.", +"defaultValue": "IncludeInAutoReplace", +"constraints": { +"required": false +} +}, +{ +"name": "_Tag_DeployTimestamp", +"type": "Microsoft.Common.TextBox", +"label": "Deploy Timestamp Tag Name", +"toolTip": "The name of the tag to use to determine when the session host was deployed. This is updated by the session host replacer function on new session hosts. After deployment, you can edit the value of this tag to force replace a VM.", +"defaultValue": "AutoReplaceDeployTimestamp", +"constraints": { +"required": false +} +}, +{ +"name": "_Tag_PendingDrainTimestamp", +"type": "Microsoft.Common.TextBox", +"label": "Pending Drain Timestamp Tag Name", +"toolTip": "The name of the tag to use to determine when the session host was marked for drain. This is updated by the session host replacer function on hosts pending deletion.", +"defaultValue": "AutoReplacePendingDrainTimestamp", +"constraints": { +"required": false +} +}, +{ +"name": "_Tag_ScalingPlanExclusionTag", +"type": "Microsoft.Common.TextBox", +"label": "Scaling Plan Exclusion Tag Name", +"toolTip": "The name of the tag session host replacer will set to exclude a session host from scaling plans actions.", +"defaultValue": "ScalingPlanExclusion", +"constraints": { +"required": false +} +}, +{ +"name": "_TargetVMAgeDays", +"type": "Microsoft.Common.TextBox", +"label": "Target VM Age (Days)", +"toolTip": "The maximum age of a VM in days before it is replaced. This is compared to the value of the Deploy Timestamp Tag.", +"defaultValue": 45, +"constraints": { +"required": false +} +}, +{ +"name": "_DrainGracePeriodHours", +"type": "Microsoft.Common.TextBox", +"label": "Drain Grace Period (Hours)", +"toolTip": "The number of hours to wait after marking a VM for drain before deleting it. This is to allow users to finish their sessions before the VM is deleted.", +"defaultValue": 24, +"constraints": { +"required": false +} +}, +{ +"name": "_FixSessionHostTags", +"type": "Microsoft.Common.CheckBox", +"label": "Fix Existing Session Host Tags", +"toolTip": "If enabled, the session host replacer will fix the tags on existing session hosts or if tags are mistakenly deleted. The tag values will NOT allow deletion of existing session hosts and must be changed post deployment. This is useful if you are deploying a new session host replacer to an existing host pool.", +"defaultValue": true, +"constraints": { +"required": "[steps('basics').HostPoolSettingsSection.IncludePreExistingSessionHosts]", +"validationMessage": "This is required if Include pre-existing session hosts is selected." +} +}, +{ +"name": "_SHRDeploymentPrefix", +"type": "Microsoft.Common.TextBox", +"label": "Deployment Prefix", +"toolTip": "The prefix of the deployment created in the session hosts resource group when replacement VMs are deploying. This is used to track running and failed deployments.", +"defaultValue": "AVDSessionHostReplacer", +"constraints": { +"required": false +} +}, +{ +"name": "_SessionHostInstanceNumberPadding", +"type": "Microsoft.Common.Slider", +"min": 1, +"max": 4, +"label": "Session Host VM Number Padding", +"defaultValue": 2, +"showStepMarkers": true, +"constraints": { +"required": false +}, +"visible": true +}, +{ +"name": "_SessionHostNameSeparator", +"type": "Microsoft.Common.CheckBox", +"label": "Use '-' as separator", +"toolTip": "If enabled, the session host replacer will use '-' as a separator between the prefix and the instance number. If disabled, the session host replacer will not use separator.", +"defaultValue": true, +"constraints": { +"required": false +} +}, +{ +"name": "SessionHostExampleNameInfoBox", +"type": "Microsoft.Common.InfoBox", +"visible": "[not(empty(steps('basics').HostPoolSettingsSection.sessionHostNamePrefix))]", +"options": { +"icon": "Info", +"text": "[concat('Example Session Host name: ', steps('basics').HostPoolSettingsSection.sessionHostNamePrefix , if(steps('optionalParametersStep')._SessionHostNameSeparator,'-','') , take('00000000', sub(steps('optionalParametersStep')._SessionHostInstanceNumberPadding,1)) ,'1') ]" +} +}, +{ +"name": "_ReplaceSessionHostOnNewImageVersion", +"type": "Microsoft.Common.CheckBox", +"label": "Replace Session Hosts On New Image Version", +"toolTip": "(Recommended) If enabled, the session host replacer will replace session hosts when a new image version is available. This works for both marketplace and custom images. If disabled, the session host replacer will only replace session hosts when the VM age is greater than the target VM age.", +"defaultValue": true, +"constraints": { +"required": false +} +}, +{ +"name": "_ReplaceSessionHostOnNewImageVersionDelayDays", +"type": "Microsoft.Common.TextBox", +"visible": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersion]", +"label": "Replace on New Image Version Delay (Days)", +"toolTip": "The number of days to wait after a new image is available before replacing session hosts. This is to allow time for the image to be tested before replacing session hosts.", +"defaultValue": "0", +"constraints": { +"required": false +} +}, +{ +"name": "_SessionHostResourceGroupName", +"type": "Microsoft.Common.TextBox", +"label": "Session Hosts Resource Group Name", +"placeholder": "Same As Host Pool Resource Group", +"toolTip": "Leave this empty to deploy to same resource group as the host pool.", +"defaultValue": "", +"constraints": { +"required": false +} +} +] +} +] +}, +"outputs": { +"kind": "ResourceGroup", +"resourceGroupId": "[steps('basics').resourceScope.resourceGroup.id]", +"location": "[steps('basics').resourceScope.location.name]", +"parameters": { +"HostPoolResourceGroupName": "[steps('basics').HostPoolSettingsSection.HostPoolSelector.resourceGroup]", +"HostPoolName": "[steps('basics').HostPoolSettingsSection.HostPoolSelector.name]", +"SessionHostNamePrefix": "[steps('basics').HostPoolSettingsSection.sessionHostNamePrefix]", +"UseUserAssignedManagedIdentity": "[if(steps('basics').IdentitySection.UseUserAssignedManagedIdentity, true, false)]", +"UserAssignedManagedIdentityResourceId": "[if(steps('basics').IdentitySection.UseUserAssignedManagedIdentity, steps('basics').IdentitySection.UserAssignedManagedIdentitySelector.id, '')]", +"TargetSessionHostCount": "[steps('basics').HostPoolSettingsSection.TargetSessionHostCount]", +"TargetSessionHostBuffer": "[steps('basics').HostPoolSettingsSection.TargetSessionHostBuffer]", +"EnableMonitoring": "[steps('basics').MonitoringSection.EnableMonitoring]", +"UseExistingLAW": "[steps('basics').MonitoringSection.UseExistingLAW]", +"LogAnalyticsWorkspaceId": "[if(steps('basics').MonitoringSection.UseExistingLAW, steps('basics').MonitoringSection.LAWSelector.id, '')]", +"SessionHostsRegion": "[steps('SessionHostsTemplate').SessionHostsRegion]", +"AvailabilityZones": "[steps('SessionHostsTemplate').AvailabilityZones]", +"SessionHostSize": "[steps('SessionHostsTemplate').SessionHostSize]", +"AcceleratedNetworking": "[steps('SessionHostsTemplate').AcceleratedNetworking]", +"SessionHostDiskType": "[steps('SessionHostsTemplate').SessionHostDiskType]", +"MarketPlaceOrCustomImage": "[steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage]", +"MarketPlaceImage": "[steps('SessionHostsTemplate').dropDownMarketPlaceImage]", +"GalleryImageId": "[steps('SessionHostsTemplate').resourceSelectorSessionHostGalleryImageId.id]", +"SecurityType": "[steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType]", +"SecureBootEnabled": "[steps('SessionHostsTemplate').sessionHostsSecuritySection.SecureBootEnabled]", +"TpmEnabled": "[steps('SessionHostsTemplate').sessionHostsSecuritySection.TpmEnabled]", +"IdentityServiceProvider": "[steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider]", +"IntuneEnrollment": "[steps('SessionHostsTemplate').DomainJoinSection.IntuneEnrollment]", +"SubnetId": "[steps('SessionHostsTemplate').SubnetId]", +"ADDomainName": "[steps('SessionHostsTemplate').DomainJoinSection.ADDomainName]", +"ADDomainJoinUserName": "[steps('SessionHostsTemplate').DomainJoinSection.ADDomainJoinUserName]", +"ADJoinUserPassword": "[steps('SessionHostsTemplate').DomainJoinSection.ADJoinUserPassword]", +"ADOUPath": "[steps('SessionHostsTemplate').DomainJoinSection.ADOUPath]", +"LocalAdminUsername": "[steps('SessionHostsTemplate').LocalAdminUsername]", +"TagIncludeInAutomation": "[steps('optionalParametersStep')._Tag_IncludeInAutomation]", +"TagDeployTimestamp": "[steps('optionalParametersStep')._Tag_DeployTimestamp]", +"TagPendingDrainTimestamp": "[steps('optionalParametersStep')._Tag_PendingDrainTimestamp]", +"TagScalingPlanExclusionTag": "[steps('optionalParametersStep')._Tag_ScalingPlanExclusionTag]", +"TargetVMAgeDays": "[steps('optionalParametersStep')._TargetVMAgeDays]", +"DrainGracePeriodHours": "[steps('optionalParametersStep')._DrainGracePeriodHours]", +"FixSessionHostTags": "[steps('optionalParametersStep')._FixSessionHostTags]", +"IncludePreExistingSessionHosts": "[steps('basics').HostPoolSettingsSection.IncludePreExistingSessionHosts]", +"DeploymentPrefix": "[steps('optionalParametersStep')._SHRDeploymentPrefix]", +"SessionHostInstanceNumberPadding": "[steps('optionalParametersStep')._SessionHostInstanceNumberPadding]", +"SessionHostNameSeparator": "[if(steps('optionalParametersStep')._SessionHostNameSeparator,'-','')]", +"ReplaceSessionHostOnNewImageVersion": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersion]", +"ReplaceSessionHostOnNewImageVersionDelayDays": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersionDelayDays]", +"VMNamesTemplateParameterName": "VMNames", +"SessionHostResourceGroupName": "[steps('optionalParametersStep')._SessionHostResourceGroupName]" +} +} +} } From 2db63e4c25b4c8f8d02d8d8cf6fccec215da9d58 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:32:41 +0100 Subject: [PATCH 60/83] Update print statement from 'Hello' to 'Goodbye' --- deploy/arm/DeployAVDSessionHostReplacer.json | 2912 +++++++++--------- 1 file changed, 1429 insertions(+), 1483 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 36c9d95..00fa674 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -1,1489 +1,1435 @@ { - "$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": "16749693425937561368" - } - }, - "parameters": { - "Location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Required: No | Region of the Function App. This does not need to be the same as the location of the Azure Virtual Desktop Host Pool. | Default: Location of the resource group." - } - }, - "EnableMonitoring": { - "type": "bool", - "defaultValue": true - }, - "UseExistingLAW": { - "type": "bool", - "defaultValue": false - }, - "LogAnalyticsWorkspaceId": { - "type": "string", - "defaultValue": "none", - "metadata": { - "description": "Required: Yes | Name of the Log Analytics Workspace used by the Function App Insights." - } - }, - "UseStandardTemplate": { - "type": "bool", - "defaultValue": true - }, - "SessionHostsRegion": { - "type": "string", - "defaultValue": "" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "SessionHostSize": { - "type": "string", - "defaultValue": "" - }, - "AcceleratedNetworking": { - "type": "bool", - "defaultValue": false - }, - "SessionHostDiskType": { - "type": "string", - "defaultValue": "Premium_LRS", - "allowedValues": [ - "Standard_LRS", - "StandardSSD_LRS", - "Premium_LRS" - ] - }, - "DiskSizeGB": { - "type": "int", - "defaultValue": 128, - "metadata": { - "description": "OS disk size in GB" - } - }, - "MarketPlaceOrCustomImage": { - "type": "string", - "defaultValue": "Marketplace", - "allowedValues": [ - "Marketplace", - "Gallery" - ] - }, - "MarketPlaceImage": { - "type": "string", - "defaultValue": "win11-23h2-avd-m365", - "allowedValues": [ - "2022-datacenter-smalldisk-g2", - "win10-21h2-avd", - "2022-datacenter-core-g2", - "win10-22h2-avd-m365-g2", - "win11-21h2-avd", - "win10-21h2-avd-m365", - "win11-22h2-avd-m365", - "2022-datacenter-core-smalldisk-g2", - "win11-21h2-avd-m365", - "win11-23h2-avd", +"$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": "16749693425937561368" +} +}, +"parameters": { +"Location": { +"type": "string", +"defaultValue": "[resourceGroup().location]", +"metadata": { +"description": "Required: No | Region of the Function App. This does not need to be the same as the location of the Azure Virtual Desktop Host Pool. | Default: Location of the resource group." +} +}, +"EnableMonitoring": { +"type": "bool", +"defaultValue": true +}, +"UseExistingLAW": { +"type": "bool", +"defaultValue": false +}, +"LogAnalyticsWorkspaceId": { +"type": "string", +"defaultValue": "none", +"metadata": { +"description": "Required: Yes | Name of the Log Analytics Workspace used by the Function App Insights." +} +}, +"UseStandardTemplate": { +"type": "bool", +"defaultValue": true +}, +"SessionHostsRegion": { +"type": "string", +"defaultValue": "" +}, +"AvailabilityZones": { +"type": "array", +"defaultValue": [] +}, +"SessionHostSize": { +"type": "string", +"defaultValue": "" +}, +"AcceleratedNetworking": { +"type": "bool", +"defaultValue": false +}, +"SessionHostDiskType": { +"type": "string", +"defaultValue": "Premium_LRS", +"allowedValues": [ +"Standard_LRS", +"StandardSSD_LRS", +"Premium_LRS" +] +}, +"MarketPlaceOrCustomImage": { +"type": "string", +"defaultValue": "Marketplace", +"allowedValues": [ +"Marketplace", +"Gallery" +] +}, +"MarketPlaceImage": { +"type": "string", +"defaultValue": "win11-23h2-avd-m365", +"allowedValues": [ +"2022-datacenter-smalldisk-g2", +"win10-21h2-avd", +"2022-datacenter-core-g2", +"win10-22h2-avd-m365-g2", +"win11-21h2-avd", +"win10-21h2-avd-m365", +"win11-22h2-avd-m365", +"2022-datacenter-core-smalldisk-g2", +"win11-21h2-avd-m365", +"win11-23h2-avd", "win11-23h2-avd-m365", - "win11-22h2-avd", - "2022-datacenter-g2", - "win10-22h2-avd-g2" - ] - }, - "GalleryImageId": { - "type": "string", - "defaultValue": "" - }, - "SecurityType": { - "type": "string", - "defaultValue": "TrustedLaunch", - "allowedValues": [ - "Standard", - "TrustedLaunch", - "ConfidentialVM" - ] - }, - "SecureBootEnabled": { - "type": "bool", - "defaultValue": true - }, - "TpmEnabled": { - "type": "bool", - "defaultValue": true - }, - "SubnetId": { - "type": "string", - "defaultValue": "" - }, - "DnsServers": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional: List of DNS server IP addresses to configure on the network interface. Leave empty to use Azure DNS." - } - }, - "IdentityServiceProvider": { - "type": "string", - "defaultValue": "EntraID", - "allowedValues": [ - "EntraID", - "ActiveDirectory", - "EntraDS" - ] - }, - "IntuneEnrollment": { - "type": "bool", - "defaultValue": false - }, - "ADDomainName": { - "type": "string", - "defaultValue": "" - }, - "ADDomainJoinUserName": { - "type": "string", - "defaultValue": "" - }, - "ADJoinUserPassword": { - "type": "securestring", - "defaultValue": "" - }, - "ADOUPath": { - "type": "string", - "defaultValue": "" - }, - "LocalAdminUsername": { - "type": "string", - "defaultValue": "" - }, - "CustomTemplateSpecResourceId": { - "type": "string", - "defaultValue": "" - }, - "VMNamesTemplateParameterName": { - "type": "string", - "defaultValue": "VMNames", - "metadata": { - "description": "Required: No | The name of the parameter in the template that specifies the VM Names array." - } - }, - "CustomTemplateSpecParameters": { - "type": "object", - "defaultValue": {} - }, - "HostPoolResourceGroupName": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "Required: No | Name of the resource group containing the Azure Virtual Desktop Host Pool. | Default: The resource group of the Function App." - } - }, - "HostPoolName": { - "type": "string", - "metadata": { - "description": "Required: Yes | Name of the Azure Virtual Desktop Host Pool." - } - }, - "SessionHostNamePrefix": { - "type": "string", - "maxLength": 12, - "metadata": { - "description": "Required: Yes | Prefix used for the name of the session hosts." - } - }, - "SessionHostNameSeparator": { - "type": "string", - "defaultValue": "-", - "maxLength": 1, - "metadata": { - "description": "Required: NO | Separator between prefix and number. | Default: -" - } - }, - "TargetSessionHostCount": { - "type": "int", - "minValue": 0, - "metadata": { - "description": "Required: Yes | Number of session hosts to maintain in the host pool." - } - }, - "TargetSessionHostBuffer": { - "type": "int", - "minValue": 1, - "metadata": { - "description": "Required: Yes | The maximum number of session hosts to add during a replacement process" - } - }, - "UseGovDodGraph": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Required: No | Switches to using the US Governmment DoD graph endpoints for the Function App. | Default: false" - } - }, - "UseUserAssignedManagedIdentity": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Required: No | Resource Id of the User Assigned Managed Identity to use for the Function App. | Default: System Identity" - } - }, - "UserAssignedManagedIdentityResourceId": { - "type": "string", - "defaultValue": "" - }, - "TagIncludeInAutomation": { - "type": "string", - "defaultValue": "IncludeInAutoReplace", - "metadata": { - "description": "Required: No | Tag name used to indicate that a session host should be included in the automatic replacement process. | Default: IncludeInAutoReplace." - } - }, - "TagDeployTimestamp": { - "type": "string", - "defaultValue": "AutoReplaceDeployTimestamp", - "metadata": { - "description": "Required: No | Tag name used to indicate the timestamp of the last deployment of a session host. | Default: AutoReplaceDeployTimestamp." - } - }, - "TagPendingDrainTimestamp": { - "type": "string", - "defaultValue": "AutoReplacePendingDrainTimestamp", - "metadata": { - "description": "Required: No | Tag name used to indicate drain timestamp of session host pending deletion. | Default: AutoReplacePendingDrainTimestamp." - } - }, - "TagScalingPlanExclusionTag": { - "type": "string", - "defaultValue": "ScalingPlanExclusion", - "metadata": { - "description": "Required: No | Tag name used to exclude session host from Scaling Plan activities. | Default: ScalingPlanExclusion" - } - }, - "TargetVMAgeDays": { - "type": "int", - "defaultValue": 45, - "metadata": { - "description": "Required: No | Target age of session hosts in days. | Default: 45 days." - } - }, - "DrainGracePeriodHours": { - "type": "int", - "defaultValue": 24, - "metadata": { - "description": "Required: No | Grace period in hours for session hosts to drain before deletion. | Default: 24 hours." - } - }, - "FixSessionHostTags": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Required: No | If true, will apply tags for Include In Auto Replace and Deployment Timestamp to existing session hosts. This will not enable automatic deletion of existing session hosts. | Default: True." - } - }, - "IncludePreExistingSessionHosts": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Required: No | When enabled, the Session Host Replacer will automatically consider pre-existing VMs for replacement if they meet the criteria | Default: False." - } - }, - "SHRDeploymentPrefix": { - "type": "string", - "defaultValue": "AVDSessionHostReplacer", - "metadata": { - "description": "Required: No | Prefix used for the deployment name of the session hosts. | Default: AVDSessionHostReplacer" - } - }, - "SessionHostInstanceNumberPadding": { - "type": "int", - "defaultValue": 2, - "metadata": { - "description": "Required: No | Number of digits to use for the instance number of the session hosts (eg. AVDVM-01). | Default: 2" - } - }, - "ReplaceSessionHostOnNewImageVersion": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Required: No | If true, will replace session hosts when a new image version is detected. | Default: true" - } - }, - "ReplaceSessionHostOnNewImageVersionDelayDays": { - "type": "int", - "defaultValue": 0, - "metadata": { - "description": "Required: No | Delay in days before replacing session hosts when a new image version is detected. | Default: 0 (no delay)." - } - }, - "SessionHostResourceGroupName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Required: No | Leave this empty to deploy to same resource group as the host pool." - } - }, - "TimeStamp": { - "type": "string", - "defaultValue": "[utcNow()]" - } - }, - "variables": { - "varMarketPlaceImages": { - "win10-21h2-avd": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "windows-10", - "sku": "win10-21h2-avd" - }, - "win10-21h2-avd-g2": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "windows-10", - "sku": "win10-21h2-avd-g2" - }, - "win10-21h2-avd-m365": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "office-365", - "sku": "win10-21h2-avd-m365" - }, - "win10-21h2-avd-m365-g2": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "office-365", - "sku": "win10-21h2-avd-m365-g2" - }, - "win10-22h2-avd": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "windows-10", - "sku": "win10-22h2-avd" - }, - "win10-22h2-avd-g2": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "windows-10", - "sku": "win10-22h2-avd-g2" - }, - "win10-22h2-avd-m365": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "office-365", - "sku": "win10-22h2-avd-m365" - }, - "win10-22h2-avd-m365-g2": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "office-365", - "sku": "win10-22h2-avd-m365-g2" - }, - "win11-21h2-avd": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "windows-11", - "sku": "win11-21h2-avd" - }, - "win11-21h2-avd-m365": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "office-365", - "sku": "win11-21h2-avd-m365" - }, - "win11-22h2-avd": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "windows-11", - "sku": "win11-22h2-avd" - }, - "win11-22h2-avd-m365": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "office-365", - "sku": "win11-22h2-avd-m365" - }, - "win11-23h2-avd": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "windows-11", - "sku": "win11-23h2-avd" - }, + "win11-25h2-avd-m365", +"win11-22h2-avd", +"2022-datacenter-g2", +"win10-22h2-avd-g2" +] +}, +"GalleryImageId": { +"type": "string", +"defaultValue": "" +}, +"SecurityType": { +"type": "string", +"defaultValue": "TrustedLaunch", +"allowedValues": [ +"Standard", +"TrustedLaunch", +"ConfidentialVM" +] +}, +"SecureBootEnabled": { +"type": "bool", +"defaultValue": true +}, +"TpmEnabled": { +"type": "bool", +"defaultValue": true +}, +"SubnetId": { +"type": "string", +"defaultValue": "" +}, +"IdentityServiceProvider": { +"type": "string", +"defaultValue": "EntraID", +"allowedValues": [ +"EntraID", +"ActiveDirectory", +"EntraDS" +] +}, +"IntuneEnrollment": { +"type": "bool", +"defaultValue": false +}, +"ADDomainName": { +"type": "string", +"defaultValue": "" +}, +"ADDomainJoinUserName": { +"type": "string", +"defaultValue": "" +}, +"ADJoinUserPassword": { +"type": "securestring", +"defaultValue": "" +}, +"ADOUPath": { +"type": "string", +"defaultValue": "" +}, +"LocalAdminUsername": { +"type": "string", +"defaultValue": "" +}, +"CustomTemplateSpecResourceId": { +"type": "string", +"defaultValue": "" +}, +"VMNamesTemplateParameterName": { +"type": "string", +"defaultValue": "VMNames", +"metadata": { +"description": "Required: No | The name of the parameter in the template that specifies the VM Names array." +} +}, +"CustomTemplateSpecParameters": { +"type": "object", +"defaultValue": {} +}, +"HostPoolResourceGroupName": { +"type": "string", +"defaultValue": "[resourceGroup().name]", +"metadata": { +"description": "Required: No | Name of the resource group containing the Azure Virtual Desktop Host Pool. | Default: The resource group of the Function App." +} +}, +"HostPoolName": { +"type": "string", +"metadata": { +"description": "Required: Yes | Name of the Azure Virtual Desktop Host Pool." +} +}, +"SessionHostNamePrefix": { +"type": "string", +"maxLength": 12, +"metadata": { +"description": "Required: Yes | Prefix used for the name of the session hosts." +} +}, +"SessionHostNameSeparator": { +"type": "string", +"defaultValue": "-", +"maxLength": 1, +"metadata": { +"description": "Required: NO | Separator between prefix and number. | Default: -" +} +}, +"TargetSessionHostCount": { +"type": "int", +"minValue": 0, +"metadata": { +"description": "Required: Yes | Number of session hosts to maintain in the host pool." +} +}, +"TargetSessionHostBuffer": { +"type": "int", +"minValue": 1, +"metadata": { +"description": "Required: Yes | The maximum number of session hosts to add during a replacement process" +} +}, +"UseGovDodGraph": { +"type": "bool", +"defaultValue": false, +"metadata": { +"description": "Required: No | Switches to using the US Governmment DoD graph endpoints for the Function App. | Default: false" +} +}, +"UseUserAssignedManagedIdentity": { +"type": "bool", +"defaultValue": false, +"metadata": { +"description": "Required: No | Resource Id of the User Assigned Managed Identity to use for the Function App. | Default: System Identity" +} +}, +"UserAssignedManagedIdentityResourceId": { +"type": "string", +"defaultValue": "" +}, +"TagIncludeInAutomation": { +"type": "string", +"defaultValue": "IncludeInAutoReplace", +"metadata": { +"description": "Required: No | Tag name used to indicate that a session host should be included in the automatic replacement process. | Default: IncludeInAutoReplace." +} +}, +"TagDeployTimestamp": { +"type": "string", +"defaultValue": "AutoReplaceDeployTimestamp", +"metadata": { +"description": "Required: No | Tag name used to indicate the timestamp of the last deployment of a session host. | Default: AutoReplaceDeployTimestamp." +} +}, +"TagPendingDrainTimestamp": { +"type": "string", +"defaultValue": "AutoReplacePendingDrainTimestamp", +"metadata": { +"description": "Required: No | Tag name used to indicate drain timestamp of session host pending deletion. | Default: AutoReplacePendingDrainTimestamp." +} +}, +"TagScalingPlanExclusionTag": { +"type": "string", +"defaultValue": "ScalingPlanExclusion", +"metadata": { +"description": "Required: No | Tag name used to exclude session host from Scaling Plan activities. | Default: ScalingPlanExclusion" +} +}, +"TargetVMAgeDays": { +"type": "int", +"defaultValue": 45, +"metadata": { +"description": "Required: No | Target age of session hosts in days. | Default: 45 days." +} +}, +"DrainGracePeriodHours": { +"type": "int", +"defaultValue": 24, +"metadata": { +"description": "Required: No | Grace period in hours for session hosts to drain before deletion. | Default: 24 hours." +} +}, +"FixSessionHostTags": { +"type": "bool", +"defaultValue": true, +"metadata": { +"description": "Required: No | If true, will apply tags for Include In Auto Replace and Deployment Timestamp to existing session hosts. This will not enable automatic deletion of existing session hosts. | Default: True." +} +}, +"IncludePreExistingSessionHosts": { +"type": "bool", +"defaultValue": false, +"metadata": { +"description": "Required: No | When enabled, the Session Host Replacer will automatically consider pre-existing VMs for replacement if they meet the criteria | Default: False." +} +}, +"SHRDeploymentPrefix": { +"type": "string", +"defaultValue": "AVDSessionHostReplacer", +"metadata": { +"description": "Required: No | Prefix used for the deployment name of the session hosts. | Default: AVDSessionHostReplacer" +} +}, +"SessionHostInstanceNumberPadding": { +"type": "int", +"defaultValue": 2, +"metadata": { +"description": "Required: No | Number of digits to use for the instance number of the session hosts (eg. AVDVM-01). | Default: 2" +} +}, +"ReplaceSessionHostOnNewImageVersion": { +"type": "bool", +"defaultValue": true, +"metadata": { +"description": "Required: No | If true, will replace session hosts when a new image version is detected. | Default: true" +} +}, +"ReplaceSessionHostOnNewImageVersionDelayDays": { +"type": "int", +"defaultValue": 0, +"metadata": { +"description": "Required: No | Delay in days before replacing session hosts when a new image version is detected. | Default: 0 (no delay)." +} +}, +"SessionHostResourceGroupName": { +"type": "string", +"defaultValue": "", +"metadata": { +"description": "Required: No | Leave this empty to deploy to same resource group as the host pool." +} +}, +"TimeStamp": { +"type": "string", +"defaultValue": "[utcNow()]" +} +}, +"variables": { +"varMarketPlaceImages": { +"win10-21h2-avd": { +"publisher": "MicrosoftWindowsDesktop", +"offer": "windows-10", +"sku": "win10-21h2-avd" +}, +"win10-21h2-avd-g2": { +"publisher": "MicrosoftWindowsDesktop", +"offer": "windows-10", +"sku": "win10-21h2-avd-g2" +}, +"win10-21h2-avd-m365": { +"publisher": "MicrosoftWindowsDesktop", +"offer": "office-365", +"sku": "win10-21h2-avd-m365" +}, +"win10-21h2-avd-m365-g2": { +"publisher": "MicrosoftWindowsDesktop", +"offer": "office-365", +"sku": "win10-21h2-avd-m365-g2" +}, +"win10-22h2-avd": { +"publisher": "MicrosoftWindowsDesktop", +"offer": "windows-10", +"sku": "win10-22h2-avd" +}, +"win10-22h2-avd-g2": { +"publisher": "MicrosoftWindowsDesktop", +"offer": "windows-10", +"sku": "win10-22h2-avd-g2" +}, +"win10-22h2-avd-m365": { +"publisher": "MicrosoftWindowsDesktop", +"offer": "office-365", +"sku": "win10-22h2-avd-m365" +}, +"win10-22h2-avd-m365-g2": { +"publisher": "MicrosoftWindowsDesktop", +"offer": "office-365", +"sku": "win10-22h2-avd-m365-g2" +}, +"win11-21h2-avd": { +"publisher": "MicrosoftWindowsDesktop", +"offer": "windows-11", +"sku": "win11-21h2-avd" +}, +"win11-21h2-avd-m365": { +"publisher": "MicrosoftWindowsDesktop", +"offer": "office-365", +"sku": "win11-21h2-avd-m365" +}, +"win11-22h2-avd": { +"publisher": "MicrosoftWindowsDesktop", +"offer": "windows-11", +"sku": "win11-22h2-avd" +}, +"win11-22h2-avd-m365": { +"publisher": "MicrosoftWindowsDesktop", +"offer": "office-365", +"sku": "win11-22h2-avd-m365" +}, +"win11-23h2-avd": { +"publisher": "MicrosoftWindowsDesktop", +"offer": "windows-11", +"sku": "win11-23h2-avd" +}, "win11-23h2-avd-m365": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "office-365", + "win11-25h2-avd-m365": { +"publisher": "MicrosoftWindowsDesktop", +"offer": "office-365", "sku": "win11-23h2-avd-m365" - } - }, - "varImageReference": "[if(equals(parameters('MarketPlaceOrCustomImage'), 'Marketplace'), createObject('publisher', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].publisher, 'offer', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].offer, 'sku', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].sku, 'version', 'latest'), createObject('Id', parameters('GalleryImageId')))]", - "varSecurityProfile": "[if(equals(parameters('SecurityType'), 'Standard'), null(), createObject('securityType', parameters('SecurityType'), 'uefiSettings', createObject('secureBootEnabled', parameters('SecureBootEnabled'), 'vTpmEnabled', parameters('TpmEnabled'))))]", - "varDomainJoinObject": "[if(equals(parameters('IdentityServiceProvider'), 'EntraID'), createObject('DomainType', 'EntraID', 'IntuneJoin', parameters('IntuneEnrollment')), createObject('DomainType', 'ActiveDirectory', 'DomainName', parameters('ADDomainName'), 'DomainJoinUserName', parameters('ADDomainJoinUserName'), 'ADOUPath', parameters('ADOUPath')))]", - "varAzureEnvironments": [ - "AzureCloud", - "AzureUSGovernment", - "AzureChinaCloud" - ], - "splitParts": "[split(parameters('HostPoolResourceGroupName'), '-')]", - "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4], '-', variables('splitParts')[5])]", - "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", - "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", - "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", - "varFunctionAppName": "[concat('AVDReplacer', variables('rgpattern'))]", - "varFunctionAppIdentity": "[if(parameters('UseUserAssignedManagedIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', parameters('UserAssignedManagedIdentityResourceId')), createObject())), createObject('type', 'SystemAssigned'))]" - }, - "resources": [ - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "deployFunctionApp", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "Location": { - "value": "[parameters('Location')]" - }, - "FunctionAppName": { - "value": "[variables('varFunctionAppName')]" - }, - "EnableMonitoring": { - "value": "[parameters('EnableMonitoring')]" - }, - "UseExistingLAW": { - "value": "[parameters('UseExistingLAW')]" - }, - "LogAnalyticsWorkspaceId": { - "value": "[parameters('LogAnalyticsWorkspaceId')]" - }, - "ReplacementPlanSettings": { - "value": [ - { - "name": "_HostPoolResourceGroupName", - "value": "[parameters('HostPoolResourceGroupName')]" - }, - { - "name": "_HostPoolName", - "value": "[parameters('HostPoolName')]" - }, - { - "name": "_TargetSessionHostCount", - "value": "[parameters('TargetSessionHostCount')]" - }, - { - "name": "_TargetSessionHostBuffer", - "value": "[parameters('TargetSessionHostBuffer')]" - }, - { - "name": "_SessionHostNamePrefix", - "value": "[parameters('SessionHostNamePrefix')]" - }, - { - "name": "_SessionHostNameSeparator", - "value": "[parameters('SessionHostNameSeparator')]" - }, - { - "name": "_SessionHostTemplate", - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate'), '2022-09-01').outputs.TemplateSpecResourceId.value]" - }, - { - "name": "_SessionHostParameters", - "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'DnsServers', parameters('DnsServers'), 'DiskSizeGB', parameters('DiskSizeGB'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" - }, - { - "name": "_SubscriptionId", - "value": "[subscription().subscriptionId]" - }, - { - "name": "_RemoveEntraDevice", - "value": "[equals(parameters('IdentityServiceProvider'), 'EntraID')]" - }, - { - "name": "_RemoveIntuneDevice", - "value": "[parameters('IntuneEnrollment')]" - }, - { - "name": "_ClientId", - "value": "[if(parameters('UseUserAssignedManagedIdentity'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').clientId, '')]" - }, - { - "name": "_TenantId", - "value": "[if(parameters('UseUserAssignedManagedIdentity'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').tenantId, '')]" - }, - { - "name": "_GraphEnvironmentName", - "value": "[variables('varGraphEnvironmentName')]" - }, - { - "name": "_AzureEnvironmentName", - "value": "[environment().name]" - }, - { - "name": "_Tag_IncludeInAutomation", - "value": "[parameters('TagIncludeInAutomation')]" - }, - { - "name": "_Tag_DeployTimestamp", - "value": "[parameters('TagDeployTimestamp')]" - }, - { - "name": "_Tag_PendingDrainTimestamp", - "value": "[parameters('TagPendingDrainTimestamp')]" - }, - { - "name": "_Tag_ScalingPlanExclusionTag", - "value": "[parameters('TagScalingPlanExclusionTag')]" - }, - { - "name": "_TargetVMAgeDays", - "value": "[parameters('TargetVMAgeDays')]" - }, - { - "name": "_DrainGracePeriodHours", - "value": "[parameters('DrainGracePeriodHours')]" - }, - { - "name": "_FixSessionHostTags", - "value": "[parameters('FixSessionHostTags')]" - }, - { - "name": "_IncludePreExistingSessionHosts", - "value": "[parameters('IncludePreExistingSessionHosts')]" - }, - { - "name": "_SHRDeploymentPrefix", - "value": "[parameters('SHRDeploymentPrefix')]" - }, - { - "name": "_SessionHostInstanceNumberPadding", - "value": "[parameters('SessionHostInstanceNumberPadding')]" - }, - { - "name": "_ReplaceSessionHostOnNewImageVersion", - "value": "[parameters('ReplaceSessionHostOnNewImageVersion')]" - }, - { - "name": "_ReplaceSessionHostOnNewImageVersionDelayDays", - "value": "[parameters('ReplaceSessionHostOnNewImageVersionDelayDays')]" - }, - { - "name": "_VMNamesTemplateParameterName", - "value": "[parameters('VMNamesTemplateParameterName')]" - }, - { - "name": "_SessionHostResourceGroupName", - "value": "[parameters('SessionHostResourceGroupName')]" - } - ] - }, - "FunctionAppIdentity": { - "value": "[variables('varFunctionAppIdentity')]" - } - }, - "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": "3734734378229800954" - } - }, - "parameters": { - "Location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Required: No | Region of the Function App. This does not need to be the same as the location of the Azure Virtual Desktop Host Pool. | Default: Location of the resource group." - } - }, - "EnableMonitoring": { - "type": "bool", - "defaultValue": true - }, - "UseExistingLAW": { - "type": "bool", - "defaultValue": false - }, - "LogAnalyticsWorkspaceId": { - "type": "string", - "defaultValue": "none", - "metadata": { - "description": "Required: Yes | Name of the Log Analytics Workspace used by the Function App Insights." - } - }, - "FunctionAppName": { - "type": "string", - "metadata": { - "description": "Required: Yes | Name of the Function App." - } - }, - "FunctionAppUrl": { - "type": "string", - "defaultValue": "https://github.com/stefze/AVDSessionHostReplacer/blob/main/FunctionApp/FunctionApp.zip", - "metadata": { - "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." - } - }, - "AppPlanName": { - "type": "string", - "defaultValue": "Y1", - "metadata": { - "description": "Required: No | App Service Plan Name | Default: Y1 for consumption based plan" - } - }, - "AppPlanTier": { - "type": "string", - "defaultValue": "Dynamic", - "metadata": { - "description": "Required: No | App Service Plan Tier | Default: Dynamic for consumption based plan" - } - }, - "ReplacementPlanSettings": { - "type": "array", - "metadata": { - "description": "Required: Yes | The following settings are mandatory. Rest are optional.\n[\n {\n name: '_HostPoolResourceGroupName'\n value: 'string'\n }\n {\n name: '_HostPoolName'\n value: 'string'\n }\n {\n name: '_RemoveEntraDevice'\n value: 'bool'\n }\n {\n name: '_SessionHostTemplate'\n value: 'string'\n }\n {\n name: '_SessionHostParameters'\n value: 'hashtable'\n }\n {\n name: '_SubscriptionId'\n value: 'string'\n }\n {\n name: '_TargetSessionHostCount'\n value: 'int'\n }\n {\n name: '_SessionHostNamePrefix'\n value: 'string'\n }\n]" - } - }, - "FunctionAppIdentity": { - "type": "object", - "defaultValue": { - "type": "SystemAssigned" - } - } - }, - "variables": { - "rgname": "[toLower(resourceGroup().name)]", - "splitParts": "[split(variables('rgname'), '-')]", - "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", - "varStorageAccountName": "[concat('st', variables('rgpattern2'))]", - "varLogAnalyticsWorkspaceName": "[concat('law-', parameters('FunctionAppName'))]", - "varAppServicePlanName": "[concat('Asp-', parameters('FunctionAppName'))]", - "varGraphEnvironmentName": "your_value_here" - }, - "resources": [ - { - "type": "Microsoft.Web/sites/extensions", - "apiVersion": "2023-01-01", - "name": "[format('{0}/{1}', parameters('FunctionAppName'), 'MSDeploy')]", - "properties": { - "packageUri": "[parameters('FunctionAppZipUrl')]" - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites', parameters('FunctionAppName'))]" - ] - }, - { - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-05-01", - "name": "[variables('varStorageAccountName')]", - "location": "[parameters('Location')]", - "kind": "StorageV2", - "sku": { - "name": "Standard_LRS" - }, - "properties": {} - }, - { - "condition": "[and(parameters('EnableMonitoring'), not(parameters('UseExistingLAW')))]", - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2023-09-01", - "name": "[variables('varLogAnalyticsWorkspaceName')]", - "location": "[parameters('Location')]", - "properties": { - "sku": { - "name": "PerGB2018" - }, - "retentionInDays": 30 - } - }, - { - "type": "Microsoft.Web/serverfarms", - "apiVersion": "2022-03-01", - "name": "[variables('varAppServicePlanName')]", - "location": "[parameters('Location')]", - "sku": { - "name": "[parameters('AppPlanName')]", - "tier": "[parameters('AppPlanTier')]" - } - }, - { - "condition": "[parameters('EnableMonitoring')]", - "type": "Microsoft.Insights/components", - "apiVersion": "2020-02-02", - "name": "[variables('varAppServicePlanName')]", - "location": "[parameters('Location')]", - "kind": "web", - "properties": { - "Application_Type": "web", - "publicNetworkAccessForIngestion": "Enabled", - "publicNetworkAccessForQuery": "Enabled", - "WorkspaceResourceId": "[if(parameters('UseExistingLAW'), parameters('LogAnalyticsWorkspaceId'), resourceId('Microsoft.OperationalInsights/workspaces', variables('varLogAnalyticsWorkspaceName')))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', variables('varLogAnalyticsWorkspaceName'))]" - ] - }, - { - "type": "Microsoft.Web/sites", - "apiVersion": "2023-01-01", - "name": "[parameters('FunctionAppName')]", - "location": "[parameters('Location')]", - "kind": "functionApp", - "identity": "[parameters('FunctionAppIdentity')]", - "properties": { - "httpsOnly": true, - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('varAppServicePlanName'))]", - "siteConfig": { - "use32BitWorkerProcess": false, - "powerShellVersion": "7.2", - "netFrameworkVersion": "v6.0", - "appSettings": "[union(createArray(createObject('name', 'FUNCTIONS_EXTENSION_VERSION', 'value', '~4'), createObject('name', 'FUNCTIONS_WORKER_RUNTIME', 'value', 'powershell'), createObject('name', 'AzureWebJobsStorage', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey), createObject('name', 'WEBSITE_CONTENTSHARE', 'value', toLower(parameters('FunctionAppName')))), if(parameters('EnableMonitoring'), createArray(createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey)), createArray()), parameters('ReplacementPlanSettings'))]", - "ftpsState": "Disabled", - "cors": { - "allowedOrigins": [ - "https://portal.azure.com" - ] - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Insights/components', variables('varAppServicePlanName'))]", - "[resourceId('Microsoft.Web/serverfarms', variables('varAppServicePlanName'))]", - "[resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName'))]" - ] - } - ], - "outputs": { - "functionAppPrincipalId": { - "type": "string", - "value": "[if(equals(parameters('FunctionAppIdentity').type, 'SystemAssigned'), reference(resourceId('Microsoft.Web/sites', parameters('FunctionAppName')), '2023-01-01', 'full').identity.principalId, '')]" - } - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', 'deployKeyVault')]", - "[resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate')]" - ] - }, - { - "condition": "[not(equals(parameters('IdentityServiceProvider'), 'EntraID'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "deployKeyVault", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "Location": { - "value": "[parameters('Location')]" - }, - "KeyVaultName": { - "value": "[concat('kvSHR', variables('rgpattern2'))]" - }, - "DomainJoinPassword": { - "value": "[parameters('ADJoinUserPassword')]" - } - }, - "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": "17584014780822218958" - } - }, - "parameters": { - "Location": { - "type": "string", - "defaultValue": "[resourceGroup().location]" - }, - "KeyVaultName": { - "type": "string" - }, - "DomainJoinPassword": { - "type": "securestring" - } - }, - "resources": [ - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('KeyVaultName'), 'DomainJoinPassword')]", - "properties": { - "value": "[parameters('DomainJoinPassword')]" - }, - "dependsOn": [ - "[resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-07-01", - "name": "[parameters('KeyVaultName')]", - "location": "[parameters('Location')]", - "properties": { - "sku": { - "family": "A", - "name": "standard" - }, - "tenantId": "[subscription().tenantId]", - "enabledForTemplateDeployment": true, - "enableRbacAuthorization": true - } - } - ], - "outputs": { - "keyVaultId": { - "type": "string", - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]" - } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "deployStandardSessionHostTemplate", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "Location": { - "value": "[parameters('Location')]" - }, - "Name": { - "value": "[format('{0}-Spec', parameters('HostPoolName'))]" - } - }, - "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": "10208603161915711494" - } - }, - "parameters": { - "Name": { - "type": "string" - }, - "Location": { - "type": "string", - "defaultValue": "[resourceGroup().location]" - } - }, - "variables": { - "$fxv#0": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.33.4.45862", - "templateHash": "13792602582245425536" - } - }, - "parameters": { - "Location": { - "type": "string", - "defaultValue": "[[resourceGroup().location]" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "VMNames": { - "type": "array" - }, - "VMNamePrefixLength": { - "type": "int" - }, - "VMSize": { - "type": "string" - }, - "SubnetID": { - "type": "string" - }, - "AdminUsername": { - "type": "string" - }, - "AcceleratedNetworking": { - "type": "bool" - }, - "DiskType": { - "type": "string" - }, - "DiskSizeGB": { - "type": "int", - "defaultValue": 128, - "metadata": { - "description": "OS disk size in GB" - } - }, - "DnsServers": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "List of DNS server IP addresses to set on the NIC" - } - }, - "Tags": { - "type": "object", - "defaultValue": {} - }, - "ImageReference": { - "type": "object" - }, - "SecurityProfile": { - "type": "object", - "defaultValue": {} - }, - "HostPoolName": { - "type": "string" - }, - "HostPoolToken": { - "type": "securestring" - }, - "WVDArtifactsURL": { - "type": "string", - "defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" - }, - "DomainJoinObject": { - "type": "object", - "defaultValue": {} - }, - "DomainJoinPassword": { - "type": "securestring", - "defaultValue": "" - } - }, - "resources": [ - { - "copy": { - "name": "deploySessionHosts", - "count": "[[length(parameters('VMNames'))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "AcceleratedNetworking": { - "value": "[[parameters('AcceleratedNetworking')]" - }, - "AdminUsername": { - "value": "[[parameters('AdminUsername')]" - }, - "HostPoolName": { - "value": "[[parameters('HostPoolName')]" - }, - "HostPoolToken": { - "value": "[[parameters('HostPoolToken')]" - }, - "ImageReference": { - "value": "[[parameters('ImageReference')]" - }, - "SecurityProfile": { - "value": "[[parameters('SecurityProfile')]" - }, - "SubnetID": { - "value": "[[parameters('SubnetID')]" - }, - "VMName": { - "value": "[[parameters('VMNames')[copyIndex()]]" - }, - "VMNamePrefixLength": { - "value": "[[parameters('VMNamePrefixLength')]" - }, - "VMSize": { - "value": "[[parameters('VMSize')]" - }, - "DiskType": { - "value": "[[parameters('DiskType')]" - }, - "DiskSizeGB": { - "value": "[[parameters('DiskSizeGB')]" - }, - "DnsServers": { - "value": "[[parameters('DnsServers')]" - }, - "WVDArtifactsURL": { - "value": "[[parameters('WVDArtifactsURL')]" - }, - "DomainJoinObject": { - "value": "[[parameters('DomainJoinObject')]" - }, - "DomainJoinPassword": { - "value": "[[parameters('DomainJoinPassword')]" - }, - "Location": { - "value": "[[parameters('Location')]" - }, - "AvailabilityZones": { - "value": "[[parameters('AvailabilityZones')]" - }, - "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.32.4.45862", - "templateHash": "9402242463429439832" - } - }, - "parameters": { - "VMName": { - "type": "string" - }, - "VMNamePrefixLength": { - "type": "int" - }, - "VMSize": { - "type": "string" - }, - "DiskType": { - "type": "string" - }, - "DiskSizeGB": { - "type": "int", - "defaultValue": 128, - "metadata": { - "description": "OS disk size in GB" - } - }, - "DnsServers": { - "type": "array", - "metadata": { - "description": "List of DNS server IP addresses to set on the NIC" - } - }, - "Location": { - "type": "string", - "defaultValue": "[[resourceGroup().location]" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "SubnetID": { - "type": "string" - }, - "AdminUsername": { - "type": "string" - }, - "AdminPassword": { - "type": "securestring", - "defaultValue": "[[newGuid()]" - }, - "AcceleratedNetworking": { - "type": "bool" - }, - "Tags": { - "type": "object", - "defaultValue": {} - }, - "ImageReference": { - "type": "object" - }, - "SecurityProfile": { - "type": "object" - }, - "HostPoolName": { - "type": "string" - }, - "HostPoolToken": { - "type": "securestring" - }, - "WVDArtifactsURL": { - "type": "string" - }, - "DomainJoinObject": { - "type": "object", - "defaultValue": {} - }, - "DomainJoinPassword": { - "type": "securestring", - "defaultValue": "" - } - }, - "variables": { - "varRequireNvidiaGPU": "[[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", - "varVMNumber": "[[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", - "varAvailabilityZone": "[[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" - }, - "resources": [ - { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'deployIntegrityMonitoring')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Azure.Security.WindowsAttestation", - "type": "GuestAttestation", - "typeHandlerVersion": "1.0", - "autoUpgradeMinorVersion": true, - "settings": { - "AttestationConfig": { - "MaaSettings": { - "maaEndpoint": "", - "maaTenantName": "Guest Attestation" - }, - "AscSettings": { - "ascReportingEndpoint": "", - "ascReportingFrequency": "" - }, - "useCustomToken": "false", - "disableAlerts": "false" - } - } - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[variables('varRequireNvidiaGPU')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.HpcCompute", - "type": "NvidiaGpuDriverWindows", - "typeHandlerVersion": "1.6", - "autoUpgradeMinorVersion": true - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployIntegrityMonitoring')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[not(equals(parameters('HostPoolName'), ''))]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.PowerShell", - "type": "DSC", - "typeHandlerVersion": "2.77", - "autoUpgradeMinorVersion": true, - "settings": { - "modulesUrl": "[[parameters('WVDArtifactsURL')]", - "configurationFunction": "Configuration.ps1\\AddSessionHost", - "properties": { - "hostPoolName": "[[parameters('HostPoolName')]", - "registrationInfoToken": "[[parameters('HostPoolToken')]", - "aadJoin": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", - "useAgentDownloadEndpoint": true, - "mdmId": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" - } - } - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Azure.ActiveDirectory", - "type": "AADLoginForWindows", - "typeHandlerVersion": "2.0", - "autoUpgradeMinorVersion": true, - "settings": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Compute", - "type": "JSonADDomainExtension", - "typeHandlerVersion": "1.3", - "autoUpgradeMinorVersion": true, - "settings": { - "Name": "[[parameters('DomainJoinObject').DomainName]", - "OUPath": "[[parameters('DomainJoinObject').ADOUPath]", - "User": "[[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", - "Restart": "true", - "Options": 3 - }, - "protectedSettings": { - "Password": "[[parameters('DomainJoinPassword')]" - } - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - + "sku": "win11-25h2-avd-m365" +} +}, +"varImageReference": "[if(equals(parameters('MarketPlaceOrCustomImage'), 'Marketplace'), createObject('publisher', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].publisher, 'offer', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].offer, 'sku', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].sku, 'version', 'latest'), createObject('Id', parameters('GalleryImageId')))]", +"varSecurityProfile": "[if(equals(parameters('SecurityType'), 'Standard'), null(), createObject('securityType', parameters('SecurityType'), 'uefiSettings', createObject('secureBootEnabled', parameters('SecureBootEnabled'), 'vTpmEnabled', parameters('TpmEnabled'))))]", +"varDomainJoinObject": "[if(equals(parameters('IdentityServiceProvider'), 'EntraID'), createObject('DomainType', 'EntraID', 'IntuneJoin', parameters('IntuneEnrollment')), createObject('DomainType', 'ActiveDirectory', 'DomainName', parameters('ADDomainName'), 'DomainJoinUserName', parameters('ADDomainJoinUserName'), 'ADOUPath', parameters('ADOUPath')))]", +"varAzureEnvironments": [ +"AzureCloud", +"AzureUSGovernment", +"AzureChinaCloud" +], +"splitParts": "[split(parameters('HostPoolResourceGroupName'), '-')]", +"rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4], '-', variables('splitParts')[5])]", +"rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", +"varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", +"varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", +"varFunctionAppName": "[concat('AVDReplacer', variables('rgpattern'))]", +"varFunctionAppIdentity": "[if(parameters('UseUserAssignedManagedIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', parameters('UserAssignedManagedIdentityResourceId')), createObject())), createObject('type', 'SystemAssigned'))]" +}, +"resources": [ +{ +"type": "Microsoft.Resources/deployments", +"apiVersion": "2022-09-01", +"name": "deployFunctionApp", +"properties": { +"expressionEvaluationOptions": { +"scope": "inner" +}, +"mode": "Incremental", +"parameters": { +"Location": { +"value": "[parameters('Location')]" +}, +"FunctionAppName": { +"value": "[variables('varFunctionAppName')]" +}, +"EnableMonitoring": { +"value": "[parameters('EnableMonitoring')]" +}, +"UseExistingLAW": { +"value": "[parameters('UseExistingLAW')]" +}, +"LogAnalyticsWorkspaceId": { +"value": "[parameters('LogAnalyticsWorkspaceId')]" +}, +"ReplacementPlanSettings": { +"value": [ { - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2023-09-01", - "name": "[[format('{0}-vNIC', parameters('VMName'))]", - "location": "[[parameters('Location')]", - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "subnet": { - "id": "[[parameters('SubnetID')]" - } - } - } - ], - -"dnsSettings": { - "dnsServers": "[[parameters('DnsServers')]" -}, - "enableAcceleratedNetworking": "[[parameters('AcceleratedNetworking')]" - }, - "tags": "[[parameters('Tags')]" -}, - - { - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2023-09-01", - "name": "[[parameters('VMName')]", - "location": "[[parameters('Location')]", - "zones": "[[variables('varAvailabilityZone')]", - "identity": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", - "properties": { - "osProfile": { - "computerName": "[[parameters('VMName')]", - "adminUsername": "[[parameters('AdminUsername')]", - "adminPassword": "[[parameters('AdminPassword')]" - }, - "hardwareProfile": { - "vmSize": "[[parameters('VMSize')]" - }, - "additionalCapabilities": { - "hibernationEnabled": true - }, - "storageProfile": { - "osDisk": { - "name": "[[format('{0}-OSDisk', parameters('VMName'))]", - "createOption": "FromImage", - "deleteOption": "Delete", - "diskSizeGB": "[[parameters('DiskSizeGB')]", - "managedDisk": { - "storageAccountType": "[[parameters('DiskType')]" - } - }, - "ImageReference": "[[parameters('ImageReference')]" - }, - "securityProfile": "[[parameters('SecurityProfile')]", - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": true - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", - "properties": { - "deleteOption": "Delete" - } - } - ] - }, - "licenseType": "Windows_Client" - }, - "tags": "[[parameters('Tags')]", - "dependsOn": [ - "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" - ] - } - ] - } - } - } - ] -} - }, - "resources": [ - { - "type": "Microsoft.Resources/templateSpecs/versions", - "apiVersion": "2022-02-01", - "name": "[format('{0}/{1}', parameters('Name'), 'deploymentTemplateSpecVersion')]", - "location": "[parameters('Location')]", - "properties": { - "mainTemplate": "[variables('$fxv#0')]" - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/templateSpecs', parameters('Name'))]" - ] - }, - { - "type": "Microsoft.Resources/templateSpecs", - "apiVersion": "2022-02-01", - "name": "[parameters('Name')]", - "location": "[parameters('Location')]", - "properties": { - "description": "Template Spec for deploying VMs through the AVD Replacement Plan", - "displayName": "AVD Replacement Plan Session Host Template" - } - } - ], - "outputs": { - "TemplateSpecResourceId": { - "type": "string", - "value": "[resourceId('Microsoft.Resources/templateSpecs', parameters('Name'))]" - } - } - } - } - }, - { - "condition": "[not(parameters('UseUserAssignedManagedIdentity'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('RBAC-vdiVMContributor-{0}', parameters('TimeStamp'))]", - "subscriptionId": "[subscription().subscriptionId]", - "location": "[resourceGroup().location]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "PrinicpalId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployFunctionApp'), '2022-09-01').outputs.functionAppPrincipalId.value]" - }, - "RoleDefinitionId": { - "value": "a959dbd1-f747-45e3-8ba6-dd80f235f97c" - }, - "Scope": { - "value": "[subscription().id]" - } - }, - "template": { - "$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": "852792330190262115" - } - }, - "parameters": { - "PrinicpalId": { - "type": "string" - }, - "RoleDefinitionId": { - "type": "string" - }, - "Scope": { - "type": "string" - } - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(parameters('PrinicpalId'), parameters('RoleDefinitionId'), parameters('Scope'))]", - "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('RoleDefinitionId'))]", - "principalId": "[parameters('PrinicpalId')]" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', 'deployFunctionApp')]" - ] - }, - { - "condition": "[not(parameters('UseUserAssignedManagedIdentity'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('RBAC-TemplateSpecReader-{0}', parameters('TimeStamp'))]", - "subscriptionId": "[subscription().subscriptionId]", - "location": "[resourceGroup().location]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "PrinicpalId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployFunctionApp'), '2022-09-01').outputs.functionAppPrincipalId.value]" - }, - "RoleDefinitionId": { - "value": "392ae280-861d-42bd-9ea5-08ee6d83b80e" - }, - "Scope": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate'), '2022-09-01').outputs.TemplateSpecResourceId.value]" - } - }, - "template": { - "$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": "852792330190262115" - } - }, - "parameters": { - "PrinicpalId": { - "type": "string" - }, - "RoleDefinitionId": { - "type": "string" - }, - "Scope": { - "type": "string" - } - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(parameters('PrinicpalId'), parameters('RoleDefinitionId'), parameters('Scope'))]", - "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('RoleDefinitionId'))]", - "principalId": "[parameters('PrinicpalId')]" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', 'deployFunctionApp')]", - "[resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate')]" - ] - } - ] +"name": "_HostPoolResourceGroupName", +"value": "[parameters('HostPoolResourceGroupName')]" +}, +{ +"name": "_HostPoolName", +"value": "[parameters('HostPoolName')]" +}, +{ +"name": "_TargetSessionHostCount", +"value": "[parameters('TargetSessionHostCount')]" +}, +{ +"name": "_TargetSessionHostBuffer", +"value": "[parameters('TargetSessionHostBuffer')]" +}, +{ +"name": "_SessionHostNamePrefix", +"value": "[parameters('SessionHostNamePrefix')]" +}, +{ +"name": "_SessionHostNameSeparator", +"value": "[parameters('SessionHostNameSeparator')]" +}, +{ +"name": "_SessionHostTemplate", +"value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate'), '2022-09-01').outputs.TemplateSpecResourceId.value]" +}, +{ +"name": "_SessionHostParameters", +"value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" +}, +{ +"name": "_SubscriptionId", +"value": "[subscription().subscriptionId]" +}, +{ +"name": "_RemoveEntraDevice", +"value": "[equals(parameters('IdentityServiceProvider'), 'EntraID')]" +}, +{ +"name": "_RemoveIntuneDevice", +"value": "[parameters('IntuneEnrollment')]" +}, +{ +"name": "_ClientId", +"value": "[if(parameters('UseUserAssignedManagedIdentity'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').clientId, '')]" +}, +{ +"name": "_TenantId", +"value": "[if(parameters('UseUserAssignedManagedIdentity'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').tenantId, '')]" +}, +{ +"name": "_GraphEnvironmentName", +"value": "[variables('varGraphEnvironmentName')]" +}, +{ +"name": "_AzureEnvironmentName", +"value": "[environment().name]" +}, +{ +"name": "_Tag_IncludeInAutomation", +"value": "[parameters('TagIncludeInAutomation')]" +}, +{ +"name": "_Tag_DeployTimestamp", +"value": "[parameters('TagDeployTimestamp')]" +}, +{ +"name": "_Tag_PendingDrainTimestamp", +"value": "[parameters('TagPendingDrainTimestamp')]" +}, +{ +"name": "_Tag_ScalingPlanExclusionTag", +"value": "[parameters('TagScalingPlanExclusionTag')]" +}, +{ +"name": "_TargetVMAgeDays", +"value": "[parameters('TargetVMAgeDays')]" +}, +{ +"name": "_DrainGracePeriodHours", +"value": "[parameters('DrainGracePeriodHours')]" +}, +{ +"name": "_FixSessionHostTags", +"value": "[parameters('FixSessionHostTags')]" +}, +{ +"name": "_IncludePreExistingSessionHosts", +"value": "[parameters('IncludePreExistingSessionHosts')]" +}, +{ +"name": "_SHRDeploymentPrefix", +"value": "[parameters('SHRDeploymentPrefix')]" +}, +{ +"name": "_SessionHostInstanceNumberPadding", +"value": "[parameters('SessionHostInstanceNumberPadding')]" +}, +{ +"name": "_ReplaceSessionHostOnNewImageVersion", +"value": "[parameters('ReplaceSessionHostOnNewImageVersion')]" +}, +{ +"name": "_ReplaceSessionHostOnNewImageVersionDelayDays", +"value": "[parameters('ReplaceSessionHostOnNewImageVersionDelayDays')]" +}, +{ +"name": "_VMNamesTemplateParameterName", +"value": "[parameters('VMNamesTemplateParameterName')]" +}, +{ +"name": "_SessionHostResourceGroupName", +"value": "[parameters('SessionHostResourceGroupName')]" +} +] +}, +"FunctionAppIdentity": { +"value": "[variables('varFunctionAppIdentity')]" +} +}, +"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": "3734734378229800954" +} +}, +"parameters": { +"Location": { +"type": "string", +"defaultValue": "[resourceGroup().location]", +"metadata": { +"description": "Required: No | Region of the Function App. This does not need to be the same as the location of the Azure Virtual Desktop Host Pool. | Default: Location of the resource group." +} +}, +"EnableMonitoring": { +"type": "bool", +"defaultValue": true +}, +"UseExistingLAW": { +"type": "bool", +"defaultValue": false +}, +"LogAnalyticsWorkspaceId": { +"type": "string", +"defaultValue": "none", +"metadata": { +"description": "Required: Yes | Name of the Log Analytics Workspace used by the Function App Insights." +} +}, +"FunctionAppName": { +"type": "string", +"metadata": { +"description": "Required: Yes | Name of the Function App." +} +}, +"FunctionAppZipUrl": { +"type": "string", +"defaultValue": "https://github.com/Azure/AVDSessionHostReplacer/releases/download/v0.3.1-beta.3/FunctionApp.zip", +"metadata": { +"description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." +} +}, +"AppPlanName": { +"type": "string", +"defaultValue": "Y1", +"metadata": { +"description": "Required: No | App Service Plan Name | Default: Y1 for consumption based plan" +} +}, +"AppPlanTier": { +"type": "string", +"defaultValue": "Dynamic", +"metadata": { +"description": "Required: No | App Service Plan Tier | Default: Dynamic for consumption based plan" +} +}, +"ReplacementPlanSettings": { +"type": "array", +"metadata": { +"description": "Required: Yes | The following settings are mandatory. Rest are optional.\n[\n {\n name: '_HostPoolResourceGroupName'\n value: 'string'\n }\n {\n name: '_HostPoolName'\n value: 'string'\n }\n {\n name: '_RemoveEntraDevice'\n value: 'bool'\n }\n {\n name: '_SessionHostTemplate'\n value: 'string'\n }\n {\n name: '_SessionHostParameters'\n value: 'hashtable'\n }\n {\n name: '_SubscriptionId'\n value: 'string'\n }\n {\n name: '_TargetSessionHostCount'\n value: 'int'\n }\n {\n name: '_SessionHostNamePrefix'\n value: 'string'\n }\n]" +} +}, +"FunctionAppIdentity": { +"type": "object", +"defaultValue": { +"type": "SystemAssigned" +} +} +}, +"variables": { +"rgname": "[toLower(resourceGroup().name)]", +"splitParts": "[split(variables('rgname'), '-')]", +"rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", +"varStorageAccountName": "[concat('st', variables('rgpattern2'))]", +"varLogAnalyticsWorkspaceName": "[concat('law-', parameters('FunctionAppName'))]", +"varAppServicePlanName": "[concat('Asp-', parameters('FunctionAppName'))]", +"varGraphEnvironmentName": "your_value_here" +}, +"resources": [ +{ +"type": "Microsoft.Web/sites/extensions", +"apiVersion": "2023-01-01", +"name": "[format('{0}/{1}', parameters('FunctionAppName'), 'MSDeploy')]", +"properties": { +"packageUri": "[parameters('FunctionAppZipUrl')]" +}, +"dependsOn": [ +"[resourceId('Microsoft.Web/sites', parameters('FunctionAppName'))]" +] +}, +{ +"type": "Microsoft.Storage/storageAccounts", +"apiVersion": "2022-05-01", +"name": "[variables('varStorageAccountName')]", +"location": "[parameters('Location')]", +"kind": "StorageV2", +"sku": { +"name": "Standard_LRS" +}, +"properties": {} +}, +{ +"condition": "[and(parameters('EnableMonitoring'), not(parameters('UseExistingLAW')))]", +"type": "Microsoft.OperationalInsights/workspaces", +"apiVersion": "2023-09-01", +"name": "[variables('varLogAnalyticsWorkspaceName')]", +"location": "[parameters('Location')]", +"properties": { +"sku": { +"name": "PerGB2018" +}, +"retentionInDays": 30 +} +}, +{ +"type": "Microsoft.Web/serverfarms", +"apiVersion": "2022-03-01", +"name": "[variables('varAppServicePlanName')]", +"location": "[parameters('Location')]", +"sku": { +"name": "[parameters('AppPlanName')]", +"tier": "[parameters('AppPlanTier')]" +} +}, +{ +"condition": "[parameters('EnableMonitoring')]", +"type": "Microsoft.Insights/components", +"apiVersion": "2020-02-02", +"name": "[variables('varAppServicePlanName')]", +"location": "[parameters('Location')]", +"kind": "web", +"properties": { +"Application_Type": "web", +"publicNetworkAccessForIngestion": "Enabled", +"publicNetworkAccessForQuery": "Enabled", +"WorkspaceResourceId": "[if(parameters('UseExistingLAW'), parameters('LogAnalyticsWorkspaceId'), resourceId('Microsoft.OperationalInsights/workspaces', variables('varLogAnalyticsWorkspaceName')))]" +}, +"dependsOn": [ +"[resourceId('Microsoft.OperationalInsights/workspaces', variables('varLogAnalyticsWorkspaceName'))]" +] +}, +{ +"type": "Microsoft.Web/sites", +"apiVersion": "2023-01-01", +"name": "[parameters('FunctionAppName')]", +"location": "[parameters('Location')]", +"kind": "functionApp", +"identity": "[parameters('FunctionAppIdentity')]", +"properties": { +"httpsOnly": true, +"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('varAppServicePlanName'))]", +"siteConfig": { +"use32BitWorkerProcess": false, +"powerShellVersion": "7.2", +"netFrameworkVersion": "v6.0", +"appSettings": "[union(createArray(createObject('name', 'FUNCTIONS_EXTENSION_VERSION', 'value', '~4'), createObject('name', 'FUNCTIONS_WORKER_RUNTIME', 'value', 'powershell'), createObject('name', 'AzureWebJobsStorage', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey), createObject('name', 'WEBSITE_CONTENTSHARE', 'value', toLower(parameters('FunctionAppName')))), if(parameters('EnableMonitoring'), createArray(createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey)), createArray()), parameters('ReplacementPlanSettings'))]", +"ftpsState": "Disabled", +"cors": { +"allowedOrigins": [ +"https://portal.azure.com" +] +} +} +}, +"dependsOn": [ +"[resourceId('Microsoft.Insights/components', variables('varAppServicePlanName'))]", +"[resourceId('Microsoft.Web/serverfarms', variables('varAppServicePlanName'))]", +"[resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName'))]" +] +} +], +"outputs": { +"functionAppPrincipalId": { +"type": "string", +"value": "[if(equals(parameters('FunctionAppIdentity').type, 'SystemAssigned'), reference(resourceId('Microsoft.Web/sites', parameters('FunctionAppName')), '2023-01-01', 'full').identity.principalId, '')]" +} +} +} +}, +"dependsOn": [ +"[resourceId('Microsoft.Resources/deployments', 'deployKeyVault')]", +"[resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate')]" +] +}, +{ +"condition": "[not(equals(parameters('IdentityServiceProvider'), 'EntraID'))]", +"type": "Microsoft.Resources/deployments", +"apiVersion": "2022-09-01", +"name": "deployKeyVault", +"properties": { +"expressionEvaluationOptions": { +"scope": "inner" +}, +"mode": "Incremental", +"parameters": { +"Location": { +"value": "[parameters('Location')]" +}, +"KeyVaultName": { +"value": "[concat('kvSHR', variables('rgpattern2'))]" +}, +"DomainJoinPassword": { +"value": "[parameters('ADJoinUserPassword')]" +} +}, +"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": "17584014780822218958" +} +}, +"parameters": { +"Location": { +"type": "string", +"defaultValue": "[resourceGroup().location]" +}, +"KeyVaultName": { +"type": "string" +}, +"DomainJoinPassword": { +"type": "securestring" +} +}, +"resources": [ +{ +"type": "Microsoft.KeyVault/vaults/secrets", +"apiVersion": "2023-07-01", +"name": "[format('{0}/{1}', parameters('KeyVaultName'), 'DomainJoinPassword')]", +"properties": { +"value": "[parameters('DomainJoinPassword')]" +}, +"dependsOn": [ +"[resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]" +] +}, +{ +"type": "Microsoft.KeyVault/vaults", +"apiVersion": "2023-07-01", +"name": "[parameters('KeyVaultName')]", +"location": "[parameters('Location')]", +"properties": { +"sku": { +"family": "A", +"name": "standard" +}, +"tenantId": "[subscription().tenantId]", +"enabledForTemplateDeployment": true, +"enableRbacAuthorization": true +} +} +], +"outputs": { +"keyVaultId": { +"type": "string", +"value": "[resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]" +} +} +} +} +}, +{ +"type": "Microsoft.Resources/deployments", +"apiVersion": "2022-09-01", +"name": "deployStandardSessionHostTemplate", +"properties": { +"expressionEvaluationOptions": { +"scope": "inner" +}, +"mode": "Incremental", +"parameters": { +"Location": { +"value": "[parameters('Location')]" +}, +"Name": { +"value": "[format('{0}-Spec', parameters('HostPoolName'))]" +} +}, +"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": "10208603161915711494" +} +}, +"parameters": { +"Name": { +"type": "string" +}, +"Location": { +"type": "string", +"defaultValue": "[resourceGroup().location]" +} +}, +"variables": { +"$fxv#0": { +"$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": "8994538122455151797" +} +}, +"parameters": { +"Location": { +"type": "string", +"defaultValue": "[[resourceGroup().location]" +}, +"AvailabilityZones": { +"type": "array", +"defaultValue": [] +}, +"VMNames": { +"type": "array" +}, +"VMNamePrefixLength": { +"type": "int" +}, +"VMSize": { +"type": "string" +}, +"SubnetID": { +"type": "string" +}, +"AdminUsername": { +"type": "string" +}, +"AcceleratedNetworking": { +"type": "bool" +}, +"DiskType": { +"type": "string" +}, +"Tags": { +"type": "object", +"defaultValue": {} +}, +"ImageReference": { +"type": "object" +}, +"SecurityProfile": { +"type": "object", +"defaultValue": {} +}, +"HostPoolName": { +"type": "string" +}, +"HostPoolToken": { +"type": "securestring" +}, +"WVDArtifactsURL": { +"type": "string", +"defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" +}, +"DomainJoinObject": { +"type": "object", +"defaultValue": {} +}, +"DomainJoinPassword": { +"type": "securestring", +"defaultValue": "" +} +}, +"resources": [ +{ +"[string('copy')]": { +"name": "deploySessionHosts", +"count": "[[length(parameters('VMNames'))]" +}, +"type": "Microsoft.Resources/deployments", +"apiVersion": "2022-09-01", +"name": "[[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", +"properties": { +"expressionEvaluationOptions": { +"scope": "inner" +}, +"mode": "Incremental", +"parameters": { +"AcceleratedNetworking": { +"value": "[[parameters('AcceleratedNetworking')]" +}, +"AdminUsername": { +"value": "[[parameters('AdminUsername')]" +}, +"HostPoolName": { +"value": "[[parameters('HostPoolName')]" +}, +"HostPoolToken": { +"value": "[[parameters('HostPoolToken')]" +}, +"ImageReference": { +"value": "[[parameters('ImageReference')]" +}, +"SecurityProfile": { +"value": "[[parameters('SecurityProfile')]" +}, +"SubnetID": { +"value": "[[parameters('SubnetID')]" +}, +"VMName": { +"value": "[[parameters('VMNames')[copyIndex()]]" +}, +"VMNamePrefixLength": { +"value": "[[parameters('VMNamePrefixLength')]" +}, +"VMSize": { +"value": "[[parameters('VMSize')]" +}, +"DiskType": { +"value": "[[parameters('DiskType')]" +}, +"WVDArtifactsURL": { +"value": "[[parameters('WVDArtifactsURL')]" +}, +"DomainJoinObject": { +"value": "[[parameters('DomainJoinObject')]" +}, +"DomainJoinPassword": { +"value": "[[parameters('DomainJoinPassword')]" +}, +"Location": { +"value": "[[parameters('Location')]" +}, +"AvailabilityZones": { +"value": "[[parameters('AvailabilityZones')]" +}, +"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": "7267962610869920549" +} +}, +"parameters": { +"VMName": { +"type": "string" +}, +"VMNamePrefixLength": { +"type": "int" +}, +"VMSize": { +"type": "string" +}, +"DiskType": { +"type": "string" +}, +"Location": { +"type": "string", +"defaultValue": "[[resourceGroup().location]" +}, +"AvailabilityZones": { +"type": "array", +"defaultValue": [] +}, +"SubnetID": { +"type": "string" +}, +"AdminUsername": { +"type": "string" +}, +"AdminPassword": { +"type": "securestring", +"defaultValue": "[[newGuid()]" +}, +"AcceleratedNetworking": { +"type": "bool" +}, +"Tags": { +"type": "object", +"defaultValue": {} +}, +"ImageReference": { +"type": "object" +}, +"SecurityProfile": { +"type": "object" +}, +"HostPoolName": { +"type": "string" +}, +"HostPoolToken": { +"type": "securestring" +}, +"WVDArtifactsURL": { +"type": "string" +}, +"DomainJoinObject": { +"type": "object", +"defaultValue": {} +}, +"DomainJoinPassword": { +"type": "securestring", +"defaultValue": "" +} +}, +"variables": { +"varRequireNvidiaGPU": "[[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", +"varVMNumber": "[[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", +"varAvailabilityZone": "[[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" +}, +"resources": [ +{ +"type": "Microsoft.Compute/virtualMachines/extensions", +"apiVersion": "2023-09-01", +"name": "[[format('{0}/{1}', parameters('VMName'), 'deployIntegrityMonitoring')]", +"location": "[[parameters('Location')]", +"properties": { +"publisher": "Microsoft.Azure.Security.WindowsAttestation", +"type": "GuestAttestation", +"typeHandlerVersion": "1.0", +"autoUpgradeMinorVersion": true, +"settings": { +"AttestationConfig": { +"MaaSettings": { +"maaEndpoint": "", +"maaTenantName": "Guest Attestation" +}, +"AscSettings": { +"ascReportingEndpoint": "", +"ascReportingFrequency": "" +}, +"useCustomToken": "false", +"disableAlerts": "false" +} +} +}, +"dependsOn": [ +"[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" +] +}, +{ +"condition": "[[variables('varRequireNvidiaGPU')]", +"type": "Microsoft.Compute/virtualMachines/extensions", +"apiVersion": "2023-09-01", +"name": "[[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", +"location": "[[parameters('Location')]", +"properties": { +"publisher": "Microsoft.HpcCompute", +"type": "NvidiaGpuDriverWindows", +"typeHandlerVersion": "1.6", +"autoUpgradeMinorVersion": true +}, +"dependsOn": [ +"[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployIntegrityMonitoring')]", +"[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" +] +}, +{ +"condition": "[[not(equals(parameters('HostPoolName'), ''))]", +"type": "Microsoft.Compute/virtualMachines/extensions", +"apiVersion": "2023-09-01", +"name": "[[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", +"location": "[[parameters('Location')]", +"properties": { +"publisher": "Microsoft.PowerShell", +"type": "DSC", +"typeHandlerVersion": "2.77", +"autoUpgradeMinorVersion": true, +"settings": { +"modulesUrl": "[[parameters('WVDArtifactsURL')]", +"configurationFunction": "Configuration.ps1\\AddSessionHost", +"properties": { +"hostPoolName": "[[parameters('HostPoolName')]", +"registrationInfoToken": "[[parameters('HostPoolToken')]", +"aadJoin": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", +"useAgentDownloadEndpoint": true, +"mdmId": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" +} +} +}, +"dependsOn": [ +"[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", +"[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" +] +}, +{ +"condition": "[[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", +"type": "Microsoft.Compute/virtualMachines/extensions", +"apiVersion": "2023-09-01", +"name": "[[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", +"location": "[[parameters('Location')]", +"properties": { +"publisher": "Microsoft.Azure.ActiveDirectory", +"type": "AADLoginForWindows", +"typeHandlerVersion": "2.0", +"autoUpgradeMinorVersion": true, +"settings": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" +}, +"dependsOn": [ +"[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", +"[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" +] +}, +{ +"condition": "[[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", +"type": "Microsoft.Compute/virtualMachines/extensions", +"apiVersion": "2023-09-01", +"name": "[[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", +"location": "[[parameters('Location')]", +"properties": { +"publisher": "Microsoft.Compute", +"type": "JSonADDomainExtension", +"typeHandlerVersion": "1.3", +"autoUpgradeMinorVersion": true, +"settings": { +"Name": "[[parameters('DomainJoinObject').DomainName]", +"OUPath": "[[parameters('DomainJoinObject').ADOUPath]", +"User": "[[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", +"Restart": "true", +"Options": 3 +}, +"protectedSettings": { +"Password": "[[parameters('DomainJoinPassword')]" +} +}, +"dependsOn": [ +"[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", +"[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" +] +}, +{ +"type": "Microsoft.Network/networkInterfaces", +"apiVersion": "2023-09-01", +"name": "[[format('{0}-vNIC', parameters('VMName'))]", +"location": "[[parameters('Location')]", +"properties": { +"ipConfigurations": [ +{ +"name": "ipconfig1", +"properties": { +"subnet": { +"id": "[[parameters('SubnetID')]" +} +} +} +], +"enableAcceleratedNetworking": "[[parameters('AcceleratedNetworking')]" +}, +"tags": "[[parameters('Tags')]" +}, +{ +"type": "Microsoft.Compute/virtualMachines", +"apiVersion": "2023-09-01", +"name": "[[parameters('VMName')]", +"location": "[[parameters('Location')]", +"zones": "[[variables('varAvailabilityZone')]", +"identity": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", +"properties": { +"osProfile": { +"computerName": "[[parameters('VMName')]", +"adminUsername": "[[parameters('AdminUsername')]", +"adminPassword": "[[parameters('AdminPassword')]" +}, +"hardwareProfile": { +"vmSize": "[[parameters('VMSize')]" +}, +"storageProfile": { +"osDisk": { +"name": "[[format('{0}-OSDisk', parameters('VMName'))]", +"createOption": "FromImage", +"deleteOption": "Delete", +"managedDisk": { +"storageAccountType": "[[parameters('DiskType')]" +} +}, +"ImageReference": "[[parameters('ImageReference')]" +}, +"securityProfile": "[[parameters('SecurityProfile')]", +"diagnosticsProfile": { +"bootDiagnostics": { +"enabled": true +} +}, +"networkProfile": { +"networkInterfaces": [ +{ +"id": "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", +"properties": { +"deleteOption": "Delete" +} +} +] +}, +"licenseType": "Windows_Client" +}, +"tags": "[[parameters('Tags')]", +"dependsOn": [ +"[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" +] +} +] +} +} +} +] +} +}, +"resources": [ +{ +"type": "Microsoft.Resources/templateSpecs/versions", +"apiVersion": "2022-02-01", +"name": "[format('{0}/{1}', parameters('Name'), 'deploymentTemplateSpecVersion')]", +"location": "[parameters('Location')]", +"properties": { +"mainTemplate": "[variables('$fxv#0')]" +}, +"dependsOn": [ +"[resourceId('Microsoft.Resources/templateSpecs', parameters('Name'))]" +] +}, +{ +"type": "Microsoft.Resources/templateSpecs", +"apiVersion": "2022-02-01", +"name": "[parameters('Name')]", +"location": "[parameters('Location')]", +"properties": { +"description": "Template Spec for deploying VMs through the AVD Replacement Plan", +"displayName": "AVD Replacement Plan Session Host Template" +} +} +], +"outputs": { +"TemplateSpecResourceId": { +"type": "string", +"value": "[resourceId('Microsoft.Resources/templateSpecs', parameters('Name'))]" +} +} +} +} +}, +{ +"condition": "[not(parameters('UseUserAssignedManagedIdentity'))]", +"type": "Microsoft.Resources/deployments", +"apiVersion": "2022-09-01", +"name": "[format('RBAC-vdiVMContributor-{0}', parameters('TimeStamp'))]", +"subscriptionId": "[subscription().subscriptionId]", +"location": "[resourceGroup().location]", +"properties": { +"expressionEvaluationOptions": { +"scope": "inner" +}, +"mode": "Incremental", +"parameters": { +"PrinicpalId": { +"value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployFunctionApp'), '2022-09-01').outputs.functionAppPrincipalId.value]" +}, +"RoleDefinitionId": { +"value": "a959dbd1-f747-45e3-8ba6-dd80f235f97c" +}, +"Scope": { +"value": "[subscription().id]" +} +}, +"template": { +"$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": "852792330190262115" +} +}, +"parameters": { +"PrinicpalId": { +"type": "string" +}, +"RoleDefinitionId": { +"type": "string" +}, +"Scope": { +"type": "string" +} +}, +"resources": [ +{ +"type": "Microsoft.Authorization/roleAssignments", +"apiVersion": "2022-04-01", +"name": "[guid(parameters('PrinicpalId'), parameters('RoleDefinitionId'), parameters('Scope'))]", +"properties": { +"roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('RoleDefinitionId'))]", +"principalId": "[parameters('PrinicpalId')]" +} +} +] +} +}, +"dependsOn": [ +"[resourceId('Microsoft.Resources/deployments', 'deployFunctionApp')]" +] +}, +{ +"condition": "[not(parameters('UseUserAssignedManagedIdentity'))]", +"type": "Microsoft.Resources/deployments", +"apiVersion": "2022-09-01", +"name": "[format('RBAC-TemplateSpecReader-{0}', parameters('TimeStamp'))]", +"subscriptionId": "[subscription().subscriptionId]", +"location": "[resourceGroup().location]", +"properties": { +"expressionEvaluationOptions": { +"scope": "inner" +}, +"mode": "Incremental", +"parameters": { +"PrinicpalId": { +"value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployFunctionApp'), '2022-09-01').outputs.functionAppPrincipalId.value]" +}, +"RoleDefinitionId": { +"value": "392ae280-861d-42bd-9ea5-08ee6d83b80e" +}, +"Scope": { +"value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate'), '2022-09-01').outputs.TemplateSpecResourceId.value]" +} +}, +"template": { +"$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": "852792330190262115" +} +}, +"parameters": { +"PrinicpalId": { +"type": "string" +}, +"RoleDefinitionId": { +"type": "string" +}, +"Scope": { +"type": "string" +} +}, +"resources": [ +{ +"type": "Microsoft.Authorization/roleAssignments", +"apiVersion": "2022-04-01", +"name": "[guid(parameters('PrinicpalId'), parameters('RoleDefinitionId'), parameters('Scope'))]", +"properties": { +"roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('RoleDefinitionId'))]", +"principalId": "[parameters('PrinicpalId')]" +} +} +] +} +}, +"dependsOn": [ +"[resourceId('Microsoft.Resources/deployments', 'deployFunctionApp')]", +"[resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate')]" +] +} +] } From 1cc568885cc087f1ff8a39dedd5dbed20192bc1a Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:33:58 +0100 Subject: [PATCH 61/83] Update AVD session host configurations --- deploy/arm/DeployAVDSessionHostReplacer.json | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 00fa674..9daa49f 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -82,8 +82,7 @@ "2022-datacenter-core-smalldisk-g2", "win11-21h2-avd-m365", "win11-23h2-avd", - "win11-23h2-avd-m365", - "win11-25h2-avd-m365", +"win11-23h2-avd-m365", "win11-22h2-avd", "2022-datacenter-g2", "win10-22h2-avd-g2" @@ -385,12 +384,10 @@ "offer": "windows-11", "sku": "win11-23h2-avd" }, - "win11-23h2-avd-m365": { - "win11-25h2-avd-m365": { +"win11-23h2-avd-m365": { "publisher": "MicrosoftWindowsDesktop", "offer": "office-365", - "sku": "win11-23h2-avd-m365" - "sku": "win11-25h2-avd-m365" +"sku": "win11-23h2-avd-m365" } }, "varImageReference": "[if(equals(parameters('MarketPlaceOrCustomImage'), 'Marketplace'), createObject('publisher', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].publisher, 'offer', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].offer, 'sku', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].sku, 'version', 'latest'), createObject('Id', parameters('GalleryImageId')))]", @@ -636,7 +633,8 @@ "rgname": "[toLower(resourceGroup().name)]", "splitParts": "[split(variables('rgname'), '-')]", "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", -"varStorageAccountName": "[concat('st', variables('rgpattern2'))]", + "varStorageAccountName": "[concat('stavdrpfunc', variables('rgpattern2'))]", + "varStorageAccountName": "[concat('st', variables('rgpattern2'))]", "varLogAnalyticsWorkspaceName": "[concat('law-', parameters('FunctionAppName'))]", "varAppServicePlanName": "[concat('Asp-', parameters('FunctionAppName'))]", "varGraphEnvironmentName": "your_value_here" From 86fcd43928c96c26ace27309f7cb5702cf516c67 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:35:10 +0100 Subject: [PATCH 62/83] Change greeting from 'Hello' to 'Goodbye' --- deploy/arm/DeployAVDSessionHostReplacer.json | 2859 +++++++++--------- 1 file changed, 1429 insertions(+), 1430 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 9daa49f..24c542f 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -1,1433 +1,1432 @@ { -"$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": "16749693425937561368" -} -}, -"parameters": { -"Location": { -"type": "string", -"defaultValue": "[resourceGroup().location]", -"metadata": { -"description": "Required: No | Region of the Function App. This does not need to be the same as the location of the Azure Virtual Desktop Host Pool. | Default: Location of the resource group." -} -}, -"EnableMonitoring": { -"type": "bool", -"defaultValue": true -}, -"UseExistingLAW": { -"type": "bool", -"defaultValue": false -}, -"LogAnalyticsWorkspaceId": { -"type": "string", -"defaultValue": "none", -"metadata": { -"description": "Required: Yes | Name of the Log Analytics Workspace used by the Function App Insights." -} -}, -"UseStandardTemplate": { -"type": "bool", -"defaultValue": true -}, -"SessionHostsRegion": { -"type": "string", -"defaultValue": "" -}, -"AvailabilityZones": { -"type": "array", -"defaultValue": [] -}, -"SessionHostSize": { -"type": "string", -"defaultValue": "" -}, -"AcceleratedNetworking": { -"type": "bool", -"defaultValue": false -}, -"SessionHostDiskType": { -"type": "string", -"defaultValue": "Premium_LRS", -"allowedValues": [ -"Standard_LRS", -"StandardSSD_LRS", -"Premium_LRS" -] -}, -"MarketPlaceOrCustomImage": { -"type": "string", -"defaultValue": "Marketplace", -"allowedValues": [ -"Marketplace", -"Gallery" -] -}, -"MarketPlaceImage": { -"type": "string", -"defaultValue": "win11-23h2-avd-m365", -"allowedValues": [ -"2022-datacenter-smalldisk-g2", -"win10-21h2-avd", -"2022-datacenter-core-g2", -"win10-22h2-avd-m365-g2", -"win11-21h2-avd", -"win10-21h2-avd-m365", -"win11-22h2-avd-m365", -"2022-datacenter-core-smalldisk-g2", -"win11-21h2-avd-m365", -"win11-23h2-avd", -"win11-23h2-avd-m365", -"win11-22h2-avd", -"2022-datacenter-g2", -"win10-22h2-avd-g2" -] -}, -"GalleryImageId": { -"type": "string", -"defaultValue": "" -}, -"SecurityType": { -"type": "string", -"defaultValue": "TrustedLaunch", -"allowedValues": [ -"Standard", -"TrustedLaunch", -"ConfidentialVM" -] -}, -"SecureBootEnabled": { -"type": "bool", -"defaultValue": true -}, -"TpmEnabled": { -"type": "bool", -"defaultValue": true -}, -"SubnetId": { -"type": "string", -"defaultValue": "" -}, -"IdentityServiceProvider": { -"type": "string", -"defaultValue": "EntraID", -"allowedValues": [ -"EntraID", -"ActiveDirectory", -"EntraDS" -] -}, -"IntuneEnrollment": { -"type": "bool", -"defaultValue": false -}, -"ADDomainName": { -"type": "string", -"defaultValue": "" -}, -"ADDomainJoinUserName": { -"type": "string", -"defaultValue": "" -}, -"ADJoinUserPassword": { -"type": "securestring", -"defaultValue": "" -}, -"ADOUPath": { -"type": "string", -"defaultValue": "" -}, -"LocalAdminUsername": { -"type": "string", -"defaultValue": "" -}, -"CustomTemplateSpecResourceId": { -"type": "string", -"defaultValue": "" -}, -"VMNamesTemplateParameterName": { -"type": "string", -"defaultValue": "VMNames", -"metadata": { -"description": "Required: No | The name of the parameter in the template that specifies the VM Names array." -} -}, -"CustomTemplateSpecParameters": { -"type": "object", -"defaultValue": {} -}, -"HostPoolResourceGroupName": { -"type": "string", -"defaultValue": "[resourceGroup().name]", -"metadata": { -"description": "Required: No | Name of the resource group containing the Azure Virtual Desktop Host Pool. | Default: The resource group of the Function App." -} -}, -"HostPoolName": { -"type": "string", -"metadata": { -"description": "Required: Yes | Name of the Azure Virtual Desktop Host Pool." -} -}, -"SessionHostNamePrefix": { -"type": "string", -"maxLength": 12, -"metadata": { -"description": "Required: Yes | Prefix used for the name of the session hosts." -} -}, -"SessionHostNameSeparator": { -"type": "string", -"defaultValue": "-", -"maxLength": 1, -"metadata": { -"description": "Required: NO | Separator between prefix and number. | Default: -" -} -}, -"TargetSessionHostCount": { -"type": "int", -"minValue": 0, -"metadata": { -"description": "Required: Yes | Number of session hosts to maintain in the host pool." -} -}, -"TargetSessionHostBuffer": { -"type": "int", -"minValue": 1, -"metadata": { -"description": "Required: Yes | The maximum number of session hosts to add during a replacement process" -} -}, -"UseGovDodGraph": { -"type": "bool", -"defaultValue": false, -"metadata": { -"description": "Required: No | Switches to using the US Governmment DoD graph endpoints for the Function App. | Default: false" -} -}, -"UseUserAssignedManagedIdentity": { -"type": "bool", -"defaultValue": false, -"metadata": { -"description": "Required: No | Resource Id of the User Assigned Managed Identity to use for the Function App. | Default: System Identity" -} -}, -"UserAssignedManagedIdentityResourceId": { -"type": "string", -"defaultValue": "" -}, -"TagIncludeInAutomation": { -"type": "string", -"defaultValue": "IncludeInAutoReplace", -"metadata": { -"description": "Required: No | Tag name used to indicate that a session host should be included in the automatic replacement process. | Default: IncludeInAutoReplace." -} -}, -"TagDeployTimestamp": { -"type": "string", -"defaultValue": "AutoReplaceDeployTimestamp", -"metadata": { -"description": "Required: No | Tag name used to indicate the timestamp of the last deployment of a session host. | Default: AutoReplaceDeployTimestamp." -} -}, -"TagPendingDrainTimestamp": { -"type": "string", -"defaultValue": "AutoReplacePendingDrainTimestamp", -"metadata": { -"description": "Required: No | Tag name used to indicate drain timestamp of session host pending deletion. | Default: AutoReplacePendingDrainTimestamp." -} -}, -"TagScalingPlanExclusionTag": { -"type": "string", -"defaultValue": "ScalingPlanExclusion", -"metadata": { -"description": "Required: No | Tag name used to exclude session host from Scaling Plan activities. | Default: ScalingPlanExclusion" -} -}, -"TargetVMAgeDays": { -"type": "int", -"defaultValue": 45, -"metadata": { -"description": "Required: No | Target age of session hosts in days. | Default: 45 days." -} -}, -"DrainGracePeriodHours": { -"type": "int", -"defaultValue": 24, -"metadata": { -"description": "Required: No | Grace period in hours for session hosts to drain before deletion. | Default: 24 hours." -} -}, -"FixSessionHostTags": { -"type": "bool", -"defaultValue": true, -"metadata": { -"description": "Required: No | If true, will apply tags for Include In Auto Replace and Deployment Timestamp to existing session hosts. This will not enable automatic deletion of existing session hosts. | Default: True." -} -}, -"IncludePreExistingSessionHosts": { -"type": "bool", -"defaultValue": false, -"metadata": { -"description": "Required: No | When enabled, the Session Host Replacer will automatically consider pre-existing VMs for replacement if they meet the criteria | Default: False." -} -}, -"SHRDeploymentPrefix": { -"type": "string", -"defaultValue": "AVDSessionHostReplacer", -"metadata": { -"description": "Required: No | Prefix used for the deployment name of the session hosts. | Default: AVDSessionHostReplacer" -} -}, -"SessionHostInstanceNumberPadding": { -"type": "int", -"defaultValue": 2, -"metadata": { -"description": "Required: No | Number of digits to use for the instance number of the session hosts (eg. AVDVM-01). | Default: 2" -} -}, -"ReplaceSessionHostOnNewImageVersion": { -"type": "bool", -"defaultValue": true, -"metadata": { -"description": "Required: No | If true, will replace session hosts when a new image version is detected. | Default: true" -} -}, -"ReplaceSessionHostOnNewImageVersionDelayDays": { -"type": "int", -"defaultValue": 0, -"metadata": { -"description": "Required: No | Delay in days before replacing session hosts when a new image version is detected. | Default: 0 (no delay)." -} -}, -"SessionHostResourceGroupName": { -"type": "string", -"defaultValue": "", -"metadata": { -"description": "Required: No | Leave this empty to deploy to same resource group as the host pool." -} -}, -"TimeStamp": { -"type": "string", -"defaultValue": "[utcNow()]" -} -}, -"variables": { -"varMarketPlaceImages": { -"win10-21h2-avd": { -"publisher": "MicrosoftWindowsDesktop", -"offer": "windows-10", -"sku": "win10-21h2-avd" -}, -"win10-21h2-avd-g2": { -"publisher": "MicrosoftWindowsDesktop", -"offer": "windows-10", -"sku": "win10-21h2-avd-g2" -}, -"win10-21h2-avd-m365": { -"publisher": "MicrosoftWindowsDesktop", -"offer": "office-365", -"sku": "win10-21h2-avd-m365" -}, -"win10-21h2-avd-m365-g2": { -"publisher": "MicrosoftWindowsDesktop", -"offer": "office-365", -"sku": "win10-21h2-avd-m365-g2" -}, -"win10-22h2-avd": { -"publisher": "MicrosoftWindowsDesktop", -"offer": "windows-10", -"sku": "win10-22h2-avd" -}, -"win10-22h2-avd-g2": { -"publisher": "MicrosoftWindowsDesktop", -"offer": "windows-10", -"sku": "win10-22h2-avd-g2" -}, -"win10-22h2-avd-m365": { -"publisher": "MicrosoftWindowsDesktop", -"offer": "office-365", -"sku": "win10-22h2-avd-m365" -}, -"win10-22h2-avd-m365-g2": { -"publisher": "MicrosoftWindowsDesktop", -"offer": "office-365", -"sku": "win10-22h2-avd-m365-g2" -}, -"win11-21h2-avd": { -"publisher": "MicrosoftWindowsDesktop", -"offer": "windows-11", -"sku": "win11-21h2-avd" -}, -"win11-21h2-avd-m365": { -"publisher": "MicrosoftWindowsDesktop", -"offer": "office-365", -"sku": "win11-21h2-avd-m365" -}, -"win11-22h2-avd": { -"publisher": "MicrosoftWindowsDesktop", -"offer": "windows-11", -"sku": "win11-22h2-avd" -}, -"win11-22h2-avd-m365": { -"publisher": "MicrosoftWindowsDesktop", -"offer": "office-365", -"sku": "win11-22h2-avd-m365" -}, -"win11-23h2-avd": { -"publisher": "MicrosoftWindowsDesktop", -"offer": "windows-11", -"sku": "win11-23h2-avd" -}, -"win11-23h2-avd-m365": { -"publisher": "MicrosoftWindowsDesktop", -"offer": "office-365", -"sku": "win11-23h2-avd-m365" -} -}, -"varImageReference": "[if(equals(parameters('MarketPlaceOrCustomImage'), 'Marketplace'), createObject('publisher', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].publisher, 'offer', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].offer, 'sku', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].sku, 'version', 'latest'), createObject('Id', parameters('GalleryImageId')))]", -"varSecurityProfile": "[if(equals(parameters('SecurityType'), 'Standard'), null(), createObject('securityType', parameters('SecurityType'), 'uefiSettings', createObject('secureBootEnabled', parameters('SecureBootEnabled'), 'vTpmEnabled', parameters('TpmEnabled'))))]", -"varDomainJoinObject": "[if(equals(parameters('IdentityServiceProvider'), 'EntraID'), createObject('DomainType', 'EntraID', 'IntuneJoin', parameters('IntuneEnrollment')), createObject('DomainType', 'ActiveDirectory', 'DomainName', parameters('ADDomainName'), 'DomainJoinUserName', parameters('ADDomainJoinUserName'), 'ADOUPath', parameters('ADOUPath')))]", -"varAzureEnvironments": [ -"AzureCloud", -"AzureUSGovernment", -"AzureChinaCloud" -], -"splitParts": "[split(parameters('HostPoolResourceGroupName'), '-')]", -"rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4], '-', variables('splitParts')[5])]", -"rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", -"varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", -"varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", -"varFunctionAppName": "[concat('AVDReplacer', variables('rgpattern'))]", -"varFunctionAppIdentity": "[if(parameters('UseUserAssignedManagedIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', parameters('UserAssignedManagedIdentityResourceId')), createObject())), createObject('type', 'SystemAssigned'))]" -}, -"resources": [ -{ -"type": "Microsoft.Resources/deployments", -"apiVersion": "2022-09-01", -"name": "deployFunctionApp", -"properties": { -"expressionEvaluationOptions": { -"scope": "inner" -}, -"mode": "Incremental", -"parameters": { -"Location": { -"value": "[parameters('Location')]" -}, -"FunctionAppName": { -"value": "[variables('varFunctionAppName')]" -}, -"EnableMonitoring": { -"value": "[parameters('EnableMonitoring')]" -}, -"UseExistingLAW": { -"value": "[parameters('UseExistingLAW')]" -}, -"LogAnalyticsWorkspaceId": { -"value": "[parameters('LogAnalyticsWorkspaceId')]" -}, -"ReplacementPlanSettings": { -"value": [ -{ -"name": "_HostPoolResourceGroupName", -"value": "[parameters('HostPoolResourceGroupName')]" -}, -{ -"name": "_HostPoolName", -"value": "[parameters('HostPoolName')]" -}, -{ -"name": "_TargetSessionHostCount", -"value": "[parameters('TargetSessionHostCount')]" -}, -{ -"name": "_TargetSessionHostBuffer", -"value": "[parameters('TargetSessionHostBuffer')]" -}, -{ -"name": "_SessionHostNamePrefix", -"value": "[parameters('SessionHostNamePrefix')]" -}, -{ -"name": "_SessionHostNameSeparator", -"value": "[parameters('SessionHostNameSeparator')]" -}, -{ -"name": "_SessionHostTemplate", -"value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate'), '2022-09-01').outputs.TemplateSpecResourceId.value]" -}, -{ -"name": "_SessionHostParameters", -"value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" -}, -{ -"name": "_SubscriptionId", -"value": "[subscription().subscriptionId]" -}, -{ -"name": "_RemoveEntraDevice", -"value": "[equals(parameters('IdentityServiceProvider'), 'EntraID')]" -}, -{ -"name": "_RemoveIntuneDevice", -"value": "[parameters('IntuneEnrollment')]" -}, -{ -"name": "_ClientId", -"value": "[if(parameters('UseUserAssignedManagedIdentity'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').clientId, '')]" -}, -{ -"name": "_TenantId", -"value": "[if(parameters('UseUserAssignedManagedIdentity'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').tenantId, '')]" -}, -{ -"name": "_GraphEnvironmentName", -"value": "[variables('varGraphEnvironmentName')]" -}, -{ -"name": "_AzureEnvironmentName", -"value": "[environment().name]" -}, -{ -"name": "_Tag_IncludeInAutomation", -"value": "[parameters('TagIncludeInAutomation')]" -}, -{ -"name": "_Tag_DeployTimestamp", -"value": "[parameters('TagDeployTimestamp')]" -}, -{ -"name": "_Tag_PendingDrainTimestamp", -"value": "[parameters('TagPendingDrainTimestamp')]" -}, -{ -"name": "_Tag_ScalingPlanExclusionTag", -"value": "[parameters('TagScalingPlanExclusionTag')]" -}, -{ -"name": "_TargetVMAgeDays", -"value": "[parameters('TargetVMAgeDays')]" -}, -{ -"name": "_DrainGracePeriodHours", -"value": "[parameters('DrainGracePeriodHours')]" -}, -{ -"name": "_FixSessionHostTags", -"value": "[parameters('FixSessionHostTags')]" -}, -{ -"name": "_IncludePreExistingSessionHosts", -"value": "[parameters('IncludePreExistingSessionHosts')]" -}, -{ -"name": "_SHRDeploymentPrefix", -"value": "[parameters('SHRDeploymentPrefix')]" -}, -{ -"name": "_SessionHostInstanceNumberPadding", -"value": "[parameters('SessionHostInstanceNumberPadding')]" -}, -{ -"name": "_ReplaceSessionHostOnNewImageVersion", -"value": "[parameters('ReplaceSessionHostOnNewImageVersion')]" -}, -{ -"name": "_ReplaceSessionHostOnNewImageVersionDelayDays", -"value": "[parameters('ReplaceSessionHostOnNewImageVersionDelayDays')]" -}, -{ -"name": "_VMNamesTemplateParameterName", -"value": "[parameters('VMNamesTemplateParameterName')]" -}, -{ -"name": "_SessionHostResourceGroupName", -"value": "[parameters('SessionHostResourceGroupName')]" -} -] -}, -"FunctionAppIdentity": { -"value": "[variables('varFunctionAppIdentity')]" -} -}, -"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": "3734734378229800954" -} -}, -"parameters": { -"Location": { -"type": "string", -"defaultValue": "[resourceGroup().location]", -"metadata": { -"description": "Required: No | Region of the Function App. This does not need to be the same as the location of the Azure Virtual Desktop Host Pool. | Default: Location of the resource group." -} -}, -"EnableMonitoring": { -"type": "bool", -"defaultValue": true -}, -"UseExistingLAW": { -"type": "bool", -"defaultValue": false -}, -"LogAnalyticsWorkspaceId": { -"type": "string", -"defaultValue": "none", -"metadata": { -"description": "Required: Yes | Name of the Log Analytics Workspace used by the Function App Insights." -} -}, -"FunctionAppName": { -"type": "string", -"metadata": { -"description": "Required: Yes | Name of the Function App." -} -}, -"FunctionAppZipUrl": { -"type": "string", -"defaultValue": "https://github.com/Azure/AVDSessionHostReplacer/releases/download/v0.3.1-beta.3/FunctionApp.zip", -"metadata": { -"description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." -} -}, -"AppPlanName": { -"type": "string", -"defaultValue": "Y1", -"metadata": { -"description": "Required: No | App Service Plan Name | Default: Y1 for consumption based plan" -} -}, -"AppPlanTier": { -"type": "string", -"defaultValue": "Dynamic", -"metadata": { -"description": "Required: No | App Service Plan Tier | Default: Dynamic for consumption based plan" -} -}, -"ReplacementPlanSettings": { -"type": "array", -"metadata": { -"description": "Required: Yes | The following settings are mandatory. Rest are optional.\n[\n {\n name: '_HostPoolResourceGroupName'\n value: 'string'\n }\n {\n name: '_HostPoolName'\n value: 'string'\n }\n {\n name: '_RemoveEntraDevice'\n value: 'bool'\n }\n {\n name: '_SessionHostTemplate'\n value: 'string'\n }\n {\n name: '_SessionHostParameters'\n value: 'hashtable'\n }\n {\n name: '_SubscriptionId'\n value: 'string'\n }\n {\n name: '_TargetSessionHostCount'\n value: 'int'\n }\n {\n name: '_SessionHostNamePrefix'\n value: 'string'\n }\n]" -} -}, -"FunctionAppIdentity": { -"type": "object", -"defaultValue": { -"type": "SystemAssigned" -} -} -}, -"variables": { -"rgname": "[toLower(resourceGroup().name)]", -"splitParts": "[split(variables('rgname'), '-')]", -"rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", - "varStorageAccountName": "[concat('stavdrpfunc', variables('rgpattern2'))]", + "$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": "16749693425937561368" + } + }, + "parameters": { + "Location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Required: No | Region of the Function App. This does not need to be the same as the location of the Azure Virtual Desktop Host Pool. | Default: Location of the resource group." + } + }, + "EnableMonitoring": { + "type": "bool", + "defaultValue": true + }, + "UseExistingLAW": { + "type": "bool", + "defaultValue": false + }, + "LogAnalyticsWorkspaceId": { + "type": "string", + "defaultValue": "none", + "metadata": { + "description": "Required: Yes | Name of the Log Analytics Workspace used by the Function App Insights." + } + }, + "UseStandardTemplate": { + "type": "bool", + "defaultValue": true + }, + "SessionHostsRegion": { + "type": "string", + "defaultValue": "" + }, + "AvailabilityZones": { + "type": "array", + "defaultValue": [] + }, + "SessionHostSize": { + "type": "string", + "defaultValue": "" + }, + "AcceleratedNetworking": { + "type": "bool", + "defaultValue": false + }, + "SessionHostDiskType": { + "type": "string", + "defaultValue": "Premium_LRS", + "allowedValues": [ + "Standard_LRS", + "StandardSSD_LRS", + "Premium_LRS" + ] + }, + "MarketPlaceOrCustomImage": { + "type": "string", + "defaultValue": "Marketplace", + "allowedValues": [ + "Marketplace", + "Gallery" + ] + }, + "MarketPlaceImage": { + "type": "string", + "defaultValue": "win11-23h2-avd-m365", + "allowedValues": [ + "2022-datacenter-smalldisk-g2", + "win10-21h2-avd", + "2022-datacenter-core-g2", + "win10-22h2-avd-m365-g2", + "win11-21h2-avd", + "win10-21h2-avd-m365", + "win11-22h2-avd-m365", + "2022-datacenter-core-smalldisk-g2", + "win11-21h2-avd-m365", + "win11-23h2-avd", + "win11-23h2-avd-m365", + "win11-22h2-avd", + "2022-datacenter-g2", + "win10-22h2-avd-g2" + ] + }, + "GalleryImageId": { + "type": "string", + "defaultValue": "" + }, + "SecurityType": { + "type": "string", + "defaultValue": "TrustedLaunch", + "allowedValues": [ + "Standard", + "TrustedLaunch", + "ConfidentialVM" + ] + }, + "SecureBootEnabled": { + "type": "bool", + "defaultValue": true + }, + "TpmEnabled": { + "type": "bool", + "defaultValue": true + }, + "SubnetId": { + "type": "string", + "defaultValue": "" + }, + "IdentityServiceProvider": { + "type": "string", + "defaultValue": "EntraID", + "allowedValues": [ + "EntraID", + "ActiveDirectory", + "EntraDS" + ] + }, + "IntuneEnrollment": { + "type": "bool", + "defaultValue": false + }, + "ADDomainName": { + "type": "string", + "defaultValue": "" + }, + "ADDomainJoinUserName": { + "type": "string", + "defaultValue": "" + }, + "ADJoinUserPassword": { + "type": "securestring", + "defaultValue": "" + }, + "ADOUPath": { + "type": "string", + "defaultValue": "" + }, + "LocalAdminUsername": { + "type": "string", + "defaultValue": "" + }, + "CustomTemplateSpecResourceId": { + "type": "string", + "defaultValue": "" + }, + "VMNamesTemplateParameterName": { + "type": "string", + "defaultValue": "VMNames", + "metadata": { + "description": "Required: No | The name of the parameter in the template that specifies the VM Names array." + } + }, + "CustomTemplateSpecParameters": { + "type": "object", + "defaultValue": {} + }, + "HostPoolResourceGroupName": { + "type": "string", + "defaultValue": "[resourceGroup().name]", + "metadata": { + "description": "Required: No | Name of the resource group containing the Azure Virtual Desktop Host Pool. | Default: The resource group of the Function App." + } + }, + "HostPoolName": { + "type": "string", + "metadata": { + "description": "Required: Yes | Name of the Azure Virtual Desktop Host Pool." + } + }, + "SessionHostNamePrefix": { + "type": "string", + "maxLength": 12, + "metadata": { + "description": "Required: Yes | Prefix used for the name of the session hosts." + } + }, + "SessionHostNameSeparator": { + "type": "string", + "defaultValue": "-", + "maxLength": 1, + "metadata": { + "description": "Required: NO | Separator between prefix and number. | Default: -" + } + }, + "TargetSessionHostCount": { + "type": "int", + "minValue": 0, + "metadata": { + "description": "Required: Yes | Number of session hosts to maintain in the host pool." + } + }, + "TargetSessionHostBuffer": { + "type": "int", + "minValue": 1, + "metadata": { + "description": "Required: Yes | The maximum number of session hosts to add during a replacement process" + } + }, + "UseGovDodGraph": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Required: No | Switches to using the US Governmment DoD graph endpoints for the Function App. | Default: false" + } + }, + "UseUserAssignedManagedIdentity": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Required: No | Resource Id of the User Assigned Managed Identity to use for the Function App. | Default: System Identity" + } + }, + "UserAssignedManagedIdentityResourceId": { + "type": "string", + "defaultValue": "" + }, + "TagIncludeInAutomation": { + "type": "string", + "defaultValue": "IncludeInAutoReplace", + "metadata": { + "description": "Required: No | Tag name used to indicate that a session host should be included in the automatic replacement process. | Default: IncludeInAutoReplace." + } + }, + "TagDeployTimestamp": { + "type": "string", + "defaultValue": "AutoReplaceDeployTimestamp", + "metadata": { + "description": "Required: No | Tag name used to indicate the timestamp of the last deployment of a session host. | Default: AutoReplaceDeployTimestamp." + } + }, + "TagPendingDrainTimestamp": { + "type": "string", + "defaultValue": "AutoReplacePendingDrainTimestamp", + "metadata": { + "description": "Required: No | Tag name used to indicate drain timestamp of session host pending deletion. | Default: AutoReplacePendingDrainTimestamp." + } + }, + "TagScalingPlanExclusionTag": { + "type": "string", + "defaultValue": "ScalingPlanExclusion", + "metadata": { + "description": "Required: No | Tag name used to exclude session host from Scaling Plan activities. | Default: ScalingPlanExclusion" + } + }, + "TargetVMAgeDays": { + "type": "int", + "defaultValue": 45, + "metadata": { + "description": "Required: No | Target age of session hosts in days. | Default: 45 days." + } + }, + "DrainGracePeriodHours": { + "type": "int", + "defaultValue": 24, + "metadata": { + "description": "Required: No | Grace period in hours for session hosts to drain before deletion. | Default: 24 hours." + } + }, + "FixSessionHostTags": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Required: No | If true, will apply tags for Include In Auto Replace and Deployment Timestamp to existing session hosts. This will not enable automatic deletion of existing session hosts. | Default: True." + } + }, + "IncludePreExistingSessionHosts": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Required: No | When enabled, the Session Host Replacer will automatically consider pre-existing VMs for replacement if they meet the criteria | Default: False." + } + }, + "SHRDeploymentPrefix": { + "type": "string", + "defaultValue": "AVDSessionHostReplacer", + "metadata": { + "description": "Required: No | Prefix used for the deployment name of the session hosts. | Default: AVDSessionHostReplacer" + } + }, + "SessionHostInstanceNumberPadding": { + "type": "int", + "defaultValue": 2, + "metadata": { + "description": "Required: No | Number of digits to use for the instance number of the session hosts (eg. AVDVM-01). | Default: 2" + } + }, + "ReplaceSessionHostOnNewImageVersion": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Required: No | If true, will replace session hosts when a new image version is detected. | Default: true" + } + }, + "ReplaceSessionHostOnNewImageVersionDelayDays": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Required: No | Delay in days before replacing session hosts when a new image version is detected. | Default: 0 (no delay)." + } + }, + "SessionHostResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Required: No | Leave this empty to deploy to same resource group as the host pool." + } + }, + "TimeStamp": { + "type": "string", + "defaultValue": "[utcNow()]" + } + }, + "variables": { + "varMarketPlaceImages": { + "win10-21h2-avd": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "windows-10", + "sku": "win10-21h2-avd" + }, + "win10-21h2-avd-g2": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "windows-10", + "sku": "win10-21h2-avd-g2" + }, + "win10-21h2-avd-m365": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "office-365", + "sku": "win10-21h2-avd-m365" + }, + "win10-21h2-avd-m365-g2": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "office-365", + "sku": "win10-21h2-avd-m365-g2" + }, + "win10-22h2-avd": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "windows-10", + "sku": "win10-22h2-avd" + }, + "win10-22h2-avd-g2": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "windows-10", + "sku": "win10-22h2-avd-g2" + }, + "win10-22h2-avd-m365": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "office-365", + "sku": "win10-22h2-avd-m365" + }, + "win10-22h2-avd-m365-g2": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "office-365", + "sku": "win10-22h2-avd-m365-g2" + }, + "win11-21h2-avd": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "windows-11", + "sku": "win11-21h2-avd" + }, + "win11-21h2-avd-m365": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "office-365", + "sku": "win11-21h2-avd-m365" + }, + "win11-22h2-avd": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "windows-11", + "sku": "win11-22h2-avd" + }, + "win11-22h2-avd-m365": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "office-365", + "sku": "win11-22h2-avd-m365" + }, + "win11-23h2-avd": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "windows-11", + "sku": "win11-23h2-avd" + }, + "win11-23h2-avd-m365": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "office-365", + "sku": "win11-23h2-avd-m365" + } + }, + "varImageReference": "[if(equals(parameters('MarketPlaceOrCustomImage'), 'Marketplace'), createObject('publisher', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].publisher, 'offer', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].offer, 'sku', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].sku, 'version', 'latest'), createObject('Id', parameters('GalleryImageId')))]", + "varSecurityProfile": "[if(equals(parameters('SecurityType'), 'Standard'), null(), createObject('securityType', parameters('SecurityType'), 'uefiSettings', createObject('secureBootEnabled', parameters('SecureBootEnabled'), 'vTpmEnabled', parameters('TpmEnabled'))))]", + "varDomainJoinObject": "[if(equals(parameters('IdentityServiceProvider'), 'EntraID'), createObject('DomainType', 'EntraID', 'IntuneJoin', parameters('IntuneEnrollment')), createObject('DomainType', 'ActiveDirectory', 'DomainName', parameters('ADDomainName'), 'DomainJoinUserName', parameters('ADDomainJoinUserName'), 'ADOUPath', parameters('ADOUPath')))]", + "varAzureEnvironments": [ + "AzureCloud", + "AzureUSGovernment", + "AzureChinaCloud" + ], + "splitParts": "[split(parameters('HostPoolResourceGroupName'), '-')]", + "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4], '-', variables('splitParts')[5])]", + "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", + "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", + "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", + "varFunctionAppName": "[concat('AVDReplacer', variables('rgpattern'))]", + "varFunctionAppIdentity": "[if(parameters('UseUserAssignedManagedIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', parameters('UserAssignedManagedIdentityResourceId')), createObject())), createObject('type', 'SystemAssigned'))]" + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "deployFunctionApp", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "Location": { + "value": "[parameters('Location')]" + }, + "FunctionAppName": { + "value": "[variables('varFunctionAppName')]" + }, + "EnableMonitoring": { + "value": "[parameters('EnableMonitoring')]" + }, + "UseExistingLAW": { + "value": "[parameters('UseExistingLAW')]" + }, + "LogAnalyticsWorkspaceId": { + "value": "[parameters('LogAnalyticsWorkspaceId')]" + }, + "ReplacementPlanSettings": { + "value": [ + { + "name": "_HostPoolResourceGroupName", + "value": "[parameters('HostPoolResourceGroupName')]" + }, + { + "name": "_HostPoolName", + "value": "[parameters('HostPoolName')]" + }, + { + "name": "_TargetSessionHostCount", + "value": "[parameters('TargetSessionHostCount')]" + }, + { + "name": "_TargetSessionHostBuffer", + "value": "[parameters('TargetSessionHostBuffer')]" + }, + { + "name": "_SessionHostNamePrefix", + "value": "[parameters('SessionHostNamePrefix')]" + }, + { + "name": "_SessionHostNameSeparator", + "value": "[parameters('SessionHostNameSeparator')]" + }, + { + "name": "_SessionHostTemplate", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate'), '2022-09-01').outputs.TemplateSpecResourceId.value]" + }, + { + "name": "_SessionHostParameters", + "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" + }, + { + "name": "_SubscriptionId", + "value": "[subscription().subscriptionId]" + }, + { + "name": "_RemoveEntraDevice", + "value": "[equals(parameters('IdentityServiceProvider'), 'EntraID')]" + }, + { + "name": "_RemoveIntuneDevice", + "value": "[parameters('IntuneEnrollment')]" + }, + { + "name": "_ClientId", + "value": "[if(parameters('UseUserAssignedManagedIdentity'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').clientId, '')]" + }, + { + "name": "_TenantId", + "value": "[if(parameters('UseUserAssignedManagedIdentity'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').tenantId, '')]" + }, + { + "name": "_GraphEnvironmentName", + "value": "[variables('varGraphEnvironmentName')]" + }, + { + "name": "_AzureEnvironmentName", + "value": "[environment().name]" + }, + { + "name": "_Tag_IncludeInAutomation", + "value": "[parameters('TagIncludeInAutomation')]" + }, + { + "name": "_Tag_DeployTimestamp", + "value": "[parameters('TagDeployTimestamp')]" + }, + { + "name": "_Tag_PendingDrainTimestamp", + "value": "[parameters('TagPendingDrainTimestamp')]" + }, + { + "name": "_Tag_ScalingPlanExclusionTag", + "value": "[parameters('TagScalingPlanExclusionTag')]" + }, + { + "name": "_TargetVMAgeDays", + "value": "[parameters('TargetVMAgeDays')]" + }, + { + "name": "_DrainGracePeriodHours", + "value": "[parameters('DrainGracePeriodHours')]" + }, + { + "name": "_FixSessionHostTags", + "value": "[parameters('FixSessionHostTags')]" + }, + { + "name": "_IncludePreExistingSessionHosts", + "value": "[parameters('IncludePreExistingSessionHosts')]" + }, + { + "name": "_SHRDeploymentPrefix", + "value": "[parameters('SHRDeploymentPrefix')]" + }, + { + "name": "_SessionHostInstanceNumberPadding", + "value": "[parameters('SessionHostInstanceNumberPadding')]" + }, + { + "name": "_ReplaceSessionHostOnNewImageVersion", + "value": "[parameters('ReplaceSessionHostOnNewImageVersion')]" + }, + { + "name": "_ReplaceSessionHostOnNewImageVersionDelayDays", + "value": "[parameters('ReplaceSessionHostOnNewImageVersionDelayDays')]" + }, + { + "name": "_VMNamesTemplateParameterName", + "value": "[parameters('VMNamesTemplateParameterName')]" + }, + { + "name": "_SessionHostResourceGroupName", + "value": "[parameters('SessionHostResourceGroupName')]" + } + ] + }, + "FunctionAppIdentity": { + "value": "[variables('varFunctionAppIdentity')]" + } + }, + "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": "3734734378229800954" + } + }, + "parameters": { + "Location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Required: No | Region of the Function App. This does not need to be the same as the location of the Azure Virtual Desktop Host Pool. | Default: Location of the resource group." + } + }, + "EnableMonitoring": { + "type": "bool", + "defaultValue": true + }, + "UseExistingLAW": { + "type": "bool", + "defaultValue": false + }, + "LogAnalyticsWorkspaceId": { + "type": "string", + "defaultValue": "none", + "metadata": { + "description": "Required: Yes | Name of the Log Analytics Workspace used by the Function App Insights." + } + }, + "FunctionAppName": { + "type": "string", + "metadata": { + "description": "Required: Yes | Name of the Function App." + } + }, + "FunctionAppZipUrl": { + "type": "string", + "defaultValue": "https://github.com/Azure/AVDSessionHostReplacer/releases/download/v0.3.1-beta.3/FunctionApp.zip", + "metadata": { + "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." + } + }, + "AppPlanName": { + "type": "string", + "defaultValue": "Y1", + "metadata": { + "description": "Required: No | App Service Plan Name | Default: Y1 for consumption based plan" + } + }, + "AppPlanTier": { + "type": "string", + "defaultValue": "Dynamic", + "metadata": { + "description": "Required: No | App Service Plan Tier | Default: Dynamic for consumption based plan" + } + }, + "ReplacementPlanSettings": { + "type": "array", + "metadata": { + "description": "Required: Yes | The following settings are mandatory. Rest are optional.\n[\n {\n name: '_HostPoolResourceGroupName'\n value: 'string'\n }\n {\n name: '_HostPoolName'\n value: 'string'\n }\n {\n name: '_RemoveEntraDevice'\n value: 'bool'\n }\n {\n name: '_SessionHostTemplate'\n value: 'string'\n }\n {\n name: '_SessionHostParameters'\n value: 'hashtable'\n }\n {\n name: '_SubscriptionId'\n value: 'string'\n }\n {\n name: '_TargetSessionHostCount'\n value: 'int'\n }\n {\n name: '_SessionHostNamePrefix'\n value: 'string'\n }\n]" + } + }, + "FunctionAppIdentity": { + "type": "object", + "defaultValue": { + "type": "SystemAssigned" + } + } + }, + "variables": { + "rgname": "[toLower(resourceGroup().name)]", + "splitParts": "[split(variables('rgname'), '-')]", + "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", "varStorageAccountName": "[concat('st', variables('rgpattern2'))]", -"varLogAnalyticsWorkspaceName": "[concat('law-', parameters('FunctionAppName'))]", -"varAppServicePlanName": "[concat('Asp-', parameters('FunctionAppName'))]", -"varGraphEnvironmentName": "your_value_here" -}, -"resources": [ -{ -"type": "Microsoft.Web/sites/extensions", -"apiVersion": "2023-01-01", -"name": "[format('{0}/{1}', parameters('FunctionAppName'), 'MSDeploy')]", -"properties": { -"packageUri": "[parameters('FunctionAppZipUrl')]" -}, -"dependsOn": [ -"[resourceId('Microsoft.Web/sites', parameters('FunctionAppName'))]" -] -}, -{ -"type": "Microsoft.Storage/storageAccounts", -"apiVersion": "2022-05-01", -"name": "[variables('varStorageAccountName')]", -"location": "[parameters('Location')]", -"kind": "StorageV2", -"sku": { -"name": "Standard_LRS" -}, -"properties": {} -}, -{ -"condition": "[and(parameters('EnableMonitoring'), not(parameters('UseExistingLAW')))]", -"type": "Microsoft.OperationalInsights/workspaces", -"apiVersion": "2023-09-01", -"name": "[variables('varLogAnalyticsWorkspaceName')]", -"location": "[parameters('Location')]", -"properties": { -"sku": { -"name": "PerGB2018" -}, -"retentionInDays": 30 -} -}, -{ -"type": "Microsoft.Web/serverfarms", -"apiVersion": "2022-03-01", -"name": "[variables('varAppServicePlanName')]", -"location": "[parameters('Location')]", -"sku": { -"name": "[parameters('AppPlanName')]", -"tier": "[parameters('AppPlanTier')]" -} -}, -{ -"condition": "[parameters('EnableMonitoring')]", -"type": "Microsoft.Insights/components", -"apiVersion": "2020-02-02", -"name": "[variables('varAppServicePlanName')]", -"location": "[parameters('Location')]", -"kind": "web", -"properties": { -"Application_Type": "web", -"publicNetworkAccessForIngestion": "Enabled", -"publicNetworkAccessForQuery": "Enabled", -"WorkspaceResourceId": "[if(parameters('UseExistingLAW'), parameters('LogAnalyticsWorkspaceId'), resourceId('Microsoft.OperationalInsights/workspaces', variables('varLogAnalyticsWorkspaceName')))]" -}, -"dependsOn": [ -"[resourceId('Microsoft.OperationalInsights/workspaces', variables('varLogAnalyticsWorkspaceName'))]" -] -}, -{ -"type": "Microsoft.Web/sites", -"apiVersion": "2023-01-01", -"name": "[parameters('FunctionAppName')]", -"location": "[parameters('Location')]", -"kind": "functionApp", -"identity": "[parameters('FunctionAppIdentity')]", -"properties": { -"httpsOnly": true, -"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('varAppServicePlanName'))]", -"siteConfig": { -"use32BitWorkerProcess": false, -"powerShellVersion": "7.2", -"netFrameworkVersion": "v6.0", -"appSettings": "[union(createArray(createObject('name', 'FUNCTIONS_EXTENSION_VERSION', 'value', '~4'), createObject('name', 'FUNCTIONS_WORKER_RUNTIME', 'value', 'powershell'), createObject('name', 'AzureWebJobsStorage', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey), createObject('name', 'WEBSITE_CONTENTSHARE', 'value', toLower(parameters('FunctionAppName')))), if(parameters('EnableMonitoring'), createArray(createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey)), createArray()), parameters('ReplacementPlanSettings'))]", -"ftpsState": "Disabled", -"cors": { -"allowedOrigins": [ -"https://portal.azure.com" -] -} -} -}, -"dependsOn": [ -"[resourceId('Microsoft.Insights/components', variables('varAppServicePlanName'))]", -"[resourceId('Microsoft.Web/serverfarms', variables('varAppServicePlanName'))]", -"[resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName'))]" -] -} -], -"outputs": { -"functionAppPrincipalId": { -"type": "string", -"value": "[if(equals(parameters('FunctionAppIdentity').type, 'SystemAssigned'), reference(resourceId('Microsoft.Web/sites', parameters('FunctionAppName')), '2023-01-01', 'full').identity.principalId, '')]" -} -} -} -}, -"dependsOn": [ -"[resourceId('Microsoft.Resources/deployments', 'deployKeyVault')]", -"[resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate')]" -] -}, -{ -"condition": "[not(equals(parameters('IdentityServiceProvider'), 'EntraID'))]", -"type": "Microsoft.Resources/deployments", -"apiVersion": "2022-09-01", -"name": "deployKeyVault", -"properties": { -"expressionEvaluationOptions": { -"scope": "inner" -}, -"mode": "Incremental", -"parameters": { -"Location": { -"value": "[parameters('Location')]" -}, -"KeyVaultName": { -"value": "[concat('kvSHR', variables('rgpattern2'))]" -}, -"DomainJoinPassword": { -"value": "[parameters('ADJoinUserPassword')]" -} -}, -"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": "17584014780822218958" -} -}, -"parameters": { -"Location": { -"type": "string", -"defaultValue": "[resourceGroup().location]" -}, -"KeyVaultName": { -"type": "string" -}, -"DomainJoinPassword": { -"type": "securestring" -} -}, -"resources": [ -{ -"type": "Microsoft.KeyVault/vaults/secrets", -"apiVersion": "2023-07-01", -"name": "[format('{0}/{1}', parameters('KeyVaultName'), 'DomainJoinPassword')]", -"properties": { -"value": "[parameters('DomainJoinPassword')]" -}, -"dependsOn": [ -"[resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]" -] -}, -{ -"type": "Microsoft.KeyVault/vaults", -"apiVersion": "2023-07-01", -"name": "[parameters('KeyVaultName')]", -"location": "[parameters('Location')]", -"properties": { -"sku": { -"family": "A", -"name": "standard" -}, -"tenantId": "[subscription().tenantId]", -"enabledForTemplateDeployment": true, -"enableRbacAuthorization": true -} -} -], -"outputs": { -"keyVaultId": { -"type": "string", -"value": "[resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]" -} -} -} -} -}, -{ -"type": "Microsoft.Resources/deployments", -"apiVersion": "2022-09-01", -"name": "deployStandardSessionHostTemplate", -"properties": { -"expressionEvaluationOptions": { -"scope": "inner" -}, -"mode": "Incremental", -"parameters": { -"Location": { -"value": "[parameters('Location')]" -}, -"Name": { -"value": "[format('{0}-Spec', parameters('HostPoolName'))]" -} -}, -"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": "10208603161915711494" -} -}, -"parameters": { -"Name": { -"type": "string" -}, -"Location": { -"type": "string", -"defaultValue": "[resourceGroup().location]" -} -}, -"variables": { -"$fxv#0": { -"$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": "8994538122455151797" -} -}, -"parameters": { -"Location": { -"type": "string", -"defaultValue": "[[resourceGroup().location]" -}, -"AvailabilityZones": { -"type": "array", -"defaultValue": [] -}, -"VMNames": { -"type": "array" -}, -"VMNamePrefixLength": { -"type": "int" -}, -"VMSize": { -"type": "string" -}, -"SubnetID": { -"type": "string" -}, -"AdminUsername": { -"type": "string" -}, -"AcceleratedNetworking": { -"type": "bool" -}, -"DiskType": { -"type": "string" -}, -"Tags": { -"type": "object", -"defaultValue": {} -}, -"ImageReference": { -"type": "object" -}, -"SecurityProfile": { -"type": "object", -"defaultValue": {} -}, -"HostPoolName": { -"type": "string" -}, -"HostPoolToken": { -"type": "securestring" -}, -"WVDArtifactsURL": { -"type": "string", -"defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" -}, -"DomainJoinObject": { -"type": "object", -"defaultValue": {} -}, -"DomainJoinPassword": { -"type": "securestring", -"defaultValue": "" -} -}, -"resources": [ -{ -"[string('copy')]": { -"name": "deploySessionHosts", -"count": "[[length(parameters('VMNames'))]" -}, -"type": "Microsoft.Resources/deployments", -"apiVersion": "2022-09-01", -"name": "[[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", -"properties": { -"expressionEvaluationOptions": { -"scope": "inner" -}, -"mode": "Incremental", -"parameters": { -"AcceleratedNetworking": { -"value": "[[parameters('AcceleratedNetworking')]" -}, -"AdminUsername": { -"value": "[[parameters('AdminUsername')]" -}, -"HostPoolName": { -"value": "[[parameters('HostPoolName')]" -}, -"HostPoolToken": { -"value": "[[parameters('HostPoolToken')]" -}, -"ImageReference": { -"value": "[[parameters('ImageReference')]" -}, -"SecurityProfile": { -"value": "[[parameters('SecurityProfile')]" -}, -"SubnetID": { -"value": "[[parameters('SubnetID')]" -}, -"VMName": { -"value": "[[parameters('VMNames')[copyIndex()]]" -}, -"VMNamePrefixLength": { -"value": "[[parameters('VMNamePrefixLength')]" -}, -"VMSize": { -"value": "[[parameters('VMSize')]" -}, -"DiskType": { -"value": "[[parameters('DiskType')]" -}, -"WVDArtifactsURL": { -"value": "[[parameters('WVDArtifactsURL')]" -}, -"DomainJoinObject": { -"value": "[[parameters('DomainJoinObject')]" -}, -"DomainJoinPassword": { -"value": "[[parameters('DomainJoinPassword')]" -}, -"Location": { -"value": "[[parameters('Location')]" -}, -"AvailabilityZones": { -"value": "[[parameters('AvailabilityZones')]" -}, -"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": "7267962610869920549" -} -}, -"parameters": { -"VMName": { -"type": "string" -}, -"VMNamePrefixLength": { -"type": "int" -}, -"VMSize": { -"type": "string" -}, -"DiskType": { -"type": "string" -}, -"Location": { -"type": "string", -"defaultValue": "[[resourceGroup().location]" -}, -"AvailabilityZones": { -"type": "array", -"defaultValue": [] -}, -"SubnetID": { -"type": "string" -}, -"AdminUsername": { -"type": "string" -}, -"AdminPassword": { -"type": "securestring", -"defaultValue": "[[newGuid()]" -}, -"AcceleratedNetworking": { -"type": "bool" -}, -"Tags": { -"type": "object", -"defaultValue": {} -}, -"ImageReference": { -"type": "object" -}, -"SecurityProfile": { -"type": "object" -}, -"HostPoolName": { -"type": "string" -}, -"HostPoolToken": { -"type": "securestring" -}, -"WVDArtifactsURL": { -"type": "string" -}, -"DomainJoinObject": { -"type": "object", -"defaultValue": {} -}, -"DomainJoinPassword": { -"type": "securestring", -"defaultValue": "" -} -}, -"variables": { -"varRequireNvidiaGPU": "[[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", -"varVMNumber": "[[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", -"varAvailabilityZone": "[[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" -}, -"resources": [ -{ -"type": "Microsoft.Compute/virtualMachines/extensions", -"apiVersion": "2023-09-01", -"name": "[[format('{0}/{1}', parameters('VMName'), 'deployIntegrityMonitoring')]", -"location": "[[parameters('Location')]", -"properties": { -"publisher": "Microsoft.Azure.Security.WindowsAttestation", -"type": "GuestAttestation", -"typeHandlerVersion": "1.0", -"autoUpgradeMinorVersion": true, -"settings": { -"AttestationConfig": { -"MaaSettings": { -"maaEndpoint": "", -"maaTenantName": "Guest Attestation" -}, -"AscSettings": { -"ascReportingEndpoint": "", -"ascReportingFrequency": "" -}, -"useCustomToken": "false", -"disableAlerts": "false" -} -} -}, -"dependsOn": [ -"[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" -] -}, -{ -"condition": "[[variables('varRequireNvidiaGPU')]", -"type": "Microsoft.Compute/virtualMachines/extensions", -"apiVersion": "2023-09-01", -"name": "[[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", -"location": "[[parameters('Location')]", -"properties": { -"publisher": "Microsoft.HpcCompute", -"type": "NvidiaGpuDriverWindows", -"typeHandlerVersion": "1.6", -"autoUpgradeMinorVersion": true -}, -"dependsOn": [ -"[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployIntegrityMonitoring')]", -"[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" -] -}, -{ -"condition": "[[not(equals(parameters('HostPoolName'), ''))]", -"type": "Microsoft.Compute/virtualMachines/extensions", -"apiVersion": "2023-09-01", -"name": "[[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", -"location": "[[parameters('Location')]", -"properties": { -"publisher": "Microsoft.PowerShell", -"type": "DSC", -"typeHandlerVersion": "2.77", -"autoUpgradeMinorVersion": true, -"settings": { -"modulesUrl": "[[parameters('WVDArtifactsURL')]", -"configurationFunction": "Configuration.ps1\\AddSessionHost", -"properties": { -"hostPoolName": "[[parameters('HostPoolName')]", -"registrationInfoToken": "[[parameters('HostPoolToken')]", -"aadJoin": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", -"useAgentDownloadEndpoint": true, -"mdmId": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" -} -} -}, -"dependsOn": [ -"[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", -"[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" -] -}, -{ -"condition": "[[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", -"type": "Microsoft.Compute/virtualMachines/extensions", -"apiVersion": "2023-09-01", -"name": "[[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", -"location": "[[parameters('Location')]", -"properties": { -"publisher": "Microsoft.Azure.ActiveDirectory", -"type": "AADLoginForWindows", -"typeHandlerVersion": "2.0", -"autoUpgradeMinorVersion": true, -"settings": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" -}, -"dependsOn": [ -"[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", -"[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" -] -}, -{ -"condition": "[[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", -"type": "Microsoft.Compute/virtualMachines/extensions", -"apiVersion": "2023-09-01", -"name": "[[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", -"location": "[[parameters('Location')]", -"properties": { -"publisher": "Microsoft.Compute", -"type": "JSonADDomainExtension", -"typeHandlerVersion": "1.3", -"autoUpgradeMinorVersion": true, -"settings": { -"Name": "[[parameters('DomainJoinObject').DomainName]", -"OUPath": "[[parameters('DomainJoinObject').ADOUPath]", -"User": "[[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", -"Restart": "true", -"Options": 3 -}, -"protectedSettings": { -"Password": "[[parameters('DomainJoinPassword')]" -} -}, -"dependsOn": [ -"[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", -"[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" -] -}, -{ -"type": "Microsoft.Network/networkInterfaces", -"apiVersion": "2023-09-01", -"name": "[[format('{0}-vNIC', parameters('VMName'))]", -"location": "[[parameters('Location')]", -"properties": { -"ipConfigurations": [ -{ -"name": "ipconfig1", -"properties": { -"subnet": { -"id": "[[parameters('SubnetID')]" -} -} -} -], -"enableAcceleratedNetworking": "[[parameters('AcceleratedNetworking')]" -}, -"tags": "[[parameters('Tags')]" -}, -{ -"type": "Microsoft.Compute/virtualMachines", -"apiVersion": "2023-09-01", -"name": "[[parameters('VMName')]", -"location": "[[parameters('Location')]", -"zones": "[[variables('varAvailabilityZone')]", -"identity": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", -"properties": { -"osProfile": { -"computerName": "[[parameters('VMName')]", -"adminUsername": "[[parameters('AdminUsername')]", -"adminPassword": "[[parameters('AdminPassword')]" -}, -"hardwareProfile": { -"vmSize": "[[parameters('VMSize')]" -}, -"storageProfile": { -"osDisk": { -"name": "[[format('{0}-OSDisk', parameters('VMName'))]", -"createOption": "FromImage", -"deleteOption": "Delete", -"managedDisk": { -"storageAccountType": "[[parameters('DiskType')]" -} -}, -"ImageReference": "[[parameters('ImageReference')]" -}, -"securityProfile": "[[parameters('SecurityProfile')]", -"diagnosticsProfile": { -"bootDiagnostics": { -"enabled": true -} -}, -"networkProfile": { -"networkInterfaces": [ -{ -"id": "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", -"properties": { -"deleteOption": "Delete" -} -} -] -}, -"licenseType": "Windows_Client" -}, -"tags": "[[parameters('Tags')]", -"dependsOn": [ -"[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" -] -} -] -} -} -} -] -} -}, -"resources": [ -{ -"type": "Microsoft.Resources/templateSpecs/versions", -"apiVersion": "2022-02-01", -"name": "[format('{0}/{1}', parameters('Name'), 'deploymentTemplateSpecVersion')]", -"location": "[parameters('Location')]", -"properties": { -"mainTemplate": "[variables('$fxv#0')]" -}, -"dependsOn": [ -"[resourceId('Microsoft.Resources/templateSpecs', parameters('Name'))]" -] -}, -{ -"type": "Microsoft.Resources/templateSpecs", -"apiVersion": "2022-02-01", -"name": "[parameters('Name')]", -"location": "[parameters('Location')]", -"properties": { -"description": "Template Spec for deploying VMs through the AVD Replacement Plan", -"displayName": "AVD Replacement Plan Session Host Template" -} -} -], -"outputs": { -"TemplateSpecResourceId": { -"type": "string", -"value": "[resourceId('Microsoft.Resources/templateSpecs', parameters('Name'))]" -} -} -} -} -}, -{ -"condition": "[not(parameters('UseUserAssignedManagedIdentity'))]", -"type": "Microsoft.Resources/deployments", -"apiVersion": "2022-09-01", -"name": "[format('RBAC-vdiVMContributor-{0}', parameters('TimeStamp'))]", -"subscriptionId": "[subscription().subscriptionId]", -"location": "[resourceGroup().location]", -"properties": { -"expressionEvaluationOptions": { -"scope": "inner" -}, -"mode": "Incremental", -"parameters": { -"PrinicpalId": { -"value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployFunctionApp'), '2022-09-01').outputs.functionAppPrincipalId.value]" -}, -"RoleDefinitionId": { -"value": "a959dbd1-f747-45e3-8ba6-dd80f235f97c" -}, -"Scope": { -"value": "[subscription().id]" -} -}, -"template": { -"$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": "852792330190262115" -} -}, -"parameters": { -"PrinicpalId": { -"type": "string" -}, -"RoleDefinitionId": { -"type": "string" -}, -"Scope": { -"type": "string" -} -}, -"resources": [ -{ -"type": "Microsoft.Authorization/roleAssignments", -"apiVersion": "2022-04-01", -"name": "[guid(parameters('PrinicpalId'), parameters('RoleDefinitionId'), parameters('Scope'))]", -"properties": { -"roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('RoleDefinitionId'))]", -"principalId": "[parameters('PrinicpalId')]" -} -} -] -} -}, -"dependsOn": [ -"[resourceId('Microsoft.Resources/deployments', 'deployFunctionApp')]" -] -}, -{ -"condition": "[not(parameters('UseUserAssignedManagedIdentity'))]", -"type": "Microsoft.Resources/deployments", -"apiVersion": "2022-09-01", -"name": "[format('RBAC-TemplateSpecReader-{0}', parameters('TimeStamp'))]", -"subscriptionId": "[subscription().subscriptionId]", -"location": "[resourceGroup().location]", -"properties": { -"expressionEvaluationOptions": { -"scope": "inner" -}, -"mode": "Incremental", -"parameters": { -"PrinicpalId": { -"value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployFunctionApp'), '2022-09-01').outputs.functionAppPrincipalId.value]" -}, -"RoleDefinitionId": { -"value": "392ae280-861d-42bd-9ea5-08ee6d83b80e" -}, -"Scope": { -"value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate'), '2022-09-01').outputs.TemplateSpecResourceId.value]" -} -}, -"template": { -"$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": "852792330190262115" -} -}, -"parameters": { -"PrinicpalId": { -"type": "string" -}, -"RoleDefinitionId": { -"type": "string" -}, -"Scope": { -"type": "string" -} -}, -"resources": [ -{ -"type": "Microsoft.Authorization/roleAssignments", -"apiVersion": "2022-04-01", -"name": "[guid(parameters('PrinicpalId'), parameters('RoleDefinitionId'), parameters('Scope'))]", -"properties": { -"roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('RoleDefinitionId'))]", -"principalId": "[parameters('PrinicpalId')]" -} -} -] -} -}, -"dependsOn": [ -"[resourceId('Microsoft.Resources/deployments', 'deployFunctionApp')]", -"[resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate')]" -] -} -] + "varLogAnalyticsWorkspaceName": "[concat('law-', parameters('FunctionAppName'))]", + "varAppServicePlanName": "[concat('Asp-', parameters('FunctionAppName'))]", + "varGraphEnvironmentName": "your_value_here" + }, + "resources": [ + { + "type": "Microsoft.Web/sites/extensions", + "apiVersion": "2023-01-01", + "name": "[format('{0}/{1}', parameters('FunctionAppName'), 'MSDeploy')]", + "properties": { + "packageUri": "[parameters('FunctionAppZipUrl')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('FunctionAppName'))]" + ] + }, + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-05-01", + "name": "[variables('varStorageAccountName')]", + "location": "[parameters('Location')]", + "kind": "StorageV2", + "sku": { + "name": "Standard_LRS" + }, + "properties": {} + }, + { + "condition": "[and(parameters('EnableMonitoring'), not(parameters('UseExistingLAW')))]", + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2023-09-01", + "name": "[variables('varLogAnalyticsWorkspaceName')]", + "location": "[parameters('Location')]", + "properties": { + "sku": { + "name": "PerGB2018" + }, + "retentionInDays": 30 + } + }, + { + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2022-03-01", + "name": "[variables('varAppServicePlanName')]", + "location": "[parameters('Location')]", + "sku": { + "name": "[parameters('AppPlanName')]", + "tier": "[parameters('AppPlanTier')]" + } + }, + { + "condition": "[parameters('EnableMonitoring')]", + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", + "name": "[variables('varAppServicePlanName')]", + "location": "[parameters('Location')]", + "kind": "web", + "properties": { + "Application_Type": "web", + "publicNetworkAccessForIngestion": "Enabled", + "publicNetworkAccessForQuery": "Enabled", + "WorkspaceResourceId": "[if(parameters('UseExistingLAW'), parameters('LogAnalyticsWorkspaceId'), resourceId('Microsoft.OperationalInsights/workspaces', variables('varLogAnalyticsWorkspaceName')))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.OperationalInsights/workspaces', variables('varLogAnalyticsWorkspaceName'))]" + ] + }, + { + "type": "Microsoft.Web/sites", + "apiVersion": "2023-01-01", + "name": "[parameters('FunctionAppName')]", + "location": "[parameters('Location')]", + "kind": "functionApp", + "identity": "[parameters('FunctionAppIdentity')]", + "properties": { + "httpsOnly": true, + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('varAppServicePlanName'))]", + "siteConfig": { + "use32BitWorkerProcess": false, + "powerShellVersion": "7.2", + "netFrameworkVersion": "v6.0", + "appSettings": "[union(createArray(createObject('name', 'FUNCTIONS_EXTENSION_VERSION', 'value', '~4'), createObject('name', 'FUNCTIONS_WORKER_RUNTIME', 'value', 'powershell'), createObject('name', 'AzureWebJobsStorage', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey), createObject('name', 'WEBSITE_CONTENTSHARE', 'value', toLower(parameters('FunctionAppName')))), if(parameters('EnableMonitoring'), createArray(createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey)), createArray()), parameters('ReplacementPlanSettings'))]", + "ftpsState": "Disabled", + "cors": { + "allowedOrigins": [ + "https://portal.azure.com" + ] + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Insights/components', variables('varAppServicePlanName'))]", + "[resourceId('Microsoft.Web/serverfarms', variables('varAppServicePlanName'))]", + "[resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName'))]" + ] + } + ], + "outputs": { + "functionAppPrincipalId": { + "type": "string", + "value": "[if(equals(parameters('FunctionAppIdentity').type, 'SystemAssigned'), reference(resourceId('Microsoft.Web/sites', parameters('FunctionAppName')), '2023-01-01', 'full').identity.principalId, '')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'deployKeyVault')]", + "[resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate')]" + ] + }, + { + "condition": "[not(equals(parameters('IdentityServiceProvider'), 'EntraID'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "deployKeyVault", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "Location": { + "value": "[parameters('Location')]" + }, + "KeyVaultName": { + "value": "[concat('kvSHR', variables('rgpattern2'))]" + }, + "DomainJoinPassword": { + "value": "[parameters('ADJoinUserPassword')]" + } + }, + "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": "17584014780822218958" + } + }, + "parameters": { + "Location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "KeyVaultName": { + "type": "string" + }, + "DomainJoinPassword": { + "type": "securestring" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('KeyVaultName'), 'DomainJoinPassword')]", + "properties": { + "value": "[parameters('DomainJoinPassword')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]" + ] + }, + { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-07-01", + "name": "[parameters('KeyVaultName')]", + "location": "[parameters('Location')]", + "properties": { + "sku": { + "family": "A", + "name": "standard" + }, + "tenantId": "[subscription().tenantId]", + "enabledForTemplateDeployment": true, + "enableRbacAuthorization": true + } + } + ], + "outputs": { + "keyVaultId": { + "type": "string", + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "deployStandardSessionHostTemplate", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "Location": { + "value": "[parameters('Location')]" + }, + "Name": { + "value": "[format('{0}-Spec', parameters('HostPoolName'))]" + } + }, + "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": "10208603161915711494" + } + }, + "parameters": { + "Name": { + "type": "string" + }, + "Location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + } + }, + "variables": { + "$fxv#0": { + "$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": "8994538122455151797" + } + }, + "parameters": { + "Location": { + "type": "string", + "defaultValue": "[[resourceGroup().location]" + }, + "AvailabilityZones": { + "type": "array", + "defaultValue": [] + }, + "VMNames": { + "type": "array" + }, + "VMNamePrefixLength": { + "type": "int" + }, + "VMSize": { + "type": "string" + }, + "SubnetID": { + "type": "string" + }, + "AdminUsername": { + "type": "string" + }, + "AcceleratedNetworking": { + "type": "bool" + }, + "DiskType": { + "type": "string" + }, + "Tags": { + "type": "object", + "defaultValue": {} + }, + "ImageReference": { + "type": "object" + }, + "SecurityProfile": { + "type": "object", + "defaultValue": {} + }, + "HostPoolName": { + "type": "string" + }, + "HostPoolToken": { + "type": "securestring" + }, + "WVDArtifactsURL": { + "type": "string", + "defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" + }, + "DomainJoinObject": { + "type": "object", + "defaultValue": {} + }, + "DomainJoinPassword": { + "type": "securestring", + "defaultValue": "" + } + }, + "resources": [ + { + "[string('copy')]": { + "name": "deploySessionHosts", + "count": "[[length(parameters('VMNames'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "AcceleratedNetworking": { + "value": "[[parameters('AcceleratedNetworking')]" + }, + "AdminUsername": { + "value": "[[parameters('AdminUsername')]" + }, + "HostPoolName": { + "value": "[[parameters('HostPoolName')]" + }, + "HostPoolToken": { + "value": "[[parameters('HostPoolToken')]" + }, + "ImageReference": { + "value": "[[parameters('ImageReference')]" + }, + "SecurityProfile": { + "value": "[[parameters('SecurityProfile')]" + }, + "SubnetID": { + "value": "[[parameters('SubnetID')]" + }, + "VMName": { + "value": "[[parameters('VMNames')[copyIndex()]]" + }, + "VMNamePrefixLength": { + "value": "[[parameters('VMNamePrefixLength')]" + }, + "VMSize": { + "value": "[[parameters('VMSize')]" + }, + "DiskType": { + "value": "[[parameters('DiskType')]" + }, + "WVDArtifactsURL": { + "value": "[[parameters('WVDArtifactsURL')]" + }, + "DomainJoinObject": { + "value": "[[parameters('DomainJoinObject')]" + }, + "DomainJoinPassword": { + "value": "[[parameters('DomainJoinPassword')]" + }, + "Location": { + "value": "[[parameters('Location')]" + }, + "AvailabilityZones": { + "value": "[[parameters('AvailabilityZones')]" + }, + "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": "7267962610869920549" + } + }, + "parameters": { + "VMName": { + "type": "string" + }, + "VMNamePrefixLength": { + "type": "int" + }, + "VMSize": { + "type": "string" + }, + "DiskType": { + "type": "string" + }, + "Location": { + "type": "string", + "defaultValue": "[[resourceGroup().location]" + }, + "AvailabilityZones": { + "type": "array", + "defaultValue": [] + }, + "SubnetID": { + "type": "string" + }, + "AdminUsername": { + "type": "string" + }, + "AdminPassword": { + "type": "securestring", + "defaultValue": "[[newGuid()]" + }, + "AcceleratedNetworking": { + "type": "bool" + }, + "Tags": { + "type": "object", + "defaultValue": {} + }, + "ImageReference": { + "type": "object" + }, + "SecurityProfile": { + "type": "object" + }, + "HostPoolName": { + "type": "string" + }, + "HostPoolToken": { + "type": "securestring" + }, + "WVDArtifactsURL": { + "type": "string" + }, + "DomainJoinObject": { + "type": "object", + "defaultValue": {} + }, + "DomainJoinPassword": { + "type": "securestring", + "defaultValue": "" + } + }, + "variables": { + "varRequireNvidiaGPU": "[[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", + "varVMNumber": "[[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", + "varAvailabilityZone": "[[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" + }, + "resources": [ + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'deployIntegrityMonitoring')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Azure.Security.WindowsAttestation", + "type": "GuestAttestation", + "typeHandlerVersion": "1.0", + "autoUpgradeMinorVersion": true, + "settings": { + "AttestationConfig": { + "MaaSettings": { + "maaEndpoint": "", + "maaTenantName": "Guest Attestation" + }, + "AscSettings": { + "ascReportingEndpoint": "", + "ascReportingFrequency": "" + }, + "useCustomToken": "false", + "disableAlerts": "false" + } + } + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[variables('varRequireNvidiaGPU')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.HpcCompute", + "type": "NvidiaGpuDriverWindows", + "typeHandlerVersion": "1.6", + "autoUpgradeMinorVersion": true + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployIntegrityMonitoring')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[not(equals(parameters('HostPoolName'), ''))]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.PowerShell", + "type": "DSC", + "typeHandlerVersion": "2.77", + "autoUpgradeMinorVersion": true, + "settings": { + "modulesUrl": "[[parameters('WVDArtifactsURL')]", + "configurationFunction": "Configuration.ps1\\AddSessionHost", + "properties": { + "hostPoolName": "[[parameters('HostPoolName')]", + "registrationInfoToken": "[[parameters('HostPoolToken')]", + "aadJoin": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", + "useAgentDownloadEndpoint": true, + "mdmId": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" + } + } + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Azure.ActiveDirectory", + "type": "AADLoginForWindows", + "typeHandlerVersion": "2.0", + "autoUpgradeMinorVersion": true, + "settings": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Compute", + "type": "JSonADDomainExtension", + "typeHandlerVersion": "1.3", + "autoUpgradeMinorVersion": true, + "settings": { + "Name": "[[parameters('DomainJoinObject').DomainName]", + "OUPath": "[[parameters('DomainJoinObject').ADOUPath]", + "User": "[[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", + "Restart": "true", + "Options": 3 + }, + "protectedSettings": { + "Password": "[[parameters('DomainJoinPassword')]" + } + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2023-09-01", + "name": "[[format('{0}-vNIC', parameters('VMName'))]", + "location": "[[parameters('Location')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[[parameters('SubnetID')]" + } + } + } + ], + "enableAcceleratedNetworking": "[[parameters('AcceleratedNetworking')]" + }, + "tags": "[[parameters('Tags')]" + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2023-09-01", + "name": "[[parameters('VMName')]", + "location": "[[parameters('Location')]", + "zones": "[[variables('varAvailabilityZone')]", + "identity": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", + "properties": { + "osProfile": { + "computerName": "[[parameters('VMName')]", + "adminUsername": "[[parameters('AdminUsername')]", + "adminPassword": "[[parameters('AdminPassword')]" + }, + "hardwareProfile": { + "vmSize": "[[parameters('VMSize')]" + }, + "storageProfile": { + "osDisk": { + "name": "[[format('{0}-OSDisk', parameters('VMName'))]", + "createOption": "FromImage", + "deleteOption": "Delete", + "managedDisk": { + "storageAccountType": "[[parameters('DiskType')]" + } + }, + "ImageReference": "[[parameters('ImageReference')]" + }, + "securityProfile": "[[parameters('SecurityProfile')]", + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": true + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", + "properties": { + "deleteOption": "Delete" + } + } + ] + }, + "licenseType": "Windows_Client" + }, + "tags": "[[parameters('Tags')]", + "dependsOn": [ + "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" + ] + } + ] + } + } + } + ] + } + }, + "resources": [ + { + "type": "Microsoft.Resources/templateSpecs/versions", + "apiVersion": "2022-02-01", + "name": "[format('{0}/{1}', parameters('Name'), 'deploymentTemplateSpecVersion')]", + "location": "[parameters('Location')]", + "properties": { + "mainTemplate": "[variables('$fxv#0')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/templateSpecs', parameters('Name'))]" + ] + }, + { + "type": "Microsoft.Resources/templateSpecs", + "apiVersion": "2022-02-01", + "name": "[parameters('Name')]", + "location": "[parameters('Location')]", + "properties": { + "description": "Template Spec for deploying VMs through the AVD Replacement Plan", + "displayName": "AVD Replacement Plan Session Host Template" + } + } + ], + "outputs": { + "TemplateSpecResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.Resources/templateSpecs', parameters('Name'))]" + } + } + } + } + }, + { + "condition": "[not(parameters('UseUserAssignedManagedIdentity'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('RBAC-vdiVMContributor-{0}', parameters('TimeStamp'))]", + "subscriptionId": "[subscription().subscriptionId]", + "location": "[resourceGroup().location]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "PrinicpalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployFunctionApp'), '2022-09-01').outputs.functionAppPrincipalId.value]" + }, + "RoleDefinitionId": { + "value": "a959dbd1-f747-45e3-8ba6-dd80f235f97c" + }, + "Scope": { + "value": "[subscription().id]" + } + }, + "template": { + "$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": "852792330190262115" + } + }, + "parameters": { + "PrinicpalId": { + "type": "string" + }, + "RoleDefinitionId": { + "type": "string" + }, + "Scope": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('PrinicpalId'), parameters('RoleDefinitionId'), parameters('Scope'))]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('RoleDefinitionId'))]", + "principalId": "[parameters('PrinicpalId')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'deployFunctionApp')]" + ] + }, + { + "condition": "[not(parameters('UseUserAssignedManagedIdentity'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('RBAC-TemplateSpecReader-{0}', parameters('TimeStamp'))]", + "subscriptionId": "[subscription().subscriptionId]", + "location": "[resourceGroup().location]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "PrinicpalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployFunctionApp'), '2022-09-01').outputs.functionAppPrincipalId.value]" + }, + "RoleDefinitionId": { + "value": "392ae280-861d-42bd-9ea5-08ee6d83b80e" + }, + "Scope": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate'), '2022-09-01').outputs.TemplateSpecResourceId.value]" + } + }, + "template": { + "$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": "852792330190262115" + } + }, + "parameters": { + "PrinicpalId": { + "type": "string" + }, + "RoleDefinitionId": { + "type": "string" + }, + "Scope": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('PrinicpalId'), parameters('RoleDefinitionId'), parameters('Scope'))]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('RoleDefinitionId'))]", + "principalId": "[parameters('PrinicpalId')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'deployFunctionApp')]", + "[resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate')]" + ] + } + ] } From dc17d1fd503f995340e35e9f769e3a1824fefec8 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:36:01 +0100 Subject: [PATCH 63/83] Update print statement from 'Hello' to 'Goodbye' --- deploy/portal-ui/portal-ui.json | 1606 +++++++++++++++---------------- 1 file changed, 802 insertions(+), 804 deletions(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index 4f4c72b..9a0936c 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -1,808 +1,806 @@ { -"$schema": "https://schema.management.azure.com/schemas/2021-09-09/uiFormDefinition.schema.json", -"view": { -"kind": "Form", -"properties": { -"title": "Azure Virtual Desktop - Session Host Replacer Deployment", -"steps": [ -{ -"name": "basics", -"label": "Basics", -"elements": [ -{ -"name": "resourceScope", -"type": "Microsoft.Common.ResourceScope", -"location": { -"resourceTypes": ["Microsoft.DesktopVirtualization/HostPools" -] -} -}, -{ -"name": "HostPoolSettingsSection", -"type": "Microsoft.Common.Section", -"label": "Host Pool Settings", -"visible": true, -"elements": [ -{ -"name": "HostPoolSelector", -"type": "Microsoft.Solutions.ResourceSelector", -"label": "Select target Host Pool", -"resourceType": "Microsoft.DesktopVirtualization/HostPools", -"constraints": { -"required": true -}, -"options": { -"filter": { -"subscription": "onBasics" -} -} -}, -{ -"name": "TargetSessionHostCount", -"type": "Microsoft.Common.Slider", -"min": 1, -"max": 5000, -"label": "Target Number of Session Hosts", -"subLabel": "VMs", -"defaultValue": 10, -"showStepMarkers": false, -"toolTip": "The target number of session hosts in the host pool.", -"constraints": { -"required": true -}, -"visible": true -}, -{ -"name": "TargetSessionHostBuffer", -"type": "Microsoft.Common.Slider", -"min": 1, -"max": "[steps('basics').HostPoolSettingsSection.TargetSessionHostCount]", -"label": "Session Hosts Buffer", -"subLabel": "VMs", -"defaultValue": 5, -"showStepMarkers": false, -"toolTip": "

The maximum number of session hosts to add during a replacement process.

Example:

Target is 10, buffer is 2, and we need to replace all VMs

Then SHR will add 2 new VMs, delete 2 old ones, and repeat until all hosts are replaced. This is useful to avoid exhausting subnets, capacity limits, and reduce costs.

", -"constraints": { -"required": true -}, -"visible": true -}, -{ -"name": "IncludePreExistingSessionHosts", -"type": "Microsoft.Common.CheckBox", -"label": "Include pre-existing session hosts", -"toolTip": "
When enabled, the Session Host Replacer will automatically consider pre-existing VMs for replacement if they meet the criteria by setting the IncludeInAutomation tag to True during the first run.
 
When disabled, the session hosts are not counted as part of the target number of VMs.
 
You can manually include a VM after deployment by updating its tag.
", -"constraints": { -"required": false -} -}, -{ -"name": "sessionHostNamePrefix", -"type": "Microsoft.Common.TextBox", -"label": "Session Host Name Prefix", -"toolTip": "

The prefix for the session host names.

Make sure this produces a unique value across all device names in your environment.

Try selecting a prefix that helps you identify the host pool such as AVDHP01

", -"constraints": { -"required": true, -"validationMessage": "Must be a valid name less than 12 characters long to allow for the 3 character suffix (eg. prefix-01)." -} -}, -{ -"name": "SessionHostExampleNameInfoBox", -"type": "Microsoft.Common.InfoBox", -"visible": "[not(empty(steps('basics').HostPoolSettingsSection.sessionHostNamePrefix))]", -"options": { -"icon": "Info", -"text": "[concat('Example Session Host name: ', steps('basics').HostPoolSettingsSection.sessionHostNamePrefix , if(steps('optionalParametersStep')._SessionHostNameSeparator,'-','') , take('00000000', sub(steps('optionalParametersStep')._SessionHostInstanceNumberPadding,1)) ,'1
You can customize the separator and padding from Optional Parameters.') ]" -} -} -] -}, -{ -"name": "IdentitySection", -"type": "Microsoft.Common.Section", -"label": "Identity", -"visible": true, -"elements": [ -{ -"name": "UseUserAssignedManagedIdentity", -"type": "Microsoft.Common.CheckBox", -"label": "Use User Assigned Managed Identity", -"toolTip": "[Recommended] When enabled, The Session Host Replacer will use the selected Identity to take actions. Otherwise System Identity (MSI) is used.", -"defaultValue": true, -"constraints": { -"required": false -} -}, -{ -"name": "UserAssignedManagedIdentitySelector", -"type": "Microsoft.Solutions.ResourceSelector", -"visible": "[steps('basics').IdentitySection.UseUserAssignedManagedIdentity]", -"label": "Select User Assigned Managed Identity", -"resourceType": "Microsoft.ManagedIdentity/userAssignedIdentities", -"constraints": { -"required": true -}, -"options": { -"filter": {} -} -}, -{ -"name": "UserAssignedManagedIdentityInfoBox", -"type": "Microsoft.Common.InfoBox", -"visible": "[steps('basics').IdentitySection.UseUserAssignedManagedIdentity]", -"options": { -"icon": "Info", -"text": "When using a User Assigned Managed Identity, make sure the identity has the needed permissions in Azure and Entra. Follow the link for more info.", -"uri": "https://github.com/Azure/AVDReplacementPlans/blob/v0.3.0/docs/Permissions.md" -} -} -] -}, -{ -"name": "MonitoringSection", -"type": "Microsoft.Common.Section", -"label": "Monitoring", -"visible": true, -"elements": [ -{ -"name": "EnableMonitoring", -"type": "Microsoft.Common.CheckBox", -"label": "Enable Monitoring", -"toolTip": "[Recommended] When enabled, the session host replacer will use App Insights and Log Analytics to collect metrics and logs.", -"defaultValue": true, -"constraints": { -"required": false -} -}, -{ -"name": "UseExistingLAW", -"visible": "[steps('basics').MonitoringSection.EnableMonitoring]", -"type": "Microsoft.Common.CheckBox", -"label": "Select existing Log Analytics Workspace", -"toolTip": "When enabled, the session host replacer will use the selected Log Analytics Workspace. If disabled, the session host replacer will create a new Log Analytics Workspace.", -"defaultValue": false, -"constraints": { -"required": false -} -}, -{ -"name": "LAWSelector", -"type": "Microsoft.Solutions.ResourceSelector", -"visible": "[and(steps('basics').MonitoringSection.EnableMonitoring, steps('basics').MonitoringSection.UseExistingLAW)]", -"label": "Log Analytics Workspace", -"resourceType": "Microsoft.OperationalInsights/workspaces", -"constraints": { -"required": true -}, -"options": { -"filter": {} -} -} -] -}, -{ -"name": "computeApi", -"type": "Microsoft.Solutions.ArmApiControl", -"request": { -"method": "GET", -"path": "[concat(steps('basics').resourceScope.subscription.id,'/providers/Microsoft.Compute/resourceTypes?api-version=2022-01-01')]" -} -}, -{ -"name": "VersionInfo", -"type": "Microsoft.Common.TextBlock", -"visible": true, -"options": { - "text": "AVD session host replacer Portal UI Version: v0.3.0", + "$schema": "https://schema.management.azure.com/schemas/2021-09-09/uiFormDefinition.schema.json", + "view": { + "kind": "Form", + "properties": { + "title": "Azure Virtual Desktop - Session Host Replacer Deployment", + "steps": [ + { + "name": "basics", + "label": "Basics", + "elements": [ + { + "name": "resourceScope", + "type": "Microsoft.Common.ResourceScope", + "location": { + "resourceTypes": ["Microsoft.DesktopVirtualization/HostPools" + ] + } + }, + { + "name": "HostPoolSettingsSection", + "type": "Microsoft.Common.Section", + "label": "Host Pool Settings", + "visible": true, + "elements": [ + { + "name": "HostPoolSelector", + "type": "Microsoft.Solutions.ResourceSelector", + "label": "Select target Host Pool", + "resourceType": "Microsoft.DesktopVirtualization/HostPools", + "constraints": { + "required": true + }, + "options": { + "filter": { + "subscription": "onBasics" + } + } + }, + { + "name": "TargetSessionHostCount", + "type": "Microsoft.Common.Slider", + "min": 1, + "max": 5000, + "label": "Target Number of Session Hosts", + "subLabel": "VMs", + "defaultValue": 10, + "showStepMarkers": false, + "toolTip": "The target number of session hosts in the host pool.", + "constraints": { + "required": true + }, + "visible": true + }, + { + "name": "TargetSessionHostBuffer", + "type": "Microsoft.Common.Slider", + "min": 1, + "max": "[steps('basics').HostPoolSettingsSection.TargetSessionHostCount]", + "label": "Session Hosts Buffer", + "subLabel": "VMs", + "defaultValue": 5, + "showStepMarkers": false, + "toolTip": "

The maximum number of session hosts to add during a replacement process.

Example:

Target is 10, buffer is 2, and we need to replace all VMs

Then SHR will add 2 new VMs, delete 2 old ones, and repeat until all hosts are replaced. This is useful to avoid exhausting subnets, capacity limits, and reduce costs.

", + "constraints": { + "required": true + }, + "visible": true + }, + { + "name": "IncludePreExistingSessionHosts", + "type": "Microsoft.Common.CheckBox", + "label": "Include pre-existing session hosts", + "toolTip": "
When enabled, the Session Host Replacer will automatically consider pre-existing VMs for replacement if they meet the criteria by setting the IncludeInAutomation tag to True during the first run.
 
When disabled, the session hosts are not counted as part of the target number of VMs.
 
You can manually include a VM after deployment by updating its tag.
", + "constraints": { + "required": false + } + }, + { + "name": "sessionHostNamePrefix", + "type": "Microsoft.Common.TextBox", + "label": "Session Host Name Prefix", + "toolTip": "

The prefix for the session host names.

Make sure this produces a unique value across all device names in your environment.

Try selecting a prefix that helps you identify the host pool such as AVDHP01

", + "constraints": { + "required": true, + "validationMessage": "Must be a valid name less than 12 characters long to allow for the 3 character suffix (eg. prefix-01)." + } + }, + { + "name": "SessionHostExampleNameInfoBox", + "type": "Microsoft.Common.InfoBox", + "visible": "[not(empty(steps('basics').HostPoolSettingsSection.sessionHostNamePrefix))]", + "options": { + "icon": "Info", + "text": "[concat('Example Session Host name: ', steps('basics').HostPoolSettingsSection.sessionHostNamePrefix , if(steps('optionalParametersStep')._SessionHostNameSeparator,'-','') , take('00000000', sub(steps('optionalParametersStep')._SessionHostInstanceNumberPadding,1)) ,'1
You can customize the separator and padding from Optional Parameters.') ]" + } + } + ] + }, + { + "name": "IdentitySection", + "type": "Microsoft.Common.Section", + "label": "Identity", + "visible": true, + "elements": [ + { + "name": "UseUserAssignedManagedIdentity", + "type": "Microsoft.Common.CheckBox", + "label": "Use User Assigned Managed Identity", + "toolTip": "[Recommended] When enabled, The Session Host Replacer will use the selected Identity to take actions. Otherwise System Identity (MSI) is used.", + "defaultValue": true, + "constraints": { + "required": false + } + }, + { + "name": "UserAssignedManagedIdentitySelector", + "type": "Microsoft.Solutions.ResourceSelector", + "visible": "[steps('basics').IdentitySection.UseUserAssignedManagedIdentity]", + "label": "Select User Assigned Managed Identity", + "resourceType": "Microsoft.ManagedIdentity/userAssignedIdentities", + "constraints": { + "required": true + }, + "options": { + "filter": {} + } + }, + { + "name": "UserAssignedManagedIdentityInfoBox", + "type": "Microsoft.Common.InfoBox", + "visible": "[steps('basics').IdentitySection.UseUserAssignedManagedIdentity]", + "options": { + "icon": "Info", + "text": "When using a User Assigned Managed Identity, make sure the identity has the needed permissions in Azure and Entra. Follow the link for more info.", + "uri": "https://github.com/Azure/AVDReplacementPlans/blob/v0.3.0/docs/Permissions.md" + } + } + ] + }, + { + "name": "MonitoringSection", + "type": "Microsoft.Common.Section", + "label": "Monitoring", + "visible": true, + "elements": [ + { + "name": "EnableMonitoring", + "type": "Microsoft.Common.CheckBox", + "label": "Enable Monitoring", + "toolTip": "[Recommended] When enabled, the session host replacer will use App Insights and Log Analytics to collect metrics and logs.", + "defaultValue": true, + "constraints": { + "required": false + } + }, + { + "name": "UseExistingLAW", + "visible": "[steps('basics').MonitoringSection.EnableMonitoring]", + "type": "Microsoft.Common.CheckBox", + "label": "Select existing Log Analytics Workspace", + "toolTip": "When enabled, the session host replacer will use the selected Log Analytics Workspace. If disabled, the session host replacer will create a new Log Analytics Workspace.", + "defaultValue": false, + "constraints": { + "required": false + } + }, + { + "name": "LAWSelector", + "type": "Microsoft.Solutions.ResourceSelector", + "visible": "[and(steps('basics').MonitoringSection.EnableMonitoring, steps('basics').MonitoringSection.UseExistingLAW)]", + "label": "Log Analytics Workspace", + "resourceType": "Microsoft.OperationalInsights/workspaces", + "constraints": { + "required": true + }, + "options": { + "filter": {} + } + } + ] + }, + { + "name": "computeApi", + "type": "Microsoft.Solutions.ArmApiControl", + "request": { + "method": "GET", + "path": "[concat(steps('basics').resourceScope.subscription.id,'/providers/Microsoft.Compute/resourceTypes?api-version=2022-01-01')]" + } + }, + { + "name": "VersionInfo", + "type": "Microsoft.Common.TextBlock", + "visible": true, + "options": { "text": "AVD session host replacer Portal UI Version: v0.3.2betasz", -"link": { -"label": "GitHub Repository", - "uri": "https://github.com/Azure/AVDSessionHostReplacer" + "link": { + "label": "GitHub Repository", "uri": "https://github.com/stefze/AVDSessionHostReplacer" -} -} -} -] -}, -{ -"name": "SessionHostsTemplate", -"label": "Session Hosts Template", -"elements": [ -{ -"name": "SessionHostsRegion", -"type": "Microsoft.Common.DropDown", -"label": "Session Hosts Region", -"visible": true, -"filter": true, -"multiselect": false, -"selectAll": false, -"toolTip": "Select region to deploy session hosts.", -"constraints": { -"required": true, -"allowedValues": "[map( first( map( filter( steps('basics').computeApi.value, (resourceTypes) => equals(resourceTypes.resourceType, 'virtualMachines') ), (item) => item.locations) ), (item) => parse(concat('{\"label\":\"', item, '\",\"value\":\"', toLower(replace(item, ' ', '')), '\"}')) )]" -} -}, -{ -"name": "AvailabilityZones", -"type": "Microsoft.Common.DropDown", -"label": "Availability Zones", -"visible": true, -"filter": false, -"defaultValue": [], -"multiselect": true, -"selectAll": false, -"toolTip": "Select Availability Zones for the session hosts. Make sure the selected size is available in the selected zones. Session hosts will be deployed across the zones.", -"constraints": { -"required": false, -"allowedValues": [ -{ -"label": "Zone 1", -"value": "1" -}, -{ -"label": "Zone 2", -"value": "2" -}, -{ -"label": "Zone 3", -"value": "3" -} -] -} -}, -{ -"name": "SessionHostSize", -"type": "Microsoft.Compute.SizeSelector", -"label": "VM Size", -"toolTip": "", -"recommendedSizes": [ -"Standard_D4ads_v5" -], -"constraints": { -"allowedSizes": [], -"excludedSizes": [], -"required": true -}, -"options": { -"hideDiskTypeFilter": true -}, -"osPlatform": "Windows", -"imageReference": { -"publisher": "MicrosoftWindowsDesktop", -"offer": "Windows-11", -"sku": "win11-23h2-avd" -} -}, -{ -"name": "AcceleratedNetworking", -"type": "Microsoft.Common.CheckBox", -"label": "Enable accelerated networking", -"defaultValue": true, -"toolTip": "Enables low latency and high throughput on the network interface." -}, -{ -"name": "SessionHostDiskType", -"type": "Microsoft.Common.DropDown", -"label": "OS Disk type", -"filter": false, -"defaultValue": "Premium SSD", -"toolTip": "Select session host disk type to host the OS.", -"constraints": { -"required": true, -"allowedValues": [ -{ -"label": "Standard HDD", -"value": "Standard_LRS" -}, -{ -"label": "Standard SSD", -"value": "StandardSSD_LRS" -}, -{ -"label": "Premium SSD", -"value": "Premium_LRS" -} -] -} -}, -{ -"name": "optionMarketPlaceOrCustomImage", -"type": "Microsoft.Common.OptionsGroup", -"label": "Image Source", -"defaultValue": "Marketplace", -"toolTip": "", -"constraints": { -"allowedValues": [ -{ -"label": "Marketplace", -"value": "Marketplace" -}, -{ -"label": "Gallery Image", -"value": "Gallery" -} -], -"required": true -}, -"visible": true -}, -{ -"name": "dropDownMarketPlaceImage", -"type": "Microsoft.Common.DropDown", -"label": "Select a Marketplace Image", -"placeholder": "", -"defaultValue": "Windows 11 Enterprise 23h2 multi-session + Microsoft 365 Apps", -"toolTip": "Marketplace images are updated on monthly basis. The Session Host Replacer will replace the session hosts when a new image is available.", -"multiselect": false, -"selectAll": false, -"filter": true, -"filterPlaceholder": "Filter items ...", -"multiLine": true, -"constraints": { -"allowedValues": [ -{ -"label": "Windows 10 Enterprise 21h2 multi-session", -"value": "win10-21h2-avd" -}, -{ -"label": "Windows 10 Enterprise 21h2 multi-session (Gen 2)", -"value": "win10-21h2-avd-g2" -}, -{ -"label": "Windows 10 Enterprise 21h2 multi-session + Microsoft 365 Apps", -"value": "win10-21h2-avd-m365" -}, -{ -"label": "Windows 10 Enterprise 21h2 multi-session + Microsoft 365 Apps (Gen 2)", -"value": "win10-21h2-avd-m365-g2" -}, -{ -"label": "Windows 10 Enterprise 22h2 multi-session", -"value": "win10-22h2-avd" -}, -{ -"label": "Windows 10 Enterprise 22h2 multi-session (Gen 2)", -"value": "win10-22h2-avd-g2" -}, -{ -"label": "Windows 10 Enterprise 22h2 multi-session + Microsoft 365 Apps", -"value": "win10-22h2-avd-m365" -}, -{ -"label": "Windows 10 Enterprise 22h2 multi-session + Microsoft 365 Apps (Gen 2)", -"value": "win10-22h2-avd-m365-g2" -}, -{ -"label": "Windows 11 Enterprise 21h2 multi-session", -"value": "win11-21h2-avd" -}, -{ -"label": "Windows 11 Enterprise 21h2 multi-session + Microsoft 365 Apps", -"value": "win11-21h2-avd-m365" -}, -{ -"label": "Windows 11 Enterprise 22h2 multi-session", -"value": "win11-22h2-avd" -}, -{ -"label": "Windows 11 Enterprise 22h2 multi-session + Microsoft 365 Apps", -"value": "win11-22h2-avd-m365" -}, -{ -"label": "Windows 11 Enterprise 23h2 multi-session", -"value": "win11-23h2-avd" -}, -{ -"label": "Windows 11 Enterprise 23h2 multi-session + Microsoft 365 Apps", -"value": "win11-23h2-avd-m365" -} -], -"required": true -}, -"visible": "[equals(steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage,'Marketplace')]" -}, -{ -"name": "GalleryImageInfoBox", -"type": "Microsoft.Common.InfoBox", -"visible": "[equals(steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage,'Gallery')]", -"options": { -"icon": "Warning", -"text": "The system identity of the Session Host Replacer function is assigned the 'Desktop Virtualization Virtual Machine Contributor' role against the subscription. If the Image Definition is in a different subscription, please make sure you manually assign the permission post deployment." -} -}, -{ -"name": "resourceSelectorSessionHostGalleryImageId", -"type": "Microsoft.Solutions.ResourceSelector", -"label": "Select Gallery Image", -"resourceType": "Microsoft.Compute/galleries/images", -"constraints": { -"required": true -}, -"options": { -"filter": {} -}, -"visible": "[equals(steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage,'Gallery')]" -}, -{ -"name": "sessionHostsSecuritySection", -"type": "Microsoft.Common.Section", -"visible": true, -"label": "Security profile", -"elements": [ -{ -"name": "SecurityType", -"type": "Microsoft.Common.DropDown", -"label": "Security type", -"filter": true, -"defaultValue": "Trusted Launch Virtual Machines", -"toolTip": "Choose a type of security that matches your needs: Trusted launch virtual machines provide additional security features on Gen2 virtual machines to protect against persistent and advanced attacks.", -"constraints": { -"required": true, -"allowedValues": [ -{ -"label": "Standard", -"value": "Standard" -}, -{ -"label": "Trusted Launch Virtual Machines", -"value": "TrustedLaunch" -}, -{ -"label": "Confidential Virtual Machines", -"value": "ConfidentialVM" -} -] -} -}, -{ -"name": "SecureBootEnabled", -"type": "Microsoft.Common.CheckBox", -"visible": "[or(equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'TrustedLaunch'), equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'ConfidentialVM'))]", -"label": "Enable secure boot", -"defaultValue": true, -"toolTip": "Secure boot helps protect your VMs against boot kits, rootkits, and kernel-level malware." -}, -{ -"name": "TpmEnabled", -"type": "Microsoft.Common.CheckBox", -"visible": "[or(equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'TrustedLaunch'), equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'ConfidentialVM'))]", -"label": "Enable vTPM", -"defaultValue": true, -"toolTip": "Virtual Trusted Platform Module (vTPM) is TPM2.0 compliant and validates your VM boot integrity apart from securely storing keys and secrets." -} -] -}, -{ -"name": "SubnetId", -"type": "Microsoft.Common.TextBox", -"label": "Subnet ID", -"visible": true, -"placeholder": "Add resource id of subnet in the same region as the Session Hosts", -"constraints": { -"required": true, -"validations": [ -{ -"regex": ".*/subnets/[A-Za-z0-9_\\.-]+$", -"message": "Invalid Subnet Resource ID. Make sure it ends with /subnets/SubnetName" -} -] -} -}, -{ -"name": "DomainJoinSection", -"type": "Microsoft.Common.Section", -"visible": true, -"label": "Domain Join", -"elements": [ -{ -"name": "IdentityServiceProvider", -"type": "Microsoft.Common.OptionsGroup", -"visible": true, -"label": "Identity service provider", -"defaultValue": "Microsoft Entra ID", -"toolTip": "Identity service provider (Active Directory or EntraDS) that already exist and will be used for Azure Virtual Desktop.", -"constraints": { -"required": true, -"allowedValues": [ -{ -"label": "Microsoft Entra ID", -"value": "EntraID" -}, -{ -"label": "Active Directory (AD DS)", -"value": "ActiveDirectory" -}, -{ -"label": "Microsoft Entra Domain Services", -"value": "EntraDS" -} -] -} -}, -{ -"name": "EntraJoinedInfoBox", -"type": "Microsoft.Common.InfoBox", -"visible": "[equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID')]", -"options": { -"icon": "Warning", -"text": "When the VMs are Entra Joined, session host replacer will attempt to delete its device object from Entra ID. This requires additional permissions to be granted to the service principal used by session host replacer. Please refer to the documentation for more information.", -"uri": "https://github.com/Azure/AVDSessionHostReplacer/blob/main/docs/Permissions.md" -} -}, -{ -"name": "IntuneEnrollment", -"type": "Microsoft.Common.CheckBox", -"visible": "[equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID')]", -"label": "Intune enrollment", -"defaultValue": false, -"toolTip": "If Intune is configured in your Microsoft Entra ID tenant, you can choose to have the VM automatically enrolled during the deployment by selecting this box. Session Host Replacer will delete the device from Intune during replacement." -}, -{ -"name": "ADDomainName", -"type": "Microsoft.Common.TextBox", -"label": "AD Domain name", -"visible": "[or(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'ActiveDirectory'), equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraDS'))]", -"placeholder": "Example: contoso.com", -"constraints": { -"required": true -} -}, -{ -"name": "ADDomainJoinUserName", -"type": "Microsoft.Common.TextBox", -"label": "User principal name", -"visible": "[not(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID'))]", -"toolTip": "Provide username with permissions to join session host to the domain.", -"placeholder": "Example: avdadmin@contoso.com", -"defaultValue": "", -"constraints": { -"required": true -} -}, -{ -"name": "ADJoinUserPassword", -"type": "Microsoft.Common.PasswordBox", -"label": { -"password": "Password" -}, -"visible": "[not(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID'))]", -"toolTip": "Provide password for domain join account. This will be stored in a new Azure Key Vault.", -"constraints": { -"required": true -}, -"options": { -"hideConfirmation": true -} -}, -{ -"name": "ADOUPath", -"type": "Microsoft.Common.TextBox", -"visible": "[not(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID'))]", -"label": "Custom OU path (Optional)", -"toolTip": "Provide OU where to locate session hosts, if not provided session hosts will be placed on the default (computers) OU.", -"placeholder": "Example: OU=session-hosts,OU=avd,DC=contoso,DC=com", -"constraints": {} -} -] -}, -{ -"name": "LocalAdminUsername", -"type": "Microsoft.Common.TextBox", -"label": "Local Administrator Username", -"toolTip": "Provide username for session host local admin account. Administrator can't be used as username, it is reserved by the system. The password is randomly generated at deployment time.", -"placeholder": "Example: avdadmin", -"defaultValue": "", -"constraints": { -"regex": "^(?!.*[aA]dministrator).*$", -"validationMessage": "This username can't be used, it is a reserved word.", -"required": true -} -} -] -}, -{ -"name": "optionalParametersStep", -"label": "Optional Parameters", -"elements": [ -{ -"name": "_Tag_IncludeInAutomation", -"type": "Microsoft.Common.TextBox", -"label": "Include in Automation Tag Name", -"toolTip": "The name of the tag to use to determine if an existing session host should be included in the automation. After deployment, if the tag is present and set to 'true', the session host will be included. If the tag is not present or set to 'false', the session host will be excluded.", -"defaultValue": "IncludeInAutoReplace", -"constraints": { -"required": false -} -}, -{ -"name": "_Tag_DeployTimestamp", -"type": "Microsoft.Common.TextBox", -"label": "Deploy Timestamp Tag Name", -"toolTip": "The name of the tag to use to determine when the session host was deployed. This is updated by the session host replacer function on new session hosts. After deployment, you can edit the value of this tag to force replace a VM.", -"defaultValue": "AutoReplaceDeployTimestamp", -"constraints": { -"required": false -} -}, -{ -"name": "_Tag_PendingDrainTimestamp", -"type": "Microsoft.Common.TextBox", -"label": "Pending Drain Timestamp Tag Name", -"toolTip": "The name of the tag to use to determine when the session host was marked for drain. This is updated by the session host replacer function on hosts pending deletion.", -"defaultValue": "AutoReplacePendingDrainTimestamp", -"constraints": { -"required": false -} -}, -{ -"name": "_Tag_ScalingPlanExclusionTag", -"type": "Microsoft.Common.TextBox", -"label": "Scaling Plan Exclusion Tag Name", -"toolTip": "The name of the tag session host replacer will set to exclude a session host from scaling plans actions.", -"defaultValue": "ScalingPlanExclusion", -"constraints": { -"required": false -} -}, -{ -"name": "_TargetVMAgeDays", -"type": "Microsoft.Common.TextBox", -"label": "Target VM Age (Days)", -"toolTip": "The maximum age of a VM in days before it is replaced. This is compared to the value of the Deploy Timestamp Tag.", -"defaultValue": 45, -"constraints": { -"required": false -} -}, -{ -"name": "_DrainGracePeriodHours", -"type": "Microsoft.Common.TextBox", -"label": "Drain Grace Period (Hours)", -"toolTip": "The number of hours to wait after marking a VM for drain before deleting it. This is to allow users to finish their sessions before the VM is deleted.", -"defaultValue": 24, -"constraints": { -"required": false -} -}, -{ -"name": "_FixSessionHostTags", -"type": "Microsoft.Common.CheckBox", -"label": "Fix Existing Session Host Tags", -"toolTip": "If enabled, the session host replacer will fix the tags on existing session hosts or if tags are mistakenly deleted. The tag values will NOT allow deletion of existing session hosts and must be changed post deployment. This is useful if you are deploying a new session host replacer to an existing host pool.", -"defaultValue": true, -"constraints": { -"required": "[steps('basics').HostPoolSettingsSection.IncludePreExistingSessionHosts]", -"validationMessage": "This is required if Include pre-existing session hosts is selected." -} -}, -{ -"name": "_SHRDeploymentPrefix", -"type": "Microsoft.Common.TextBox", -"label": "Deployment Prefix", -"toolTip": "The prefix of the deployment created in the session hosts resource group when replacement VMs are deploying. This is used to track running and failed deployments.", -"defaultValue": "AVDSessionHostReplacer", -"constraints": { -"required": false -} -}, -{ -"name": "_SessionHostInstanceNumberPadding", -"type": "Microsoft.Common.Slider", -"min": 1, -"max": 4, -"label": "Session Host VM Number Padding", -"defaultValue": 2, -"showStepMarkers": true, -"constraints": { -"required": false -}, -"visible": true -}, -{ -"name": "_SessionHostNameSeparator", -"type": "Microsoft.Common.CheckBox", -"label": "Use '-' as separator", -"toolTip": "If enabled, the session host replacer will use '-' as a separator between the prefix and the instance number. If disabled, the session host replacer will not use separator.", -"defaultValue": true, -"constraints": { -"required": false -} -}, -{ -"name": "SessionHostExampleNameInfoBox", -"type": "Microsoft.Common.InfoBox", -"visible": "[not(empty(steps('basics').HostPoolSettingsSection.sessionHostNamePrefix))]", -"options": { -"icon": "Info", -"text": "[concat('Example Session Host name: ', steps('basics').HostPoolSettingsSection.sessionHostNamePrefix , if(steps('optionalParametersStep')._SessionHostNameSeparator,'-','') , take('00000000', sub(steps('optionalParametersStep')._SessionHostInstanceNumberPadding,1)) ,'1') ]" -} -}, -{ -"name": "_ReplaceSessionHostOnNewImageVersion", -"type": "Microsoft.Common.CheckBox", -"label": "Replace Session Hosts On New Image Version", -"toolTip": "(Recommended) If enabled, the session host replacer will replace session hosts when a new image version is available. This works for both marketplace and custom images. If disabled, the session host replacer will only replace session hosts when the VM age is greater than the target VM age.", -"defaultValue": true, -"constraints": { -"required": false -} -}, -{ -"name": "_ReplaceSessionHostOnNewImageVersionDelayDays", -"type": "Microsoft.Common.TextBox", -"visible": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersion]", -"label": "Replace on New Image Version Delay (Days)", -"toolTip": "The number of days to wait after a new image is available before replacing session hosts. This is to allow time for the image to be tested before replacing session hosts.", -"defaultValue": "0", -"constraints": { -"required": false -} -}, -{ -"name": "_SessionHostResourceGroupName", -"type": "Microsoft.Common.TextBox", -"label": "Session Hosts Resource Group Name", -"placeholder": "Same As Host Pool Resource Group", -"toolTip": "Leave this empty to deploy to same resource group as the host pool.", -"defaultValue": "", -"constraints": { -"required": false -} -} -] -} -] -}, -"outputs": { -"kind": "ResourceGroup", -"resourceGroupId": "[steps('basics').resourceScope.resourceGroup.id]", -"location": "[steps('basics').resourceScope.location.name]", -"parameters": { -"HostPoolResourceGroupName": "[steps('basics').HostPoolSettingsSection.HostPoolSelector.resourceGroup]", -"HostPoolName": "[steps('basics').HostPoolSettingsSection.HostPoolSelector.name]", -"SessionHostNamePrefix": "[steps('basics').HostPoolSettingsSection.sessionHostNamePrefix]", -"UseUserAssignedManagedIdentity": "[if(steps('basics').IdentitySection.UseUserAssignedManagedIdentity, true, false)]", -"UserAssignedManagedIdentityResourceId": "[if(steps('basics').IdentitySection.UseUserAssignedManagedIdentity, steps('basics').IdentitySection.UserAssignedManagedIdentitySelector.id, '')]", -"TargetSessionHostCount": "[steps('basics').HostPoolSettingsSection.TargetSessionHostCount]", -"TargetSessionHostBuffer": "[steps('basics').HostPoolSettingsSection.TargetSessionHostBuffer]", -"EnableMonitoring": "[steps('basics').MonitoringSection.EnableMonitoring]", -"UseExistingLAW": "[steps('basics').MonitoringSection.UseExistingLAW]", -"LogAnalyticsWorkspaceId": "[if(steps('basics').MonitoringSection.UseExistingLAW, steps('basics').MonitoringSection.LAWSelector.id, '')]", -"SessionHostsRegion": "[steps('SessionHostsTemplate').SessionHostsRegion]", -"AvailabilityZones": "[steps('SessionHostsTemplate').AvailabilityZones]", -"SessionHostSize": "[steps('SessionHostsTemplate').SessionHostSize]", -"AcceleratedNetworking": "[steps('SessionHostsTemplate').AcceleratedNetworking]", -"SessionHostDiskType": "[steps('SessionHostsTemplate').SessionHostDiskType]", -"MarketPlaceOrCustomImage": "[steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage]", -"MarketPlaceImage": "[steps('SessionHostsTemplate').dropDownMarketPlaceImage]", -"GalleryImageId": "[steps('SessionHostsTemplate').resourceSelectorSessionHostGalleryImageId.id]", -"SecurityType": "[steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType]", -"SecureBootEnabled": "[steps('SessionHostsTemplate').sessionHostsSecuritySection.SecureBootEnabled]", -"TpmEnabled": "[steps('SessionHostsTemplate').sessionHostsSecuritySection.TpmEnabled]", -"IdentityServiceProvider": "[steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider]", -"IntuneEnrollment": "[steps('SessionHostsTemplate').DomainJoinSection.IntuneEnrollment]", -"SubnetId": "[steps('SessionHostsTemplate').SubnetId]", -"ADDomainName": "[steps('SessionHostsTemplate').DomainJoinSection.ADDomainName]", -"ADDomainJoinUserName": "[steps('SessionHostsTemplate').DomainJoinSection.ADDomainJoinUserName]", -"ADJoinUserPassword": "[steps('SessionHostsTemplate').DomainJoinSection.ADJoinUserPassword]", -"ADOUPath": "[steps('SessionHostsTemplate').DomainJoinSection.ADOUPath]", -"LocalAdminUsername": "[steps('SessionHostsTemplate').LocalAdminUsername]", -"TagIncludeInAutomation": "[steps('optionalParametersStep')._Tag_IncludeInAutomation]", -"TagDeployTimestamp": "[steps('optionalParametersStep')._Tag_DeployTimestamp]", -"TagPendingDrainTimestamp": "[steps('optionalParametersStep')._Tag_PendingDrainTimestamp]", -"TagScalingPlanExclusionTag": "[steps('optionalParametersStep')._Tag_ScalingPlanExclusionTag]", -"TargetVMAgeDays": "[steps('optionalParametersStep')._TargetVMAgeDays]", -"DrainGracePeriodHours": "[steps('optionalParametersStep')._DrainGracePeriodHours]", -"FixSessionHostTags": "[steps('optionalParametersStep')._FixSessionHostTags]", -"IncludePreExistingSessionHosts": "[steps('basics').HostPoolSettingsSection.IncludePreExistingSessionHosts]", -"DeploymentPrefix": "[steps('optionalParametersStep')._SHRDeploymentPrefix]", -"SessionHostInstanceNumberPadding": "[steps('optionalParametersStep')._SessionHostInstanceNumberPadding]", -"SessionHostNameSeparator": "[if(steps('optionalParametersStep')._SessionHostNameSeparator,'-','')]", -"ReplaceSessionHostOnNewImageVersion": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersion]", -"ReplaceSessionHostOnNewImageVersionDelayDays": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersionDelayDays]", -"VMNamesTemplateParameterName": "VMNames", -"SessionHostResourceGroupName": "[steps('optionalParametersStep')._SessionHostResourceGroupName]" -} -} -} + } + } + } + ] + }, + { + "name": "SessionHostsTemplate", + "label": "Session Hosts Template", + "elements": [ + { + "name": "SessionHostsRegion", + "type": "Microsoft.Common.DropDown", + "label": "Session Hosts Region", + "visible": true, + "filter": true, + "multiselect": false, + "selectAll": false, + "toolTip": "Select region to deploy session hosts.", + "constraints": { + "required": true, + "allowedValues": "[map( first( map( filter( steps('basics').computeApi.value, (resourceTypes) => equals(resourceTypes.resourceType, 'virtualMachines') ), (item) => item.locations) ), (item) => parse(concat('{\"label\":\"', item, '\",\"value\":\"', toLower(replace(item, ' ', '')), '\"}')) )]" + } + }, + { + "name": "AvailabilityZones", + "type": "Microsoft.Common.DropDown", + "label": "Availability Zones", + "visible": true, + "filter": false, + "defaultValue": [], + "multiselect": true, + "selectAll": false, + "toolTip": "Select Availability Zones for the session hosts. Make sure the selected size is available in the selected zones. Session hosts will be deployed across the zones.", + "constraints": { + "required": false, + "allowedValues": [ + { + "label": "Zone 1", + "value": "1" + }, + { + "label": "Zone 2", + "value": "2" + }, + { + "label": "Zone 3", + "value": "3" + } + ] + } + }, + { + "name": "SessionHostSize", + "type": "Microsoft.Compute.SizeSelector", + "label": "VM Size", + "toolTip": "", + "recommendedSizes": [ + "Standard_D4ads_v5" + ], + "constraints": { + "allowedSizes": [], + "excludedSizes": [], + "required": true + }, + "options": { + "hideDiskTypeFilter": true + }, + "osPlatform": "Windows", + "imageReference": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "Windows-11", + "sku": "win11-23h2-avd" + } + }, + { + "name": "AcceleratedNetworking", + "type": "Microsoft.Common.CheckBox", + "label": "Enable accelerated networking", + "defaultValue": true, + "toolTip": "Enables low latency and high throughput on the network interface." + }, + { + "name": "SessionHostDiskType", + "type": "Microsoft.Common.DropDown", + "label": "OS Disk type", + "filter": false, + "defaultValue": "Premium SSD", + "toolTip": "Select session host disk type to host the OS.", + "constraints": { + "required": true, + "allowedValues": [ + { + "label": "Standard HDD", + "value": "Standard_LRS" + }, + { + "label": "Standard SSD", + "value": "StandardSSD_LRS" + }, + { + "label": "Premium SSD", + "value": "Premium_LRS" + } + ] + } + }, + { + "name": "optionMarketPlaceOrCustomImage", + "type": "Microsoft.Common.OptionsGroup", + "label": "Image Source", + "defaultValue": "Marketplace", + "toolTip": "", + "constraints": { + "allowedValues": [ + { + "label": "Marketplace", + "value": "Marketplace" + }, + { + "label": "Gallery Image", + "value": "Gallery" + } + ], + "required": true + }, + "visible": true + }, + { + "name": "dropDownMarketPlaceImage", + "type": "Microsoft.Common.DropDown", + "label": "Select a Marketplace Image", + "placeholder": "", + "defaultValue": "Windows 11 Enterprise 23h2 multi-session + Microsoft 365 Apps", + "toolTip": "Marketplace images are updated on monthly basis. The Session Host Replacer will replace the session hosts when a new image is available.", + "multiselect": false, + "selectAll": false, + "filter": true, + "filterPlaceholder": "Filter items ...", + "multiLine": true, + "constraints": { + "allowedValues": [ + { + "label": "Windows 10 Enterprise 21h2 multi-session", + "value": "win10-21h2-avd" + }, + { + "label": "Windows 10 Enterprise 21h2 multi-session (Gen 2)", + "value": "win10-21h2-avd-g2" + }, + { + "label": "Windows 10 Enterprise 21h2 multi-session + Microsoft 365 Apps", + "value": "win10-21h2-avd-m365" + }, + { + "label": "Windows 10 Enterprise 21h2 multi-session + Microsoft 365 Apps (Gen 2)", + "value": "win10-21h2-avd-m365-g2" + }, + { + "label": "Windows 10 Enterprise 22h2 multi-session", + "value": "win10-22h2-avd" + }, + { + "label": "Windows 10 Enterprise 22h2 multi-session (Gen 2)", + "value": "win10-22h2-avd-g2" + }, + { + "label": "Windows 10 Enterprise 22h2 multi-session + Microsoft 365 Apps", + "value": "win10-22h2-avd-m365" + }, + { + "label": "Windows 10 Enterprise 22h2 multi-session + Microsoft 365 Apps (Gen 2)", + "value": "win10-22h2-avd-m365-g2" + }, + { + "label": "Windows 11 Enterprise 21h2 multi-session", + "value": "win11-21h2-avd" + }, + { + "label": "Windows 11 Enterprise 21h2 multi-session + Microsoft 365 Apps", + "value": "win11-21h2-avd-m365" + }, + { + "label": "Windows 11 Enterprise 22h2 multi-session", + "value": "win11-22h2-avd" + }, + { + "label": "Windows 11 Enterprise 22h2 multi-session + Microsoft 365 Apps", + "value": "win11-22h2-avd-m365" + }, + { + "label": "Windows 11 Enterprise 23h2 multi-session", + "value": "win11-23h2-avd" + }, + { + "label": "Windows 11 Enterprise 23h2 multi-session + Microsoft 365 Apps", + "value": "win11-23h2-avd-m365" + } + ], + "required": true + }, + "visible": "[equals(steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage,'Marketplace')]" + }, + { + "name": "GalleryImageInfoBox", + "type": "Microsoft.Common.InfoBox", + "visible": "[equals(steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage,'Gallery')]", + "options": { + "icon": "Warning", + "text": "The system identity of the Session Host Replacer function is assigned the 'Desktop Virtualization Virtual Machine Contributor' role against the subscription. If the Image Definition is in a different subscription, please make sure you manually assign the permission post deployment." + } + }, + { + "name": "resourceSelectorSessionHostGalleryImageId", + "type": "Microsoft.Solutions.ResourceSelector", + "label": "Select Gallery Image", + "resourceType": "Microsoft.Compute/galleries/images", + "constraints": { + "required": true + }, + "options": { + "filter": {} + }, + "visible": "[equals(steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage,'Gallery')]" + }, + { + "name": "sessionHostsSecuritySection", + "type": "Microsoft.Common.Section", + "visible": true, + "label": "Security profile", + "elements": [ + { + "name": "SecurityType", + "type": "Microsoft.Common.DropDown", + "label": "Security type", + "filter": true, + "defaultValue": "Trusted Launch Virtual Machines", + "toolTip": "Choose a type of security that matches your needs: Trusted launch virtual machines provide additional security features on Gen2 virtual machines to protect against persistent and advanced attacks.", + "constraints": { + "required": true, + "allowedValues": [ + { + "label": "Standard", + "value": "Standard" + }, + { + "label": "Trusted Launch Virtual Machines", + "value": "TrustedLaunch" + }, + { + "label": "Confidential Virtual Machines", + "value": "ConfidentialVM" + } + ] + } + }, + { + "name": "SecureBootEnabled", + "type": "Microsoft.Common.CheckBox", + "visible": "[or(equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'TrustedLaunch'), equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'ConfidentialVM'))]", + "label": "Enable secure boot", + "defaultValue": true, + "toolTip": "Secure boot helps protect your VMs against boot kits, rootkits, and kernel-level malware." + }, + { + "name": "TpmEnabled", + "type": "Microsoft.Common.CheckBox", + "visible": "[or(equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'TrustedLaunch'), equals(steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType, 'ConfidentialVM'))]", + "label": "Enable vTPM", + "defaultValue": true, + "toolTip": "Virtual Trusted Platform Module (vTPM) is TPM2.0 compliant and validates your VM boot integrity apart from securely storing keys and secrets." + } + ] + }, + { + "name": "SubnetId", + "type": "Microsoft.Common.TextBox", + "label": "Subnet ID", + "visible": true, + "placeholder": "Add resource id of subnet in the same region as the Session Hosts", + "constraints": { + "required": true, + "validations": [ + { + "regex": ".*/subnets/[A-Za-z0-9_\\.-]+$", + "message": "Invalid Subnet Resource ID. Make sure it ends with /subnets/SubnetName" + } + ] + } + }, + { + "name": "DomainJoinSection", + "type": "Microsoft.Common.Section", + "visible": true, + "label": "Domain Join", + "elements": [ + { + "name": "IdentityServiceProvider", + "type": "Microsoft.Common.OptionsGroup", + "visible": true, + "label": "Identity service provider", + "defaultValue": "Microsoft Entra ID", + "toolTip": "Identity service provider (Active Directory or EntraDS) that already exist and will be used for Azure Virtual Desktop.", + "constraints": { + "required": true, + "allowedValues": [ + { + "label": "Microsoft Entra ID", + "value": "EntraID" + }, + { + "label": "Active Directory (AD DS)", + "value": "ActiveDirectory" + }, + { + "label": "Microsoft Entra Domain Services", + "value": "EntraDS" + } + ] + } + }, + { + "name": "EntraJoinedInfoBox", + "type": "Microsoft.Common.InfoBox", + "visible": "[equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID')]", + "options": { + "icon": "Warning", + "text": "When the VMs are Entra Joined, session host replacer will attempt to delete its device object from Entra ID. This requires additional permissions to be granted to the service principal used by session host replacer. Please refer to the documentation for more information.", + "uri": "https://github.com/Azure/AVDSessionHostReplacer/blob/main/docs/Permissions.md" + } + }, + { + "name": "IntuneEnrollment", + "type": "Microsoft.Common.CheckBox", + "visible": "[equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID')]", + "label": "Intune enrollment", + "defaultValue": false, + "toolTip": "If Intune is configured in your Microsoft Entra ID tenant, you can choose to have the VM automatically enrolled during the deployment by selecting this box. Session Host Replacer will delete the device from Intune during replacement." + }, + { + "name": "ADDomainName", + "type": "Microsoft.Common.TextBox", + "label": "AD Domain name", + "visible": "[or(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'ActiveDirectory'), equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraDS'))]", + "placeholder": "Example: contoso.com", + "constraints": { + "required": true + } + }, + { + "name": "ADDomainJoinUserName", + "type": "Microsoft.Common.TextBox", + "label": "User principal name", + "visible": "[not(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID'))]", + "toolTip": "Provide username with permissions to join session host to the domain.", + "placeholder": "Example: avdadmin@contoso.com", + "defaultValue": "", + "constraints": { + "required": true + } + }, + { + "name": "ADJoinUserPassword", + "type": "Microsoft.Common.PasswordBox", + "label": { + "password": "Password" + }, + "visible": "[not(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID'))]", + "toolTip": "Provide password for domain join account. This will be stored in a new Azure Key Vault.", + "constraints": { + "required": true + }, + "options": { + "hideConfirmation": true + } + }, + { + "name": "ADOUPath", + "type": "Microsoft.Common.TextBox", + "visible": "[not(equals(steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider, 'EntraID'))]", + "label": "Custom OU path (Optional)", + "toolTip": "Provide OU where to locate session hosts, if not provided session hosts will be placed on the default (computers) OU.", + "placeholder": "Example: OU=session-hosts,OU=avd,DC=contoso,DC=com", + "constraints": {} + } + ] + }, + { + "name": "LocalAdminUsername", + "type": "Microsoft.Common.TextBox", + "label": "Local Administrator Username", + "toolTip": "Provide username for session host local admin account. Administrator can't be used as username, it is reserved by the system. The password is randomly generated at deployment time.", + "placeholder": "Example: avdadmin", + "defaultValue": "", + "constraints": { + "regex": "^(?!.*[aA]dministrator).*$", + "validationMessage": "This username can't be used, it is a reserved word.", + "required": true + } + } + ] + }, + { + "name": "optionalParametersStep", + "label": "Optional Parameters", + "elements": [ + { + "name": "_Tag_IncludeInAutomation", + "type": "Microsoft.Common.TextBox", + "label": "Include in Automation Tag Name", + "toolTip": "The name of the tag to use to determine if an existing session host should be included in the automation. After deployment, if the tag is present and set to 'true', the session host will be included. If the tag is not present or set to 'false', the session host will be excluded.", + "defaultValue": "IncludeInAutoReplace", + "constraints": { + "required": false + } + }, + { + "name": "_Tag_DeployTimestamp", + "type": "Microsoft.Common.TextBox", + "label": "Deploy Timestamp Tag Name", + "toolTip": "The name of the tag to use to determine when the session host was deployed. This is updated by the session host replacer function on new session hosts. After deployment, you can edit the value of this tag to force replace a VM.", + "defaultValue": "AutoReplaceDeployTimestamp", + "constraints": { + "required": false + } + }, + { + "name": "_Tag_PendingDrainTimestamp", + "type": "Microsoft.Common.TextBox", + "label": "Pending Drain Timestamp Tag Name", + "toolTip": "The name of the tag to use to determine when the session host was marked for drain. This is updated by the session host replacer function on hosts pending deletion.", + "defaultValue": "AutoReplacePendingDrainTimestamp", + "constraints": { + "required": false + } + }, + { + "name": "_Tag_ScalingPlanExclusionTag", + "type": "Microsoft.Common.TextBox", + "label": "Scaling Plan Exclusion Tag Name", + "toolTip": "The name of the tag session host replacer will set to exclude a session host from scaling plans actions.", + "defaultValue": "ScalingPlanExclusion", + "constraints": { + "required": false + } + }, + { + "name": "_TargetVMAgeDays", + "type": "Microsoft.Common.TextBox", + "label": "Target VM Age (Days)", + "toolTip": "The maximum age of a VM in days before it is replaced. This is compared to the value of the Deploy Timestamp Tag.", + "defaultValue": 45, + "constraints": { + "required": false + } + }, + { + "name": "_DrainGracePeriodHours", + "type": "Microsoft.Common.TextBox", + "label": "Drain Grace Period (Hours)", + "toolTip": "The number of hours to wait after marking a VM for drain before deleting it. This is to allow users to finish their sessions before the VM is deleted.", + "defaultValue": 24, + "constraints": { + "required": false + } + }, + { + "name": "_FixSessionHostTags", + "type": "Microsoft.Common.CheckBox", + "label": "Fix Existing Session Host Tags", + "toolTip": "If enabled, the session host replacer will fix the tags on existing session hosts or if tags are mistakenly deleted. The tag values will NOT allow deletion of existing session hosts and must be changed post deployment. This is useful if you are deploying a new session host replacer to an existing host pool.", + "defaultValue": true, + "constraints": { + "required": "[steps('basics').HostPoolSettingsSection.IncludePreExistingSessionHosts]", + "validationMessage": "This is required if Include pre-existing session hosts is selected." + } + }, + { + "name": "_SHRDeploymentPrefix", + "type": "Microsoft.Common.TextBox", + "label": "Deployment Prefix", + "toolTip": "The prefix of the deployment created in the session hosts resource group when replacement VMs are deploying. This is used to track running and failed deployments.", + "defaultValue": "AVDSessionHostReplacer", + "constraints": { + "required": false + } + }, + { + "name": "_SessionHostInstanceNumberPadding", + "type": "Microsoft.Common.Slider", + "min": 1, + "max": 4, + "label": "Session Host VM Number Padding", + "defaultValue": 2, + "showStepMarkers": true, + "constraints": { + "required": false + }, + "visible": true + }, + { + "name": "_SessionHostNameSeparator", + "type": "Microsoft.Common.CheckBox", + "label": "Use '-' as separator", + "toolTip": "If enabled, the session host replacer will use '-' as a separator between the prefix and the instance number. If disabled, the session host replacer will not use separator.", + "defaultValue": true, + "constraints": { + "required": false + } + }, + { + "name": "SessionHostExampleNameInfoBox", + "type": "Microsoft.Common.InfoBox", + "visible": "[not(empty(steps('basics').HostPoolSettingsSection.sessionHostNamePrefix))]", + "options": { + "icon": "Info", + "text": "[concat('Example Session Host name: ', steps('basics').HostPoolSettingsSection.sessionHostNamePrefix , if(steps('optionalParametersStep')._SessionHostNameSeparator,'-','') , take('00000000', sub(steps('optionalParametersStep')._SessionHostInstanceNumberPadding,1)) ,'1') ]" + } + }, + { + "name": "_ReplaceSessionHostOnNewImageVersion", + "type": "Microsoft.Common.CheckBox", + "label": "Replace Session Hosts On New Image Version", + "toolTip": "(Recommended) If enabled, the session host replacer will replace session hosts when a new image version is available. This works for both marketplace and custom images. If disabled, the session host replacer will only replace session hosts when the VM age is greater than the target VM age.", + "defaultValue": true, + "constraints": { + "required": false + } + }, + { + "name": "_ReplaceSessionHostOnNewImageVersionDelayDays", + "type": "Microsoft.Common.TextBox", + "visible": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersion]", + "label": "Replace on New Image Version Delay (Days)", + "toolTip": "The number of days to wait after a new image is available before replacing session hosts. This is to allow time for the image to be tested before replacing session hosts.", + "defaultValue": "0", + "constraints": { + "required": false + } + }, + { + "name": "_SessionHostResourceGroupName", + "type": "Microsoft.Common.TextBox", + "label": "Session Hosts Resource Group Name", + "placeholder": "Same As Host Pool Resource Group", + "toolTip": "Leave this empty to deploy to same resource group as the host pool.", + "defaultValue": "", + "constraints": { + "required": false + } + } + ] + } + ] + }, + "outputs": { + "kind": "ResourceGroup", + "resourceGroupId": "[steps('basics').resourceScope.resourceGroup.id]", + "location": "[steps('basics').resourceScope.location.name]", + "parameters": { + "HostPoolResourceGroupName": "[steps('basics').HostPoolSettingsSection.HostPoolSelector.resourceGroup]", + "HostPoolName": "[steps('basics').HostPoolSettingsSection.HostPoolSelector.name]", + "SessionHostNamePrefix": "[steps('basics').HostPoolSettingsSection.sessionHostNamePrefix]", + "UseUserAssignedManagedIdentity": "[if(steps('basics').IdentitySection.UseUserAssignedManagedIdentity, true, false)]", + "UserAssignedManagedIdentityResourceId": "[if(steps('basics').IdentitySection.UseUserAssignedManagedIdentity, steps('basics').IdentitySection.UserAssignedManagedIdentitySelector.id, '')]", + "TargetSessionHostCount": "[steps('basics').HostPoolSettingsSection.TargetSessionHostCount]", + "TargetSessionHostBuffer": "[steps('basics').HostPoolSettingsSection.TargetSessionHostBuffer]", + "EnableMonitoring": "[steps('basics').MonitoringSection.EnableMonitoring]", + "UseExistingLAW": "[steps('basics').MonitoringSection.UseExistingLAW]", + "LogAnalyticsWorkspaceId": "[if(steps('basics').MonitoringSection.UseExistingLAW, steps('basics').MonitoringSection.LAWSelector.id, '')]", + "SessionHostsRegion": "[steps('SessionHostsTemplate').SessionHostsRegion]", + "AvailabilityZones": "[steps('SessionHostsTemplate').AvailabilityZones]", + "SessionHostSize": "[steps('SessionHostsTemplate').SessionHostSize]", + "AcceleratedNetworking": "[steps('SessionHostsTemplate').AcceleratedNetworking]", + "SessionHostDiskType": "[steps('SessionHostsTemplate').SessionHostDiskType]", + "MarketPlaceOrCustomImage": "[steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage]", + "MarketPlaceImage": "[steps('SessionHostsTemplate').dropDownMarketPlaceImage]", + "GalleryImageId": "[steps('SessionHostsTemplate').resourceSelectorSessionHostGalleryImageId.id]", + "SecurityType": "[steps('SessionHostsTemplate').sessionHostsSecuritySection.SecurityType]", + "SecureBootEnabled": "[steps('SessionHostsTemplate').sessionHostsSecuritySection.SecureBootEnabled]", + "TpmEnabled": "[steps('SessionHostsTemplate').sessionHostsSecuritySection.TpmEnabled]", + "IdentityServiceProvider": "[steps('SessionHostsTemplate').DomainJoinSection.IdentityServiceProvider]", + "IntuneEnrollment": "[steps('SessionHostsTemplate').DomainJoinSection.IntuneEnrollment]", + "SubnetId": "[steps('SessionHostsTemplate').SubnetId]", + "ADDomainName": "[steps('SessionHostsTemplate').DomainJoinSection.ADDomainName]", + "ADDomainJoinUserName": "[steps('SessionHostsTemplate').DomainJoinSection.ADDomainJoinUserName]", + "ADJoinUserPassword": "[steps('SessionHostsTemplate').DomainJoinSection.ADJoinUserPassword]", + "ADOUPath": "[steps('SessionHostsTemplate').DomainJoinSection.ADOUPath]", + "LocalAdminUsername": "[steps('SessionHostsTemplate').LocalAdminUsername]", + "TagIncludeInAutomation": "[steps('optionalParametersStep')._Tag_IncludeInAutomation]", + "TagDeployTimestamp": "[steps('optionalParametersStep')._Tag_DeployTimestamp]", + "TagPendingDrainTimestamp": "[steps('optionalParametersStep')._Tag_PendingDrainTimestamp]", + "TagScalingPlanExclusionTag": "[steps('optionalParametersStep')._Tag_ScalingPlanExclusionTag]", + "TargetVMAgeDays": "[steps('optionalParametersStep')._TargetVMAgeDays]", + "DrainGracePeriodHours": "[steps('optionalParametersStep')._DrainGracePeriodHours]", + "FixSessionHostTags": "[steps('optionalParametersStep')._FixSessionHostTags]", + "IncludePreExistingSessionHosts": "[steps('basics').HostPoolSettingsSection.IncludePreExistingSessionHosts]", + "DeploymentPrefix": "[steps('optionalParametersStep')._SHRDeploymentPrefix]", + "SessionHostInstanceNumberPadding": "[steps('optionalParametersStep')._SessionHostInstanceNumberPadding]", + "SessionHostNameSeparator": "[if(steps('optionalParametersStep')._SessionHostNameSeparator,'-','')]", + "ReplaceSessionHostOnNewImageVersion": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersion]", + "ReplaceSessionHostOnNewImageVersionDelayDays": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersionDelayDays]", + "VMNamesTemplateParameterName": "VMNames", + "SessionHostResourceGroupName": "[steps('optionalParametersStep')._SessionHostResourceGroupName]" + } + } + } } From 6beeaf34f458d8d1ec656f9c920d187427a009b2 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:39:42 +0100 Subject: [PATCH 64/83] Rename FunctionAppZipUrl to FunctionAppUrl --- deploy/arm/DeployAVDSessionHostReplacer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 24c542f..09086b3 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -595,9 +595,9 @@ "description": "Required: Yes | Name of the Function App." } }, - "FunctionAppZipUrl": { + "FunctionAppUrl": { "type": "string", - "defaultValue": "https://github.com/Azure/AVDSessionHostReplacer/releases/download/v0.3.1-beta.3/FunctionApp.zip", + "defaultValue": "https://github.com/stefze/AVDSessionHostReplacer/blob/main/FunctionApp/FunctionApp.zip", "metadata": { "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." } From b4587c6d2266efd0815369f3aae9591baba5583c Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:42:13 +0100 Subject: [PATCH 65/83] Add DNS settings to portal-ui configuration Added DNS settings section with primary and secondary DNS fields. --- deploy/portal-ui/portal-ui.json | 50 ++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/deploy/portal-ui/portal-ui.json b/deploy/portal-ui/portal-ui.json index 9a0936c..5697af1 100644 --- a/deploy/portal-ui/portal-ui.json +++ b/deploy/portal-ui/portal-ui.json @@ -12,6 +12,8 @@ { "name": "resourceScope", "type": "Microsoft.Common.ResourceScope", + "subscription": {}, + "resourceGroup": {}, "location": { "resourceTypes": ["Microsoft.DesktopVirtualization/HostPools" ] @@ -303,6 +305,21 @@ ] } }, + { + "name": "DiskSizeGB", + "type": "Microsoft.Common.Slider", + "min": 64, + "max": 512, + "label": "OS Disk Size", + "subLabel": "GB", + "defaultValue": 128, + "showStepMarkers": false, + "toolTip": "Size of the OS disk in GB.", + "constraints": { + "required": true + }, + "visible": true + }, { "name": "optionMarketPlaceOrCustomImage", "type": "Microsoft.Common.OptionsGroup", @@ -486,6 +503,35 @@ ] } }, + { + "name": "DnsSettings", + "type": "Microsoft.Common.Section", + "label": "DNS settings", + "elements": [ + { + "name": "DnsServer1", + "type": "Microsoft.Common.TextBox", + "label": "Primary DNS (IPv4)", + "placeholder": "e.g., 10.0.0.4", + "constraints": { + "required": true, + "regex": "^((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$", + "validationMessage": "Enter a valid IPv4 address (0-255 in each octet)." + } + }, + { + "name": "DnsServer2", + "type": "Microsoft.Common.TextBox", + "label": "Secondary DNS (IPv4)", + "placeholder": "e.g., 10.0.0.5", + "constraints": { + "required": true, + "regex": "^((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)$", + "validationMessage": "Enter a valid IPv4 address (0-255 in each octet)." + } + } + ] + }, { "name": "DomainJoinSection", "type": "Microsoft.Common.Section", @@ -771,6 +817,7 @@ "SessionHostSize": "[steps('SessionHostsTemplate').SessionHostSize]", "AcceleratedNetworking": "[steps('SessionHostsTemplate').AcceleratedNetworking]", "SessionHostDiskType": "[steps('SessionHostsTemplate').SessionHostDiskType]", + "DiskSizeGB": "[int(steps('SessionHostsTemplate').DiskSizeGB)]", "MarketPlaceOrCustomImage": "[steps('SessionHostsTemplate').optionMarketPlaceOrCustomImage]", "MarketPlaceImage": "[steps('SessionHostsTemplate').dropDownMarketPlaceImage]", "GalleryImageId": "[steps('SessionHostsTemplate').resourceSelectorSessionHostGalleryImageId.id]", @@ -799,7 +846,8 @@ "ReplaceSessionHostOnNewImageVersion": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersion]", "ReplaceSessionHostOnNewImageVersionDelayDays": "[steps('optionalParametersStep')._ReplaceSessionHostOnNewImageVersionDelayDays]", "VMNamesTemplateParameterName": "VMNames", - "SessionHostResourceGroupName": "[steps('optionalParametersStep')._SessionHostResourceGroupName]" + "SessionHostResourceGroupName": "[steps('optionalParametersStep')._SessionHostResourceGroupName]", + "DnsServers": "[createArray(steps('SessionHostsTemplate').DnsSettings.DnsServer1, steps('SessionHostsTemplate').DnsSettings.DnsServer2)]" } } } From 6820385b5b71531b63d4d584d4506609a4a0aa27 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:48:22 +0100 Subject: [PATCH 66/83] Update print statement to say 'Goodbye World' --- deploy/arm/DeployAVDSessionHostReplacer.json | 851 ++++++++++--------- 1 file changed, 454 insertions(+), 397 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 09086b3..52f0df2 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -60,6 +60,13 @@ "Premium_LRS" ] }, + "DiskSizeGB": { + "type": "int", + "defaultValue": 128, + "metadata": { + "description": "OS disk size in GB" + } + }, "MarketPlaceOrCustomImage": { "type": "string", "defaultValue": "Marketplace", @@ -113,6 +120,13 @@ "type": "string", "defaultValue": "" }, + "DnsServers": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional: List of DNS server IP addresses to configure on the network interface. Leave empty to use Azure DNS." + } + }, "IdentityServiceProvider": { "type": "string", "defaultValue": "EntraID", @@ -464,7 +478,7 @@ }, { "name": "_SessionHostParameters", - "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" + "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'DnsServers', parameters('DnsServers'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" }, { "name": "_SubscriptionId", @@ -862,415 +876,458 @@ }, "variables": { "$fxv#0": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.4.45862", + "templateHash": "13792602582245425536" + } + }, + "parameters": { + "Location": { + "type": "string", + "defaultValue": "[[resourceGroup().location]" + }, + "AvailabilityZones": { + "type": "array", + "defaultValue": [] + }, + "VMNames": { + "type": "array" + }, + "VMNamePrefixLength": { + "type": "int" + }, + "VMSize": { + "type": "string" + }, + "SubnetID": { + "type": "string" + }, + "AdminUsername": { + "type": "string" + }, + "AcceleratedNetworking": { + "type": "bool" + }, + "DiskType": { + "type": "string" + }, + "DiskSizeGB": { + "type": "int", + "defaultValue": 64, + "metadata": { + "description": "OS disk size in GB" + } + }, + "DnsServers": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "List of DNS server IP addresses to set on the NIC" + } + }, + "Tags": { + "type": "object", + "defaultValue": {} + }, + "ImageReference": { + "type": "object" + }, + "SecurityProfile": { + "type": "object", + "defaultValue": {} + }, + "HostPoolName": { + "type": "string" + }, + "HostPoolToken": { + "type": "securestring" + }, + "WVDArtifactsURL": { + "type": "string", + "defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" + }, + "DomainJoinObject": { + "type": "object", + "defaultValue": {} + }, + "DomainJoinPassword": { + "type": "securestring", + "defaultValue": "" + } + }, + "resources": [ + { + "copy": { + "name": "deploySessionHosts", + "count": "[[length(parameters('VMNames'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "AcceleratedNetworking": { + "value": "[[parameters('AcceleratedNetworking')]" + }, + "AdminUsername": { + "value": "[[parameters('AdminUsername')]" + }, + "HostPoolName": { + "value": "[[parameters('HostPoolName')]" + }, + "HostPoolToken": { + "value": "[[parameters('HostPoolToken')]" + }, + "ImageReference": { + "value": "[[parameters('ImageReference')]" + }, + "SecurityProfile": { + "value": "[[parameters('SecurityProfile')]" + }, + "SubnetID": { + "value": "[[parameters('SubnetID')]" + }, + "VMName": { + "value": "[[parameters('VMNames')[copyIndex()]]" + }, + "VMNamePrefixLength": { + "value": "[[parameters('VMNamePrefixLength')]" + }, + "VMSize": { + "value": "[[parameters('VMSize')]" + }, + "DiskType": { + "value": "[[parameters('DiskType')]" + }, + "DiskSizeGB": { + "value": "[[parameters('DiskSizeGB')]" + }, + "DnsServers": { + "value": "[[parameters('DnsServers')]" + }, + "WVDArtifactsURL": { + "value": "[[parameters('WVDArtifactsURL')]" + }, + "DomainJoinObject": { + "value": "[[parameters('DomainJoinObject')]" + }, + "DomainJoinPassword": { + "value": "[[parameters('DomainJoinPassword')]" + }, + "Location": { + "value": "[[parameters('Location')]" + }, + "AvailabilityZones": { + "value": "[[parameters('AvailabilityZones')]" + }, + "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.32.4.45862", + "templateHash": "9402242463429439832" + } + }, + "parameters": { + "VMName": { + "type": "string" + }, + "VMNamePrefixLength": { + "type": "int" + }, + "VMSize": { + "type": "string" + }, + "DiskType": { + "type": "string" + }, + "DiskSizeGB": { + "type": "int", + "defaultValue": 128, "metadata": { - "_generator": { - "name": "bicep", - "version": "0.29.47.4906", - "templateHash": "8994538122455151797" + "description": "OS disk size in GB" + } + }, + "DnsServers": { + "type": "array", + "metadata": { + "description": "List of DNS server IP addresses to set on the NIC" + } + }, + "Location": { + "type": "string", + "defaultValue": "[[resourceGroup().location]" + }, + "AvailabilityZones": { + "type": "array", + "defaultValue": [] + }, + "SubnetID": { + "type": "string" + }, + "AdminUsername": { + "type": "string" + }, + "AdminPassword": { + "type": "securestring", + "defaultValue": "[[newGuid()]" + }, + "AcceleratedNetworking": { + "type": "bool" + }, + "Tags": { + "type": "object", + "defaultValue": {} + }, + "ImageReference": { + "type": "object" + }, + "SecurityProfile": { + "type": "object" + }, + "HostPoolName": { + "type": "string" + }, + "HostPoolToken": { + "type": "securestring" + }, + "WVDArtifactsURL": { + "type": "string" + }, + "DomainJoinObject": { + "type": "object", + "defaultValue": {} + }, + "DomainJoinPassword": { + "type": "securestring", + "defaultValue": "" + } + }, + "variables": { + "varRequireNvidiaGPU": "[[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", + "varVMNumber": "[[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", + "varAvailabilityZone": "[[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" + }, + "resources": [ + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'deployIntegrityMonitoring')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Azure.Security.WindowsAttestation", + "type": "GuestAttestation", + "typeHandlerVersion": "1.0", + "autoUpgradeMinorVersion": true, + "settings": { + "AttestationConfig": { + "MaaSettings": { + "maaEndpoint": "", + "maaTenantName": "Guest Attestation" + }, + "AscSettings": { + "ascReportingEndpoint": "", + "ascReportingFrequency": "" + }, + "useCustomToken": "false", + "disableAlerts": "false" + } } }, - "parameters": { - "Location": { - "type": "string", - "defaultValue": "[[resourceGroup().location]" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "VMNames": { - "type": "array" - }, - "VMNamePrefixLength": { - "type": "int" - }, - "VMSize": { - "type": "string" - }, - "SubnetID": { - "type": "string" - }, - "AdminUsername": { - "type": "string" - }, - "AcceleratedNetworking": { - "type": "bool" - }, - "DiskType": { - "type": "string" - }, - "Tags": { - "type": "object", - "defaultValue": {} - }, - "ImageReference": { - "type": "object" + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[variables('varRequireNvidiaGPU')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.HpcCompute", + "type": "NvidiaGpuDriverWindows", + "typeHandlerVersion": "1.6", + "autoUpgradeMinorVersion": true + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployIntegrityMonitoring')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[not(equals(parameters('HostPoolName'), ''))]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.PowerShell", + "type": "DSC", + "typeHandlerVersion": "2.77", + "autoUpgradeMinorVersion": true, + "settings": { + "modulesUrl": "[[parameters('WVDArtifactsURL')]", + "configurationFunction": "Configuration.ps1\\AddSessionHost", + "properties": { + "hostPoolName": "[[parameters('HostPoolName')]", + "registrationInfoToken": "[[parameters('HostPoolToken')]", + "aadJoin": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", + "useAgentDownloadEndpoint": true, + "mdmId": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" + } + } + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Azure.ActiveDirectory", + "type": "AADLoginForWindows", + "typeHandlerVersion": "2.0", + "autoUpgradeMinorVersion": true, + "settings": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Compute", + "type": "JSonADDomainExtension", + "typeHandlerVersion": "1.3", + "autoUpgradeMinorVersion": true, + "settings": { + "Name": "[[parameters('DomainJoinObject').DomainName]", + "OUPath": "[[parameters('DomainJoinObject').ADOUPath]", + "User": "[[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", + "Restart": "true", + "Options": 3 }, - "SecurityProfile": { - "type": "object", - "defaultValue": {} + "protectedSettings": { + "Password": "[[parameters('DomainJoinPassword')]" + } + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + +{ + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2023-09-01", + "name": "[[format('{0}-vNIC', parameters('VMName'))]", + "location": "[[parameters('Location')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[[parameters('SubnetID')]" + } + } + } + ], + +"dnsSettings": { + "dnsServers": "[[parameters('DnsServers')]" +}, + "enableAcceleratedNetworking": "[[parameters('AcceleratedNetworking')]" + }, + "tags": "[[parameters('Tags')]" +}, + + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2023-09-01", + "name": "[[parameters('VMName')]", + "location": "[[parameters('Location')]", + "zones": "[[variables('varAvailabilityZone')]", + "identity": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", + "properties": { + "osProfile": { + "computerName": "[[parameters('VMName')]", + "adminUsername": "[[parameters('AdminUsername')]", + "adminPassword": "[[parameters('AdminPassword')]" }, - "HostPoolName": { - "type": "string" + "hardwareProfile": { + "vmSize": "[[parameters('VMSize')]" }, - "HostPoolToken": { - "type": "securestring" + "additionalCapabilities": { + "hibernationEnabled": true }, - "WVDArtifactsURL": { - "type": "string", - "defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" + "storageProfile": { + "osDisk": { + "name": "[[format('{0}-OSDisk', parameters('VMName'))]", + "createOption": "FromImage", + "deleteOption": "Delete", + "diskSizeGB": "[[parameters('DiskSizeGB')]", + "managedDisk": { + "storageAccountType": "[[parameters('DiskType')]" + } + }, + "ImageReference": "[[parameters('ImageReference')]" }, - "DomainJoinObject": { - "type": "object", - "defaultValue": {} + "securityProfile": "[[parameters('SecurityProfile')]", + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": true + } }, - "DomainJoinPassword": { - "type": "securestring", - "defaultValue": "" - } - }, - "resources": [ - { - "[string('copy')]": { - "name": "deploySessionHosts", - "count": "[[length(parameters('VMNames'))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "AcceleratedNetworking": { - "value": "[[parameters('AcceleratedNetworking')]" - }, - "AdminUsername": { - "value": "[[parameters('AdminUsername')]" - }, - "HostPoolName": { - "value": "[[parameters('HostPoolName')]" - }, - "HostPoolToken": { - "value": "[[parameters('HostPoolToken')]" - }, - "ImageReference": { - "value": "[[parameters('ImageReference')]" - }, - "SecurityProfile": { - "value": "[[parameters('SecurityProfile')]" - }, - "SubnetID": { - "value": "[[parameters('SubnetID')]" - }, - "VMName": { - "value": "[[parameters('VMNames')[copyIndex()]]" - }, - "VMNamePrefixLength": { - "value": "[[parameters('VMNamePrefixLength')]" - }, - "VMSize": { - "value": "[[parameters('VMSize')]" - }, - "DiskType": { - "value": "[[parameters('DiskType')]" - }, - "WVDArtifactsURL": { - "value": "[[parameters('WVDArtifactsURL')]" - }, - "DomainJoinObject": { - "value": "[[parameters('DomainJoinObject')]" - }, - "DomainJoinPassword": { - "value": "[[parameters('DomainJoinPassword')]" - }, - "Location": { - "value": "[[parameters('Location')]" - }, - "AvailabilityZones": { - "value": "[[parameters('AvailabilityZones')]" - }, - "Tags": { - "value": "[[parameters('Tags')]" + "networkProfile": { + "networkInterfaces": [ + { + "id": "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", + "properties": { + "deleteOption": "Delete" } - }, - "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": "7267962610869920549" - } - }, - "parameters": { - "VMName": { - "type": "string" - }, - "VMNamePrefixLength": { - "type": "int" - }, - "VMSize": { - "type": "string" - }, - "DiskType": { - "type": "string" - }, - "Location": { - "type": "string", - "defaultValue": "[[resourceGroup().location]" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "SubnetID": { - "type": "string" - }, - "AdminUsername": { - "type": "string" - }, - "AdminPassword": { - "type": "securestring", - "defaultValue": "[[newGuid()]" - }, - "AcceleratedNetworking": { - "type": "bool" - }, - "Tags": { - "type": "object", - "defaultValue": {} - }, - "ImageReference": { - "type": "object" - }, - "SecurityProfile": { - "type": "object" - }, - "HostPoolName": { - "type": "string" - }, - "HostPoolToken": { - "type": "securestring" - }, - "WVDArtifactsURL": { - "type": "string" - }, - "DomainJoinObject": { - "type": "object", - "defaultValue": {} - }, - "DomainJoinPassword": { - "type": "securestring", - "defaultValue": "" - } - }, - "variables": { - "varRequireNvidiaGPU": "[[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", - "varVMNumber": "[[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", - "varAvailabilityZone": "[[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" - }, - "resources": [ - { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'deployIntegrityMonitoring')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Azure.Security.WindowsAttestation", - "type": "GuestAttestation", - "typeHandlerVersion": "1.0", - "autoUpgradeMinorVersion": true, - "settings": { - "AttestationConfig": { - "MaaSettings": { - "maaEndpoint": "", - "maaTenantName": "Guest Attestation" - }, - "AscSettings": { - "ascReportingEndpoint": "", - "ascReportingFrequency": "" - }, - "useCustomToken": "false", - "disableAlerts": "false" - } - } - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[variables('varRequireNvidiaGPU')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.HpcCompute", - "type": "NvidiaGpuDriverWindows", - "typeHandlerVersion": "1.6", - "autoUpgradeMinorVersion": true - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployIntegrityMonitoring')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[not(equals(parameters('HostPoolName'), ''))]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.PowerShell", - "type": "DSC", - "typeHandlerVersion": "2.77", - "autoUpgradeMinorVersion": true, - "settings": { - "modulesUrl": "[[parameters('WVDArtifactsURL')]", - "configurationFunction": "Configuration.ps1\\AddSessionHost", - "properties": { - "hostPoolName": "[[parameters('HostPoolName')]", - "registrationInfoToken": "[[parameters('HostPoolToken')]", - "aadJoin": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", - "useAgentDownloadEndpoint": true, - "mdmId": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" - } - } - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Azure.ActiveDirectory", - "type": "AADLoginForWindows", - "typeHandlerVersion": "2.0", - "autoUpgradeMinorVersion": true, - "settings": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Compute", - "type": "JSonADDomainExtension", - "typeHandlerVersion": "1.3", - "autoUpgradeMinorVersion": true, - "settings": { - "Name": "[[parameters('DomainJoinObject').DomainName]", - "OUPath": "[[parameters('DomainJoinObject').ADOUPath]", - "User": "[[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", - "Restart": "true", - "Options": 3 - }, - "protectedSettings": { - "Password": "[[parameters('DomainJoinPassword')]" - } - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2023-09-01", - "name": "[[format('{0}-vNIC', parameters('VMName'))]", - "location": "[[parameters('Location')]", - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "subnet": { - "id": "[[parameters('SubnetID')]" - } - } - } - ], - "enableAcceleratedNetworking": "[[parameters('AcceleratedNetworking')]" - }, - "tags": "[[parameters('Tags')]" - }, - { - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2023-09-01", - "name": "[[parameters('VMName')]", - "location": "[[parameters('Location')]", - "zones": "[[variables('varAvailabilityZone')]", - "identity": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", - "properties": { - "osProfile": { - "computerName": "[[parameters('VMName')]", - "adminUsername": "[[parameters('AdminUsername')]", - "adminPassword": "[[parameters('AdminPassword')]" - }, - "hardwareProfile": { - "vmSize": "[[parameters('VMSize')]" - }, - "storageProfile": { - "osDisk": { - "name": "[[format('{0}-OSDisk', parameters('VMName'))]", - "createOption": "FromImage", - "deleteOption": "Delete", - "managedDisk": { - "storageAccountType": "[[parameters('DiskType')]" - } - }, - "ImageReference": "[[parameters('ImageReference')]" - }, - "securityProfile": "[[parameters('SecurityProfile')]", - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": true - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", - "properties": { - "deleteOption": "Delete" - } - } - ] - }, - "licenseType": "Windows_Client" - }, - "tags": "[[parameters('Tags')]", - "dependsOn": [ - "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" - ] - } - ] } - } - } + ] + }, + "licenseType": "Windows_Client" + }, + "tags": "[[parameters('Tags')]", + "dependsOn": [ + "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" ] } + ] + } + } + } + ] +} }, "resources": [ { From dce924139b482739b1f35cf1639ce16cfb14d00d Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:58:26 +0100 Subject: [PATCH 67/83] Update DNS server description and add DiskSizeGB parameter --- deploy/arm/DeployAVDSessionHostReplacer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 52f0df2..f914eeb 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -124,7 +124,7 @@ "type": "array", "defaultValue": [], "metadata": { - "description": "Optional: List of DNS server IP addresses to configure on the network interface. Leave empty to use Azure DNS." + "description": "List of DNS server IP addresses to set on the NIC" } }, "IdentityServiceProvider": { @@ -478,7 +478,7 @@ }, { "name": "_SessionHostParameters", - "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'DnsServers', parameters('DnsServers'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" + "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'DnsServers', parameters('DnsServers'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'DiskSizeGB', parameters('DiskSizeGB'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" }, { "name": "_SubscriptionId", From 814a123fabe370f7bc56c06e5c04e57dc1c0c0c1 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 21:00:25 +0100 Subject: [PATCH 68/83] Remove location from RBAC deployment configurations Removed location property from RBAC deployment configurations. --- deploy/arm/DeployAVDSessionHostReplacer.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index f914eeb..03679b3 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -1368,7 +1368,6 @@ "apiVersion": "2022-09-01", "name": "[format('RBAC-vdiVMContributor-{0}', parameters('TimeStamp'))]", "subscriptionId": "[subscription().subscriptionId]", - "location": "[resourceGroup().location]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -1429,7 +1428,6 @@ "apiVersion": "2022-09-01", "name": "[format('RBAC-TemplateSpecReader-{0}', parameters('TimeStamp'))]", "subscriptionId": "[subscription().subscriptionId]", - "location": "[resourceGroup().location]", "properties": { "expressionEvaluationOptions": { "scope": "inner" From 5825015252e22ddaf8d81dc496f8e927851476a7 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 23:02:22 +0100 Subject: [PATCH 69/83] Update print statement to say 'Goodbye World' --- deploy/arm/DeployAVDSessionHostReplacer.json | 891 +++++++++---------- 1 file changed, 445 insertions(+), 446 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 03679b3..9739790 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -67,6 +67,13 @@ "description": "OS disk size in GB" } }, + "DnsServers": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "List of DNS server IP addresses to set on the NIC" + } + }, "MarketPlaceOrCustomImage": { "type": "string", "defaultValue": "Marketplace", @@ -120,13 +127,6 @@ "type": "string", "defaultValue": "" }, - "DnsServers": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "List of DNS server IP addresses to set on the NIC" - } - }, "IdentityServiceProvider": { "type": "string", "defaultValue": "EntraID", @@ -478,7 +478,7 @@ }, { "name": "_SessionHostParameters", - "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'DnsServers', parameters('DnsServers'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'DiskSizeGB', parameters('DiskSizeGB'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" + "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'DiskSizeGB', parameters('DiskSizeGB'), 'DnsServers', parameters('DnsServers'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" }, { "name": "_SubscriptionId", @@ -876,458 +876,455 @@ }, "variables": { "$fxv#0": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.33.4.45862", - "templateHash": "13792602582245425536" - } - }, - "parameters": { - "Location": { - "type": "string", - "defaultValue": "[[resourceGroup().location]" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "VMNames": { - "type": "array" - }, - "VMNamePrefixLength": { - "type": "int" - }, - "VMSize": { - "type": "string" - }, - "SubnetID": { - "type": "string" - }, - "AdminUsername": { - "type": "string" - }, - "AcceleratedNetworking": { - "type": "bool" - }, - "DiskType": { - "type": "string" - }, - "DiskSizeGB": { - "type": "int", - "defaultValue": 64, - "metadata": { - "description": "OS disk size in GB" - } - }, - "DnsServers": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "List of DNS server IP addresses to set on the NIC" - } - }, - "Tags": { - "type": "object", - "defaultValue": {} - }, - "ImageReference": { - "type": "object" - }, - "SecurityProfile": { - "type": "object", - "defaultValue": {} - }, - "HostPoolName": { - "type": "string" - }, - "HostPoolToken": { - "type": "securestring" - }, - "WVDArtifactsURL": { - "type": "string", - "defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" - }, - "DomainJoinObject": { - "type": "object", - "defaultValue": {} - }, - "DomainJoinPassword": { - "type": "securestring", - "defaultValue": "" - } - }, - "resources": [ - { - "copy": { - "name": "deploySessionHosts", - "count": "[[length(parameters('VMNames'))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "AcceleratedNetworking": { - "value": "[[parameters('AcceleratedNetworking')]" - }, - "AdminUsername": { - "value": "[[parameters('AdminUsername')]" - }, - "HostPoolName": { - "value": "[[parameters('HostPoolName')]" - }, - "HostPoolToken": { - "value": "[[parameters('HostPoolToken')]" - }, - "ImageReference": { - "value": "[[parameters('ImageReference')]" - }, - "SecurityProfile": { - "value": "[[parameters('SecurityProfile')]" - }, - "SubnetID": { - "value": "[[parameters('SubnetID')]" - }, - "VMName": { - "value": "[[parameters('VMNames')[copyIndex()]]" - }, - "VMNamePrefixLength": { - "value": "[[parameters('VMNamePrefixLength')]" - }, - "VMSize": { - "value": "[[parameters('VMSize')]" - }, - "DiskType": { - "value": "[[parameters('DiskType')]" - }, - "DiskSizeGB": { - "value": "[[parameters('DiskSizeGB')]" - }, - "DnsServers": { - "value": "[[parameters('DnsServers')]" - }, - "WVDArtifactsURL": { - "value": "[[parameters('WVDArtifactsURL')]" - }, - "DomainJoinObject": { - "value": "[[parameters('DomainJoinObject')]" - }, - "DomainJoinPassword": { - "value": "[[parameters('DomainJoinPassword')]" - }, - "Location": { - "value": "[[parameters('Location')]" - }, - "AvailabilityZones": { - "value": "[[parameters('AvailabilityZones')]" - }, - "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.32.4.45862", - "templateHash": "9402242463429439832" - } - }, - "parameters": { - "VMName": { - "type": "string" - }, - "VMNamePrefixLength": { - "type": "int" - }, - "VMSize": { - "type": "string" - }, - "DiskType": { - "type": "string" - }, - "DiskSizeGB": { - "type": "int", - "defaultValue": 128, + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", "metadata": { - "description": "OS disk size in GB" - } - }, - "DnsServers": { - "type": "array", - "metadata": { - "description": "List of DNS server IP addresses to set on the NIC" - } - }, - "Location": { - "type": "string", - "defaultValue": "[[resourceGroup().location]" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "SubnetID": { - "type": "string" - }, - "AdminUsername": { - "type": "string" - }, - "AdminPassword": { - "type": "securestring", - "defaultValue": "[[newGuid()]" - }, - "AcceleratedNetworking": { - "type": "bool" - }, - "Tags": { - "type": "object", - "defaultValue": {} - }, - "ImageReference": { - "type": "object" - }, - "SecurityProfile": { - "type": "object" - }, - "HostPoolName": { - "type": "string" - }, - "HostPoolToken": { - "type": "securestring" - }, - "WVDArtifactsURL": { - "type": "string" - }, - "DomainJoinObject": { - "type": "object", - "defaultValue": {} - }, - "DomainJoinPassword": { - "type": "securestring", - "defaultValue": "" - } - }, - "variables": { - "varRequireNvidiaGPU": "[[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", - "varVMNumber": "[[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", - "varAvailabilityZone": "[[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" - }, - "resources": [ - { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'deployIntegrityMonitoring')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Azure.Security.WindowsAttestation", - "type": "GuestAttestation", - "typeHandlerVersion": "1.0", - "autoUpgradeMinorVersion": true, - "settings": { - "AttestationConfig": { - "MaaSettings": { - "maaEndpoint": "", - "maaTenantName": "Guest Attestation" - }, - "AscSettings": { - "ascReportingEndpoint": "", - "ascReportingFrequency": "" - }, - "useCustomToken": "false", - "disableAlerts": "false" - } - } - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[variables('varRequireNvidiaGPU')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.HpcCompute", - "type": "NvidiaGpuDriverWindows", - "typeHandlerVersion": "1.6", - "autoUpgradeMinorVersion": true - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployIntegrityMonitoring')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[not(equals(parameters('HostPoolName'), ''))]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.PowerShell", - "type": "DSC", - "typeHandlerVersion": "2.77", - "autoUpgradeMinorVersion": true, - "settings": { - "modulesUrl": "[[parameters('WVDArtifactsURL')]", - "configurationFunction": "Configuration.ps1\\AddSessionHost", - "properties": { - "hostPoolName": "[[parameters('HostPoolName')]", - "registrationInfoToken": "[[parameters('HostPoolToken')]", - "aadJoin": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", - "useAgentDownloadEndpoint": true, - "mdmId": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" - } + "_generator": { + "name": "bicep", + "version": "0.33.4.45862", + "templateHash": "13792602582245425536" } }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Azure.ActiveDirectory", - "type": "AADLoginForWindows", - "typeHandlerVersion": "2.0", - "autoUpgradeMinorVersion": true, - "settings": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Compute", - "type": "JSonADDomainExtension", - "typeHandlerVersion": "1.3", - "autoUpgradeMinorVersion": true, - "settings": { - "Name": "[[parameters('DomainJoinObject').DomainName]", - "OUPath": "[[parameters('DomainJoinObject').ADOUPath]", - "User": "[[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", - "Restart": "true", - "Options": 3 + "parameters": { + "Location": { + "type": "string", + "defaultValue": "[[resourceGroup().location]" }, - "protectedSettings": { - "Password": "[[parameters('DomainJoinPassword')]" - } - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - -{ - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2023-09-01", - "name": "[[format('{0}-vNIC', parameters('VMName'))]", - "location": "[[parameters('Location')]", - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "subnet": { - "id": "[[parameters('SubnetID')]" - } - } - } - ], - -"dnsSettings": { - "dnsServers": "[[parameters('DnsServers')]" -}, - "enableAcceleratedNetworking": "[[parameters('AcceleratedNetworking')]" - }, - "tags": "[[parameters('Tags')]" -}, - - { - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2023-09-01", - "name": "[[parameters('VMName')]", - "location": "[[parameters('Location')]", - "zones": "[[variables('varAvailabilityZone')]", - "identity": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", - "properties": { - "osProfile": { - "computerName": "[[parameters('VMName')]", - "adminUsername": "[[parameters('AdminUsername')]", - "adminPassword": "[[parameters('AdminPassword')]" + "AvailabilityZones": { + "type": "array", + "defaultValue": [] }, - "hardwareProfile": { - "vmSize": "[[parameters('VMSize')]" + "VMNames": { + "type": "array" }, - "additionalCapabilities": { - "hibernationEnabled": true + "VMNamePrefixLength": { + "type": "int" }, - "storageProfile": { - "osDisk": { - "name": "[[format('{0}-OSDisk', parameters('VMName'))]", - "createOption": "FromImage", - "deleteOption": "Delete", - "diskSizeGB": "[[parameters('DiskSizeGB')]", - "managedDisk": { - "storageAccountType": "[[parameters('DiskType')]" - } - }, - "ImageReference": "[[parameters('ImageReference')]" + "VMSize": { + "type": "string" + }, + "SubnetID": { + "type": "string" + }, + "AdminUsername": { + "type": "string" }, - "securityProfile": "[[parameters('SecurityProfile')]", - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": true + "AcceleratedNetworking": { + "type": "bool" + }, + "DiskType": { + "type": "string" + }, + "DiskSizeGB": { + "type": "int", + "defaultValue": 128, + "metadata": { + "description": "OS disk size in GB" } }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", - "properties": { - "deleteOption": "Delete" - } - } - ] + "DnsServers": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "List of DNS server IP addresses to set on the NIC" + } + }, + "Tags": { + "type": "object", + "defaultValue": {} + }, + "ImageReference": { + "type": "object" }, - "licenseType": "Windows_Client" + "SecurityProfile": { + "type": "object", + "defaultValue": {} + }, + "HostPoolName": { + "type": "string" + }, + "HostPoolToken": { + "type": "securestring" + }, + "WVDArtifactsURL": { + "type": "string", + "defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" + }, + "DomainJoinObject": { + "type": "object", + "defaultValue": {} + }, + "DomainJoinPassword": { + "type": "securestring", + "defaultValue": "" + } }, - "tags": "[[parameters('Tags')]", - "dependsOn": [ - "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" + "resources": [ + { + "copy": { + "name": "deploySessionHosts", + "count": "[[length(parameters('VMNames'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "AcceleratedNetworking": { + "value": "[[parameters('AcceleratedNetworking')]" + }, + "AdminUsername": { + "value": "[[parameters('AdminUsername')]" + }, + "HostPoolName": { + "value": "[[parameters('HostPoolName')]" + }, + "HostPoolToken": { + "value": "[[parameters('HostPoolToken')]" + }, + "ImageReference": { + "value": "[[parameters('ImageReference')]" + }, + "SecurityProfile": { + "value": "[[parameters('SecurityProfile')]" + }, + "SubnetID": { + "value": "[[parameters('SubnetID')]" + }, + "VMName": { + "value": "[[parameters('VMNames')[copyIndex()]]" + }, + "VMNamePrefixLength": { + "value": "[[parameters('VMNamePrefixLength')]" + }, + "VMSize": { + "value": "[[parameters('VMSize')]" + }, + "DiskType": { + "value": "[[parameters('DiskType')]" + }, + "DiskSizeGB": { + "value": "[[parameters('DiskSizeGB')]" + }, + "DnsServers": { + "value": "[[parameters('DnsServers')]" + }, + "WVDArtifactsURL": { + "value": "[[parameters('WVDArtifactsURL')]" + }, + "DomainJoinObject": { + "value": "[[parameters('DomainJoinObject')]" + }, + "DomainJoinPassword": { + "value": "[[parameters('DomainJoinPassword')]" + }, + "Location": { + "value": "[[parameters('Location')]" + }, + "AvailabilityZones": { + "value": "[[parameters('AvailabilityZones')]" + }, + "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.32.4.45862", + "templateHash": "9402242463429439832" + } + }, + "parameters": { + "VMName": { + "type": "string" + }, + "VMNamePrefixLength": { + "type": "int" + }, + "VMSize": { + "type": "string" + }, + "DiskType": { + "type": "string" + }, + "DiskSizeGB": { + "type": "int", + "defaultValue": 128, + "metadata": { + "description": "OS disk size in GB" + } + }, + "DnsServers": { + "type": "array", + "metadata": { + "description": "List of DNS server IP addresses to set on the NIC" + } + }, + "Location": { + "type": "string", + "defaultValue": "[[resourceGroup().location]" + }, + "AvailabilityZones": { + "type": "array", + "defaultValue": [] + }, + "SubnetID": { + "type": "string" + }, + "AdminUsername": { + "type": "string" + }, + "AdminPassword": { + "type": "securestring", + "defaultValue": "[[newGuid()]" + }, + "AcceleratedNetworking": { + "type": "bool" + }, + "Tags": { + "type": "object", + "defaultValue": {} + }, + "ImageReference": { + "type": "object" + }, + "SecurityProfile": { + "type": "object" + }, + "HostPoolName": { + "type": "string" + }, + "HostPoolToken": { + "type": "securestring" + }, + "WVDArtifactsURL": { + "type": "string" + }, + "DomainJoinObject": { + "type": "object", + "defaultValue": {} + }, + "DomainJoinPassword": { + "type": "securestring", + "defaultValue": "" + } + }, + "variables": { + "varRequireNvidiaGPU": "[[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", + "varVMNumber": "[[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", + "varAvailabilityZone": "[[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" + }, + "resources": [ + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'deployIntegrityMonitoring')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Azure.Security.WindowsAttestation", + "type": "GuestAttestation", + "typeHandlerVersion": "1.0", + "autoUpgradeMinorVersion": true, + "settings": { + "AttestationConfig": { + "MaaSettings": { + "maaEndpoint": "", + "maaTenantName": "Guest Attestation" + }, + "AscSettings": { + "ascReportingEndpoint": "", + "ascReportingFrequency": "" + }, + "useCustomToken": "false", + "disableAlerts": "false" + } + } + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[variables('varRequireNvidiaGPU')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.HpcCompute", + "type": "NvidiaGpuDriverWindows", + "typeHandlerVersion": "1.6", + "autoUpgradeMinorVersion": true + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployIntegrityMonitoring')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[not(equals(parameters('HostPoolName'), ''))]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.PowerShell", + "type": "DSC", + "typeHandlerVersion": "2.77", + "autoUpgradeMinorVersion": true, + "settings": { + "modulesUrl": "[[parameters('WVDArtifactsURL')]", + "configurationFunction": "Configuration.ps1\\AddSessionHost", + "properties": { + "hostPoolName": "[[parameters('HostPoolName')]", + "registrationInfoToken": "[[parameters('HostPoolToken')]", + "aadJoin": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", + "useAgentDownloadEndpoint": true, + "mdmId": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" + } + } + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Azure.ActiveDirectory", + "type": "AADLoginForWindows", + "typeHandlerVersion": "2.0", + "autoUpgradeMinorVersion": true, + "settings": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Compute", + "type": "JSonADDomainExtension", + "typeHandlerVersion": "1.3", + "autoUpgradeMinorVersion": true, + "settings": { + "Name": "[[parameters('DomainJoinObject').DomainName]", + "OUPath": "[[parameters('DomainJoinObject').ADOUPath]", + "User": "[[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", + "Restart": "true", + "Options": 3 + }, + "protectedSettings": { + "Password": "[[parameters('DomainJoinPassword')]" + } + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2023-09-01", + "name": "[[format('{0}-vNIC', parameters('VMName'))]", + "location": "[[parameters('Location')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[[parameters('SubnetID')]" + } + } + } + ], + "dnsSettings": { + "dnsServers": "[[parameters('DnsServers')]" + }, + "enableAcceleratedNetworking": "[[parameters('AcceleratedNetworking')]" + }, + "tags": "[[parameters('Tags')]" + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2023-09-01", + "name": "[[parameters('VMName')]", + "location": "[[parameters('Location')]", + "zones": "[[variables('varAvailabilityZone')]", + "identity": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", + "properties": { + "osProfile": { + "computerName": "[[parameters('VMName')]", + "adminUsername": "[[parameters('AdminUsername')]", + "adminPassword": "[[parameters('AdminPassword')]" + }, + "hardwareProfile": { + "vmSize": "[[parameters('VMSize')]" + }, + "additionalCapabilities": { + "hibernationEnabled": true + }, + "storageProfile": { + "osDisk": { + "name": "[[format('{0}-OSDisk', parameters('VMName'))]", + "createOption": "FromImage", + "deleteOption": "Delete", + "diskSizeGB": "[[parameters('DiskSizeGB')]", + "managedDisk": { + "storageAccountType": "[[parameters('DiskType')]" + } + }, + "ImageReference": "[[parameters('ImageReference')]" + }, + "securityProfile": "[[parameters('SecurityProfile')]", + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": true + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", + "properties": { + "deleteOption": "Delete" + } + } + ] + }, + "licenseType": "Windows_Client" + }, + "tags": "[[parameters('Tags')]", + "dependsOn": [ + "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" + ] + } + ] + } + } + } ] } - ] - } - } - } - ] -} }, "resources": [ { @@ -1368,6 +1365,7 @@ "apiVersion": "2022-09-01", "name": "[format('RBAC-vdiVMContributor-{0}', parameters('TimeStamp'))]", "subscriptionId": "[subscription().subscriptionId]", + "location": "[resourceGroup().location]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -1428,6 +1426,7 @@ "apiVersion": "2022-09-01", "name": "[format('RBAC-TemplateSpecReader-{0}', parameters('TimeStamp'))]", "subscriptionId": "[subscription().subscriptionId]", + "location": "[resourceGroup().location]", "properties": { "expressionEvaluationOptions": { "scope": "inner" From c9bc0d9b57b8b154ccc886f893fe24f0355677a4 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 23:14:34 +0100 Subject: [PATCH 70/83] Fix JSON syntax in DeployAVDSessionHostReplacer --- deploy/arm/DeployAVDSessionHostReplacer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 9739790..448066c 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -961,7 +961,7 @@ }, "resources": [ { - "copy": { + "[string('copy')]": { "name": "deploySessionHosts", "count": "[[length(parameters('VMNames'))]" }, From e93a23e37bb6d9869e1fba3c47e5a08fe01df6d4 Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Tue, 6 Jan 2026 23:35:32 +0100 Subject: [PATCH 71/83] Update packageUri parameter in AVD deployment JSON --- deploy/arm/DeployAVDSessionHostReplacer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 448066c..46a3ccd 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -658,7 +658,7 @@ "apiVersion": "2023-01-01", "name": "[format('{0}/{1}', parameters('FunctionAppName'), 'MSDeploy')]", "properties": { - "packageUri": "[parameters('FunctionAppZipUrl')]" + "packageUri": "[parameters('FunctionAppUrl')]" }, "dependsOn": [ "[resourceId('Microsoft.Web/sites', parameters('FunctionAppName'))]" From c20fe8428c7416d93bc8260ae2d1f8ee32fea59b Mon Sep 17 00:00:00 2001 From: stefze <43411563+stefze@users.noreply.github.com> Date: Thu, 5 Mar 2026 12:14:48 +0100 Subject: [PATCH 72/83] Add files via upload --- .../functions/Get-SHRHostPoolDecision.ps1 | 262 +++++++++--------- .../functions/Get-SHRSessionHost.ps1 | 230 +++++++-------- 2 files changed, 252 insertions(+), 240 deletions(-) diff --git a/FunctionApp/Modules/SessionHostReplacer/functions/Get-SHRHostPoolDecision.ps1 b/FunctionApp/Modules/SessionHostReplacer/functions/Get-SHRHostPoolDecision.ps1 index 4a3a4a4..9cf65d9 100644 --- a/FunctionApp/Modules/SessionHostReplacer/functions/Get-SHRHostPoolDecision.ps1 +++ b/FunctionApp/Modules/SessionHostReplacer/functions/Get-SHRHostPoolDecision.ps1 @@ -1,126 +1,138 @@ -function Get-SHRHostPoolDecision { - <# - .SYNOPSIS - This function will decide how many session hosts to deploy and if we should decommission any session hosts. - #> - [CmdletBinding()] - param ( - # Session hosts to consider - [Parameter()] - [array] $SessionHosts = @(), - - # Running deployments - [Parameter()] - $RunningDeployments, - - # Target age of session hosts in days - after this many days we consider a session host for replacement. - [Parameter()] - [int] $TargetVMAgeDays = (Get-FunctionConfig _TargetVMAgeDays), - - # Target number of session hosts in the host pool. If we have more than or equal to this number of session hosts we will decommission some. - [Parameter()] - [int] $TargetSessionHostCount = (Get-FunctionConfig _TargetSessionHostCount), - - [Parameter()] - [int] $TargetSessionHostBuffer = (Get-FunctionConfig _TargetSessionHostBuffer), - - # Latest image version - [Parameter()] - [PSCustomObject] $LatestImageVersion, - - # Should we replace session hosts on new image version - [Parameter()] - [bool] $ReplaceSessionHostOnNewImageVersion = (Get-FunctionConfig _ReplaceSessionHostOnNewImageVersion), - - # Delay days before replacing session hosts on new image version - [Parameter()] - [int] $ReplaceSessionHostOnNewImageVersionDelayDays = (Get-FunctionConfig _ReplaceSessionHostOnNewImageVersionDelayDays) - ) - # Basic Info - Write-PSFMessage -Level Host -Message "We have {0} session hosts (included in Automation)" -StringValues $SessionHosts.Count - - # Identify Session hosts that should be replaced - if ($TargetVMAgeDays -gt 0) { - $targetReplacementDate = (Get-Date).AddDays(-$TargetVMAgeDays) - [array] $sessionHostsOldAge = $SessionHosts | Where-Object { $_.DeployTimestamp -lt $targetReplacementDate } - Write-PSFMessage -Level Host -Message "Found {0} session hosts to replace due to old age. {1}" -StringValues $sessionHostsOldAge.Count, ($sessionHostsOldAge.VMName -join ',') - - } - - if ($ReplaceSessionHostOnNewImageVersion) { - $latestImageAge = (New-TimeSpan -Start $LatestImageVersion.Date -End (Get-Date -AsUTC)).TotalDays - Write-PSFMessage -Level Host -Message "Latest Image {0} is {1:N0} days old." -StringValues $LatestImageVersion.Version, $latestImageAge - if ($latestImageAge -ge $ReplaceSessionHostOnNewImageVersionDelayDays) { - Write-PSFMessage -Level Host -Message "Latest Image age is older than (or equal) New Image Delay value {0}" -StringValues $ReplaceSessionHostOnNewImageVersionDelayDays - [array] $sessionHostsOldVersion = $sessionHosts | Where-Object { $_.ImageVersion -ne $LatestImageVersion.Version } - Write-PSFMessage -Level Host -Message "Found {0} session hosts to replace due to new image version. {1}" -StringValues $sessionHostsOldVersion.Count, ($sessionHostsOldVersion.VMName -Join ',') - } - } - - [array] $sessionHostsToReplace = ($sessionHostsOldAge + $sessionHostsOldVersion) | Select-Object -Property * -Unique - Write-PSFMessage -Level Host -Message "Found {0} session hosts to replace in total. {1}" -StringValues $sessionHostsToReplace.Count, ($sessionHostsToReplace.VMName -join ',') - - # Good Session Hosts - - $goodSessionHosts = $SessionHosts | Where-Object { $_.VMName -notin $sessionHostsToReplace.VMName } - $sessionHostsCurrentTotal = ([array]$goodSessionHosts.VMName + [array]$runningDeployments.SessionHostNames ) | Select-Object -Unique - - Write-PSFMessage -Level Host -Message "We have {0} good session hosts including {1} session hosts being deployed" -StringValues $sessionHostsCurrentTotal.Count, $runningDeployments.SessionHostNames.Count - Write-PSFMessage -Level Host -Message "We target having {0} session hosts in good shape" -StringValues $TargetSessionHostCount - Write-PSFMessage -Level Host -Message "We have a buffer of {0} session hosts more than the target." -StringValues $TargetSessionHostBuffer - - $weCanDeployUpTo = $TargetSessionHostCount + $TargetSessionHostBuffer - $SessionHosts.count - $RunningDeployments.SessionHostNames.Count - if ($weCanDeployUpTo -ge 0) { - Write-PSFMessage -Level Host -Message "We can deploy up to {0} session hosts" -StringValues $weCanDeployUpTo - - $weNeedToDeploy = $TargetSessionHostCount - $sessionHostsCurrentTotal.Count - if ($weNeedToDeploy -gt 0) { - Write-PSFMessage -Level Host -Message "We need to deploy {0} new session hosts" -StringValues $weNeedToDeploy - $weCanDeploy = if ($weNeedToDeploy -gt $weCanDeployUpTo) { $weCanDeployUpTo } else { $weNeedToDeploy } # If we need to deploy 10 machines, and we can deploy 5, we should only deploy 5. - Write-PSFMessage -Level Host -Message "Buffer allows deploying {0} session hosts" -StringValues $weCanDeploy - } - else { - $weCanDeploy = 0 - Write-PSFMessage -Level Host -Message "We have enough session hosts in good shape." - } - } - else { - Write-PSFMessage -Level Host -Message "Buffer is full. We can not deploy more session hosts" - $weCanDeploy = 0 - } - - - $weCanDelete = $SessionHosts.Count - $TargetSessionHostCount - if ($weCanDelete -gt 0) { - Write-PSFMessage -Level Host -Message "We need to delete {0} session hosts" -StringValues $weCanDelete - if ($weCanDelete -gt $sessionHostsToReplace.Count) { - Write-PSFMessage -Level Host -Message "Host pool is over populated" - - $goodSessionHostsToDeleteCount = $weCanDelete - $sessionHostsToReplace.Count - Write-PSFMessage -Level Host -Message "We will delete {0} good session hosts" -StringValues $goodSessionHostsToDeleteCount - - $selectedGoodHostsTotDelete = [array] ($goodSessionHosts | Sort-Object -Property Session | Select-Object -First $goodSessionHostsToDeleteCount) - Write-PSFMessage -Level Host -Message "Selected the following good session hosts to delete: {0}" -StringValues ($selectedGoodHostsTotDelete.VMName -join ',') - } - else { - $selectedGoodHostsTotDelete = @() - Write-PSFMessage -Level Host -Message "Host pool is not over populated" - } - - $sessionHostsPendingDelete = ($sessionHostsToReplace + $selectedGoodHostsTotDelete) | Select-Object -First $weCanDelete - Write-PSFMessage -Level Host -Message "The following Session Hosts are now pending delete: {0}" -StringValues ($SessionHostsPendingDelete.VMName -join ',') - - } - elseif ($sessionHostsToReplace.Count -gt 0) { - Write-PSFMessage -Level Host -Message "We need to delete {0} session hosts but we don't have enough session hosts in the host pool." -StringValues ($sessionHostsToReplace.Count) - } - else { Write-PSFMessage -Level Host -Message "We do not need to delete any session hosts" } - - - [PSCustomObject]@{ - PossibleDeploymentsCount = $weCanDeploy - PossibleSessionHostDeleteCount = $weCanDelete - SessionHostsPendingDelete = $sessionHostsPendingDelete - ExistingSessionHostVMNames = ([array]$SessionHosts.VMName + [array]$runningDeployments.SessionHostNames) | Select-Object -Unique - } +function Get-SHRHostPoolDecision { + <# + .SYNOPSIS + This function will decide how many session hosts to deploy and if we should decommission any session hosts. + #> + [CmdletBinding()] + param ( + # Session hosts to consider + [Parameter()] + [array] $SessionHosts = @(), + + # Running deployments + [Parameter()] + $RunningDeployments, + + # Target age of session hosts in days - after this many days we consider a session host for replacement. + [Parameter()] + [int] $TargetVMAgeDays = (Get-FunctionConfig _TargetVMAgeDays), + + # Target number of session hosts in the host pool. If we have more than or equal to this number of session hosts we will decommission some. + [Parameter()] + [int] $TargetSessionHostCount = (Get-FunctionConfig _TargetSessionHostCount), + + [Parameter()] + [int] $TargetSessionHostBuffer = (Get-FunctionConfig _TargetSessionHostBuffer), + + # Latest image version + [Parameter()] + [PSCustomObject] $LatestImageVersion, + + # Should we replace session hosts on new image version + [Parameter()] + [bool] $ReplaceSessionHostOnNewImageVersion = (Get-FunctionConfig _ReplaceSessionHostOnNewImageVersion), + + # Delay days before replacing session hosts on new image version + [Parameter()] + [int] $ReplaceSessionHostOnNewImageVersionDelayDays = (Get-FunctionConfig _ReplaceSessionHostOnNewImageVersionDelayDays) + ) + # Basic Info + Write-PSFMessage -Level Host -Message "We have {0} session hosts (included in Automation)" -StringValues $SessionHosts.Count + + [array] $deletionEligibleSessionHosts = $SessionHosts | Where-Object { [string]::IsNullOrWhiteSpace($_.AssignedUser) } + [array] $assignedSessionHosts = $SessionHosts | Where-Object { -not [string]::IsNullOrWhiteSpace($_.AssignedUser) } + Write-PSFMessage -Level Host -Message "Found {0} session hosts assigned to users. These hosts are excluded from removal." -StringValues $assignedSessionHosts.Count + Write-PSFMessage -Level Host -Message "Found {0} session hosts eligible for removal." -StringValues $deletionEligibleSessionHosts.Count + + # Identify Session hosts that should be replaced + if ($TargetVMAgeDays -gt 0) { + $targetReplacementDate = (Get-Date).AddDays(-$TargetVMAgeDays) + [array] $sessionHostsOldAge = $deletionEligibleSessionHosts | Where-Object { $_.DeployTimestamp -lt $targetReplacementDate } + Write-PSFMessage -Level Host -Message "Found {0} session hosts to replace due to old age. {1}" -StringValues $sessionHostsOldAge.Count, ($sessionHostsOldAge.VMName -join ',') + + } + + if ($ReplaceSessionHostOnNewImageVersion) { + $latestImageAge = (New-TimeSpan -Start $LatestImageVersion.Date -End (Get-Date -AsUTC)).TotalDays + Write-PSFMessage -Level Host -Message "Latest Image {0} is {1:N0} days old." -StringValues $LatestImageVersion.Version, $latestImageAge + if ($latestImageAge -ge $ReplaceSessionHostOnNewImageVersionDelayDays) { + Write-PSFMessage -Level Host -Message "Latest Image age is older than (or equal) New Image Delay value {0}" -StringValues $ReplaceSessionHostOnNewImageVersionDelayDays + [array] $sessionHostsOldVersion = $deletionEligibleSessionHosts | Where-Object { $_.ImageVersion -ne $LatestImageVersion.Version } + Write-PSFMessage -Level Host -Message "Found {0} session hosts to replace due to new image version. {1}" -StringValues $sessionHostsOldVersion.Count, ($sessionHostsOldVersion.VMName -Join ',') + } + } + + [array] $sessionHostsToReplace = ($sessionHostsOldAge + $sessionHostsOldVersion) | Select-Object -Property * -Unique + Write-PSFMessage -Level Host -Message "Found {0} session hosts to replace in total. {1}" -StringValues $sessionHostsToReplace.Count, ($sessionHostsToReplace.VMName -join ',') + + # Good Session Hosts + + $goodSessionHosts = $SessionHosts | Where-Object { $_.VMName -notin $sessionHostsToReplace.VMName } + $sessionHostsCurrentTotal = ([array]$goodSessionHosts.VMName + [array]$runningDeployments.SessionHostNames ) | Select-Object -Unique + + Write-PSFMessage -Level Host -Message "We have {0} good session hosts including {1} session hosts being deployed" -StringValues $sessionHostsCurrentTotal.Count, $runningDeployments.SessionHostNames.Count + Write-PSFMessage -Level Host -Message "We target having {0} session hosts in good shape" -StringValues $TargetSessionHostCount + Write-PSFMessage -Level Host -Message "We have a buffer of {0} session hosts more than the target." -StringValues $TargetSessionHostBuffer + + $weCanDeployUpTo = $TargetSessionHostCount + $TargetSessionHostBuffer - $SessionHosts.count - $RunningDeployments.SessionHostNames.Count + if ($weCanDeployUpTo -ge 0) { + Write-PSFMessage -Level Host -Message "We can deploy up to {0} session hosts" -StringValues $weCanDeployUpTo + + $weNeedToDeploy = $TargetSessionHostCount - $sessionHostsCurrentTotal.Count + if ($weNeedToDeploy -gt 0) { + Write-PSFMessage -Level Host -Message "We need to deploy {0} new session hosts" -StringValues $weNeedToDeploy + $weCanDeploy = if ($weNeedToDeploy -gt $weCanDeployUpTo) { $weCanDeployUpTo } else { $weNeedToDeploy } # If we need to deploy 10 machines, and we can deploy 5, we should only deploy 5. + Write-PSFMessage -Level Host -Message "Buffer allows deploying {0} session hosts" -StringValues $weCanDeploy + } + else { + $weCanDeploy = 0 + Write-PSFMessage -Level Host -Message "We have enough session hosts in good shape." + } + } + else { + Write-PSFMessage -Level Host -Message "Buffer is full. We can not deploy more session hosts" + $weCanDeploy = 0 + } + + + $weCanDelete = 0 + $requestedDeleteCount = $SessionHosts.Count - $TargetSessionHostCount + if ($requestedDeleteCount -gt 0) { + Write-PSFMessage -Level Host -Message "We need to delete {0} session hosts" -StringValues $requestedDeleteCount + + $weCanDelete = [Math]::Min($requestedDeleteCount, $deletionEligibleSessionHosts.Count) + if ($weCanDelete -lt $requestedDeleteCount) { + Write-PSFMessage -Level Warning -Message "Can only delete {0} session hosts because {1} hosts are assigned to users and protected from removal." -StringValues $weCanDelete, ($requestedDeleteCount - $weCanDelete) + } + + if ($weCanDelete -gt $sessionHostsToReplace.Count) { + Write-PSFMessage -Level Host -Message "Host pool is over populated" + + $goodSessionHostsToDeleteCount = $weCanDelete - $sessionHostsToReplace.Count + Write-PSFMessage -Level Host -Message "We will delete {0} good session hosts" -StringValues $goodSessionHostsToDeleteCount + + $selectedGoodHostsTotDelete = [array] ($goodSessionHosts | Where-Object { [string]::IsNullOrWhiteSpace($_.AssignedUser) } | Sort-Object -Property Session | Select-Object -First $goodSessionHostsToDeleteCount) + Write-PSFMessage -Level Host -Message "Selected the following good session hosts to delete: {0}" -StringValues ($selectedGoodHostsTotDelete.VMName -join ',') + } + else { + $selectedGoodHostsTotDelete = @() + Write-PSFMessage -Level Host -Message "Host pool is not over populated" + } + + $sessionHostsPendingDelete = ($sessionHostsToReplace + $selectedGoodHostsTotDelete) | Select-Object -First $weCanDelete + Write-PSFMessage -Level Host -Message "The following Session Hosts are now pending delete: {0}" -StringValues ($SessionHostsPendingDelete.VMName -join ',') + + } + elseif ($sessionHostsToReplace.Count -gt 0) { + Write-PSFMessage -Level Host -Message "We need to delete {0} session hosts but we don't have enough session hosts in the host pool." -StringValues ($sessionHostsToReplace.Count) + } + else { Write-PSFMessage -Level Host -Message "We do not need to delete any session hosts" } + + + [PSCustomObject]@{ + PossibleDeploymentsCount = $weCanDeploy + PossibleSessionHostDeleteCount = $weCanDelete + SessionHostsPendingDelete = $sessionHostsPendingDelete + ExistingSessionHostVMNames = ([array]$SessionHosts.VMName + [array]$runningDeployments.SessionHostNames) | Select-Object -Unique + } } \ No newline at end of file diff --git a/FunctionApp/Modules/SessionHostReplacer/functions/Get-SHRSessionHost.ps1 b/FunctionApp/Modules/SessionHostReplacer/functions/Get-SHRSessionHost.ps1 index 1a43d1d..f2802ab 100644 --- a/FunctionApp/Modules/SessionHostReplacer/functions/Get-SHRSessionHost.ps1 +++ b/FunctionApp/Modules/SessionHostReplacer/functions/Get-SHRSessionHost.ps1 @@ -1,116 +1,116 @@ -function Get-SHRSessionHost { - <# -.SYNOPSIS - This function gets Session Host details from a host pool. -.DESCRIPTION - A longer description of the function, its purpose, common use cases, etc. -.NOTES - Information or caveats about the function e.g. 'This function is not supported in Linux' -.LINK - Specify a URI to a help page, this will show when Get-Help -Online is used. -.EXAMPLE - Test-MyTestFunction -Verbose - Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines -#> - [CmdletBinding()] - param ( - [Parameter()] - [string] $ResourceGroupName = (Get-FunctionConfig _HostPoolResourceGroupName), - [Parameter()] - [string] $HostPoolName = (Get-FunctionConfig _HostPoolName), - [Parameter()] - [string] $TagIncludeInAutomation = (Get-FunctionConfig _Tag_IncludeInAutomation), - [Parameter()] - [string] $TagDeployTimestamp = (Get-FunctionConfig _Tag_DeployTimestamp), - [Parameter()] - [string] $TagPendingDrainTimeStamp = (Get-FunctionConfig _Tag_PendingDrainTimestamp), - [Parameter()] - [switch] $FixSessionHostTags, - [Parameter()] - [bool] $IncludePreExistingSessionHosts = (Get-FunctionConfig _IncludePreExistingSessionHosts) - - ) - - # Get current session hosts - Write-PSFMessage -Level Host -Message 'Getting current session hosts in host pool {0}' -StringValues $HostPoolName - $sessionHosts = Get-AzWvdSessionHost -ResourceGroupName $ResourceGroupName -HostPoolName $HostPoolName -ErrorAction Stop | Select-Object Name, ResourceId, Session, AllowNewSession, Status - Write-PSFMessage -Level Host -Message 'Found {0} session hosts' -StringValues $sessionHosts.Count - - # For each session host, get the VM details - $result = foreach ($item in $sessionHosts) { - Write-PSFMessage -Level Host -Message 'Getting VM details for {0}' -StringValues $item.Name - - $vm = Get-AzVM -ResourceId $item.ResourceId | Select-Object Name, TimeCreated,StorageProfile - Write-PSFMessage -Level Host -Message 'VM was created on {0}' -StringValues $vm.TimeCreated - Write-PSFMessage -Level Host -Message 'VM exact version is {0}' -StringValues $vm.StorageProfile.ImageReference.ExactVersion - - Write-PSFMessage -Level Host -Message 'Getting VM tags' -StringValues $item.Name - $vmTags = Get-AzTag -ResourceId $item.ResourceId - #region: Tag DeployTimestamp - $vmDeployTimeStamp = $vmTags.Properties.TagsProperty[$TagDeployTimestamp] - try { - $vmDeployTimeStamp = [DateTime]::Parse($vmDeployTimeStamp) - Write-PSFMessage -Level Host -Message 'VM has a tag {0} with value {1}' -StringValues $TagDeployTimestamp, $vmDeployTimeStamp - } - catch { - $value = if ($null -eq $vmDeployTimeStamp) { 'null' } else { $vmDeployTimeStamp } - Write-PSFMessage -Level Host -Message 'VM tag {0} with value {1} is not a valid date' -StringValues $TagDeployTimestamp, $value - if ($FixSessionHostTags) { - Write-PSFMessage -Level Host -Message 'Copying VM CreateTime to tag {0} with value {1}' -StringValues $TagDeployTimestamp, $vm.TimeCreated.ToString('o') - Update-AzTag -ResourceId $item.ResourceId -Tag @{ $TagDeployTimestamp = $vm.TimeCreated.ToString('o') } -Operation Merge - } - $vmDeployTimeStamp = $vm.TimeCreated - } - #endregion: Tag DeployTimestamp - - #region: Tag IncludeInAutomation - $vmIncludeInAutomation = $vmTags.Properties.TagsProperty[$TagIncludeInAutomation] - if ($vmIncludeInAutomation -eq "True") { - Write-PSFMessage -Level Host -Message 'VM has a tag {0} with value {1}' -StringValues $TagIncludeInAutomation, $vmIncludeInAutomation - $vmIncludeInAutomation = $true - } - elseif ($vmIncludeInAutomation -eq "False") { - Write-PSFMessage -Level Host -Message 'VM has a tag {0} with value {1}' -StringValues $TagIncludeInAutomation, $vmIncludeInAutomation - $vmIncludeInAutomation = $false - } - else { - $value = if ($null -eq $vmIncludeInAutomation) { 'null' } else { $vmIncludeInAutomation } - Write-PSFMessage -Level Host -Message 'VM tag {0} with value {1} is not set to True/False' -StringValues $TagIncludeInAutomation, $value - if ($FixSessionHostTags) { - Write-PSFMessage -Level Host -Message 'Setting tag {0} to {1}' -StringValues $TagIncludeInAutomation, $IncludePreExistingSessionHosts - Update-AzTag -ResourceId $item.ResourceId -Tag @{ $TagIncludeInAutomation = "$IncludePreExistingSessionHosts" } -Operation Merge - } - - $vmIncludeInAutomation = $IncludePreExistingSessionHosts - } - #endregion: Tag IncludeInAutomation - - #region: Tag PendingDrainTimeStamp - $vmPendingDrainTimeStamp = $vmTags.Properties.TagsProperty[$TagPendingDrainTimeStamp] - try { - $vmPendingDrainTimeStamp = [DateTime]::Parse($vmPendingDrainTimeStamp) - Write-PSFMessage -Level Host -Message 'VM has a tag {0} with value {1}' -StringValues $TagPendingDrainTimeStamp, $vmPendingDrainTimeStamp - } - catch { - Write-PSFMessage -Level Host -Message "VM tag {0} is not set." -StringValues $TagPendingDrainTimeStamp - $vmPendingDrainTimeStamp = $null - } - - #endregion: Tag PendingDrainTimeStamp - - $vmOutput = @{ # We are combining the VM details and SessionHost objects into a single PS Custom Object - VMName = $vm.Name - FQDN = $item.Name -replace ".+\/(.+)", '$1' - DeployTimestamp = $vmDeployTimeStamp - IncludeInAutomation = $vmIncludeInAutomation - PendingDrainTimeStamp = $vmPendingDrainTimeStamp - ImageVersion = $vm.StorageProfile.ImageReference.ExactVersion - } - $item.PSObject.Properties.ForEach{ $vmOutput[$_.Name] = $_.Value } - - [PSCustomObject]$vmOutput - - } - - $result +function Get-SHRSessionHost { + <# +.SYNOPSIS + This function gets Session Host details from a host pool. +.DESCRIPTION + A longer description of the function, its purpose, common use cases, etc. +.NOTES + Information or caveats about the function e.g. 'This function is not supported in Linux' +.LINK + Specify a URI to a help page, this will show when Get-Help -Online is used. +.EXAMPLE + Test-MyTestFunction -Verbose + Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines +#> + [CmdletBinding()] + param ( + [Parameter()] + [string] $ResourceGroupName = (Get-FunctionConfig _HostPoolResourceGroupName), + [Parameter()] + [string] $HostPoolName = (Get-FunctionConfig _HostPoolName), + [Parameter()] + [string] $TagIncludeInAutomation = (Get-FunctionConfig _Tag_IncludeInAutomation), + [Parameter()] + [string] $TagDeployTimestamp = (Get-FunctionConfig _Tag_DeployTimestamp), + [Parameter()] + [string] $TagPendingDrainTimeStamp = (Get-FunctionConfig _Tag_PendingDrainTimestamp), + [Parameter()] + [switch] $FixSessionHostTags, + [Parameter()] + [bool] $IncludePreExistingSessionHosts = (Get-FunctionConfig _IncludePreExistingSessionHosts) + + ) + + # Get current session hosts + Write-PSFMessage -Level Host -Message 'Getting current session hosts in host pool {0}' -StringValues $HostPoolName + $sessionHosts = Get-AzWvdSessionHost -ResourceGroupName $ResourceGroupName -HostPoolName $HostPoolName -ErrorAction Stop | Select-Object Name, ResourceId, Session, AllowNewSession, Status, AssignedUser + Write-PSFMessage -Level Host -Message 'Found {0} session hosts' -StringValues $sessionHosts.Count + + # For each session host, get the VM details + $result = foreach ($item in $sessionHosts) { + Write-PSFMessage -Level Host -Message 'Getting VM details for {0}' -StringValues $item.Name + + $vm = Get-AzVM -ResourceId $item.ResourceId | Select-Object Name, TimeCreated,StorageProfile + Write-PSFMessage -Level Host -Message 'VM was created on {0}' -StringValues $vm.TimeCreated + Write-PSFMessage -Level Host -Message 'VM exact version is {0}' -StringValues $vm.StorageProfile.ImageReference.ExactVersion + + Write-PSFMessage -Level Host -Message 'Getting VM tags' -StringValues $item.Name + $vmTags = Get-AzTag -ResourceId $item.ResourceId + #region: Tag DeployTimestamp + $vmDeployTimeStamp = $vmTags.Properties.TagsProperty[$TagDeployTimestamp] + try { + $vmDeployTimeStamp = [DateTime]::Parse($vmDeployTimeStamp) + Write-PSFMessage -Level Host -Message 'VM has a tag {0} with value {1}' -StringValues $TagDeployTimestamp, $vmDeployTimeStamp + } + catch { + $value = if ($null -eq $vmDeployTimeStamp) { 'null' } else { $vmDeployTimeStamp } + Write-PSFMessage -Level Host -Message 'VM tag {0} with value {1} is not a valid date' -StringValues $TagDeployTimestamp, $value + if ($FixSessionHostTags) { + Write-PSFMessage -Level Host -Message 'Copying VM CreateTime to tag {0} with value {1}' -StringValues $TagDeployTimestamp, $vm.TimeCreated.ToString('o') + Update-AzTag -ResourceId $item.ResourceId -Tag @{ $TagDeployTimestamp = $vm.TimeCreated.ToString('o') } -Operation Merge + } + $vmDeployTimeStamp = $vm.TimeCreated + } + #endregion: Tag DeployTimestamp + + #region: Tag IncludeInAutomation + $vmIncludeInAutomation = $vmTags.Properties.TagsProperty[$TagIncludeInAutomation] + if ($vmIncludeInAutomation -eq "True") { + Write-PSFMessage -Level Host -Message 'VM has a tag {0} with value {1}' -StringValues $TagIncludeInAutomation, $vmIncludeInAutomation + $vmIncludeInAutomation = $true + } + elseif ($vmIncludeInAutomation -eq "False") { + Write-PSFMessage -Level Host -Message 'VM has a tag {0} with value {1}' -StringValues $TagIncludeInAutomation, $vmIncludeInAutomation + $vmIncludeInAutomation = $false + } + else { + $value = if ($null -eq $vmIncludeInAutomation) { 'null' } else { $vmIncludeInAutomation } + Write-PSFMessage -Level Host -Message 'VM tag {0} with value {1} is not set to True/False' -StringValues $TagIncludeInAutomation, $value + if ($FixSessionHostTags) { + Write-PSFMessage -Level Host -Message 'Setting tag {0} to {1}' -StringValues $TagIncludeInAutomation, $IncludePreExistingSessionHosts + Update-AzTag -ResourceId $item.ResourceId -Tag @{ $TagIncludeInAutomation = "$IncludePreExistingSessionHosts" } -Operation Merge + } + + $vmIncludeInAutomation = $IncludePreExistingSessionHosts + } + #endregion: Tag IncludeInAutomation + + #region: Tag PendingDrainTimeStamp + $vmPendingDrainTimeStamp = $vmTags.Properties.TagsProperty[$TagPendingDrainTimeStamp] + try { + $vmPendingDrainTimeStamp = [DateTime]::Parse($vmPendingDrainTimeStamp) + Write-PSFMessage -Level Host -Message 'VM has a tag {0} with value {1}' -StringValues $TagPendingDrainTimeStamp, $vmPendingDrainTimeStamp + } + catch { + Write-PSFMessage -Level Host -Message "VM tag {0} is not set." -StringValues $TagPendingDrainTimeStamp + $vmPendingDrainTimeStamp = $null + } + + #endregion: Tag PendingDrainTimeStamp + + $vmOutput = @{ # We are combining the VM details and SessionHost objects into a single PS Custom Object + VMName = $vm.Name + FQDN = $item.Name -replace ".+\/(.+)", '$1' + DeployTimestamp = $vmDeployTimeStamp + IncludeInAutomation = $vmIncludeInAutomation + PendingDrainTimeStamp = $vmPendingDrainTimeStamp + ImageVersion = $vm.StorageProfile.ImageReference.ExactVersion + } + $item.PSObject.Properties.ForEach{ $vmOutput[$_.Name] = $_.Value } + + [PSCustomObject]$vmOutput + + } + + $result } \ No newline at end of file From 783b4187e1af0bb58032dc71c43a9fde771c261e Mon Sep 17 00:00:00 2001 From: Stefano Zeglio Date: Mon, 23 Mar 2026 12:00:00 +0100 Subject: [PATCH 73/83] Disable host removal and add managed suffix baseline --- Build/Bicep/FunctionApps.bicep | 7 + FunctionApp/FunctionParameters.psd1 | 1 + .../functions/Deploy-SHRSessionHost.ps1 | 6 +- .../functions/Get-SHRHostPoolDecision.ps1 | 95 +- .../functions/Remove-SHRSessionHost.ps1 | 3 + FunctionApp/TimerTrigger1/run.ps1 | 10 +- deploy/arm/DeployAVDSessionHostReplacer.json | 11 + deploy/custom/FunctionApp_Template.json | 1497 +++++++++++++++++ 8 files changed, 1580 insertions(+), 50 deletions(-) create mode 100644 deploy/custom/FunctionApp_Template.json diff --git a/Build/Bicep/FunctionApps.bicep b/Build/Bicep/FunctionApps.bicep index 23e18bf..4aeb2ef 100644 --- a/Build/Bicep/FunctionApps.bicep +++ b/Build/Bicep/FunctionApps.bicep @@ -82,6 +82,9 @@ param SubnetId string @description('Required: No | Number of digits to use for the instance number of the session hosts (eg. AVDVM-01). | Default: 2') param SessionHostInstanceNumberPadding int = 2 +@description('Required: No | Minimum numeric suffix for managed session hosts. Hosts below this suffix are ignored when calculating how many new hosts to deploy. | Default: 1025') +param ManagedSessionHostMinSuffix int = 1025 + @description('Required: No | If true, will replace session hosts when a new image version is detected. | Default: true') param ReplaceSessionHostOnNewImageVersion bool = true @@ -170,6 +173,10 @@ var varFunctionAppSettings = [ name: '_SessionHostInstanceNumberPadding' value: SessionHostInstanceNumberPadding } + { + name: '_ManagedSessionHostMinSuffix' + value: ManagedSessionHostMinSuffix + } { name: '_TargetSessionHostCount' value: TargetSessionHostCount diff --git a/FunctionApp/FunctionParameters.psd1 b/FunctionApp/FunctionParameters.psd1 index a5e9d3f..20609e3 100644 --- a/FunctionApp/FunctionParameters.psd1 +++ b/FunctionApp/FunctionParameters.psd1 @@ -9,6 +9,7 @@ _IncludePreExistingSessionHosts = @{Required = $false ; Type = 'bool ' ; Default = $false ; Description = 'When enabled, the Session Host Replacer will automatically consider pre-existing VMs for replacement if they meet the criteria by setting the IncludeInAutomation tag to True during the first run. When disabled, the session hosts are not counted as part of the target number of VMs. You can manually include a VM after deployment by updating its tag.' } _SHRDeploymentPrefix = @{Required = $false ; Type = 'string' ; Default = 'AVDSessionHostReplacer' ; Description = '' } _SessionHostInstanceNumberPadding = @{Required = $false ; Type = 'int ' ; Default = 2 ; Description = '' } + _ManagedSessionHostMinSuffix = @{Required = $false ; Type = 'int ' ; Default = 1025 ; Description = 'Minimum numeric suffix to consider when calculating target host count and allocating new VM names.' } _ReplaceSessionHostOnNewImageVersion = @{Required = $false ; Type = 'bool ' ; Default = $true ; Description = '' } _ReplaceSessionHostOnNewImageVersionDelayDays = @{Required = $false ; Type = 'int ' ; Default = 0 ; Description = '' } _VMNamesTemplateParameterName = @{Required = $false ; Type = 'string' ; Default = 'VMNames' ; Description = 'The name of the array parameter used in the Session Host deployment template to define the VM names. Default is "VMNames"' } diff --git a/FunctionApp/Modules/SessionHostReplacer/functions/Deploy-SHRSessionHost.ps1 b/FunctionApp/Modules/SessionHostReplacer/functions/Deploy-SHRSessionHost.ps1 index a1162ce..7992db9 100644 --- a/FunctionApp/Modules/SessionHostReplacer/functions/Deploy-SHRSessionHost.ps1 +++ b/FunctionApp/Modules/SessionHostReplacer/functions/Deploy-SHRSessionHost.ps1 @@ -25,6 +25,9 @@ function Deploy-SHRSessionHost { [Parameter()] [int] $SessionHostInstanceNumberPadding = (Get-FunctionConfig _SessionHostInstanceNumberPadding), + [Parameter()] + [int] $ManagedSessionHostMinSuffix = (Get-FunctionConfig _ManagedSessionHostMinSuffix), + [Parameter()] [string] $DeploymentPrefix = (Get-FunctionConfig _SHRDeploymentPrefix), @@ -51,13 +54,14 @@ function Deploy-SHRSessionHost { # Calculate Session Host Names Write-PSFMessage -Level Host -Message "Existing session host VM names: {0}" -StringValues ($ExistingSessionHostVMNames -join ',') + $vmNumber = [Math]::Max(1, $ManagedSessionHostMinSuffix) [array] $sessionHostNames = for ($i = 0; $i -lt $NewSessionHostsCount; $i++) { - $vmNumber = 1 While (("$SessionHostNamePrefix$SessionHostNameSeparator{0:d$SessionHostInstanceNumberPadding}" -f $vmNumber) -in $ExistingSessionHostVMNames) { $vmNumber++ } $vmName = "$SessionHostNamePrefix$SessionHostNameSeparator{0:d$SessionHostInstanceNumberPadding}" -f $vmNumber $ExistingSessionHostVMNames += $vmName + $vmNumber++ $vmName } Write-PSFMessage -Level Host -Message "Creating session host(s) {0}" -StringValues ($sessionHostNames -join ',') diff --git a/FunctionApp/Modules/SessionHostReplacer/functions/Get-SHRHostPoolDecision.ps1 b/FunctionApp/Modules/SessionHostReplacer/functions/Get-SHRHostPoolDecision.ps1 index 9cf65d9..e084b05 100644 --- a/FunctionApp/Modules/SessionHostReplacer/functions/Get-SHRHostPoolDecision.ps1 +++ b/FunctionApp/Modules/SessionHostReplacer/functions/Get-SHRHostPoolDecision.ps1 @@ -34,15 +34,58 @@ function Get-SHRHostPoolDecision { # Delay days before replacing session hosts on new image version [Parameter()] - [int] $ReplaceSessionHostOnNewImageVersionDelayDays = (Get-FunctionConfig _ReplaceSessionHostOnNewImageVersionDelayDays) + [int] $ReplaceSessionHostOnNewImageVersionDelayDays = (Get-FunctionConfig _ReplaceSessionHostOnNewImageVersionDelayDays), + + # Minimum numeric suffix for session hosts managed by this function. + [Parameter()] + [int] $ManagedSessionHostMinSuffix = (Get-FunctionConfig _ManagedSessionHostMinSuffix) ) + + function Get-SHRSessionHostNumericSuffix { + param( + [Parameter(Mandatory = $true)] + [string] $VMName + ) + + $suffixMatch = [regex]::Match($VMName, '(\d+)$') + if (-not $suffixMatch.Success) { + return $null + } + + [int]$suffixMatch.Groups[1].Value + } + # Basic Info Write-PSFMessage -Level Host -Message "We have {0} session hosts (included in Automation)" -StringValues $SessionHosts.Count - [array] $deletionEligibleSessionHosts = $SessionHosts | Where-Object { [string]::IsNullOrWhiteSpace($_.AssignedUser) } + [array] $managedSessionHosts = foreach ($sessionHost in $SessionHosts) { + $numericSuffix = Get-SHRSessionHostNumericSuffix -VMName $sessionHost.VMName + if ($null -eq $numericSuffix -or $numericSuffix -ge $ManagedSessionHostMinSuffix) { + $sessionHost + } + } + + [array] $legacySessionHosts = foreach ($sessionHost in $SessionHosts) { + $numericSuffix = Get-SHRSessionHostNumericSuffix -VMName $sessionHost.VMName + if ($null -ne $numericSuffix -and $numericSuffix -lt $ManagedSessionHostMinSuffix) { + $sessionHost + } + } + + Write-PSFMessage -Level Host -Message "Managed session host suffix baseline is {0}" -StringValues $ManagedSessionHostMinSuffix + Write-PSFMessage -Level Host -Message "Found {0} managed session hosts (suffix >= baseline or non-numeric suffix)." -StringValues $managedSessionHosts.Count + Write-PSFMessage -Level Host -Message "Ignoring {0} legacy session hosts with suffix below baseline for deployment count evaluation." -StringValues $legacySessionHosts.Count + + [array] $managedRunningDeployments = foreach ($runningSessionHostName in $RunningDeployments.SessionHostNames) { + $numericSuffix = Get-SHRSessionHostNumericSuffix -VMName $runningSessionHostName + if ($null -eq $numericSuffix -or $numericSuffix -ge $ManagedSessionHostMinSuffix) { + $runningSessionHostName + } + } + + [array] $deletionEligibleSessionHosts = $managedSessionHosts | Where-Object { [string]::IsNullOrWhiteSpace($_.AssignedUser) } [array] $assignedSessionHosts = $SessionHosts | Where-Object { -not [string]::IsNullOrWhiteSpace($_.AssignedUser) } - Write-PSFMessage -Level Host -Message "Found {0} session hosts assigned to users. These hosts are excluded from removal." -StringValues $assignedSessionHosts.Count - Write-PSFMessage -Level Host -Message "Found {0} session hosts eligible for removal." -StringValues $deletionEligibleSessionHosts.Count + Write-PSFMessage -Level Host -Message "Found {0} session hosts assigned to users." -StringValues $assignedSessionHosts.Count # Identify Session hosts that should be replaced if ($TargetVMAgeDays -gt 0) { @@ -67,18 +110,17 @@ function Get-SHRHostPoolDecision { # Good Session Hosts - $goodSessionHosts = $SessionHosts | Where-Object { $_.VMName -notin $sessionHostsToReplace.VMName } - $sessionHostsCurrentTotal = ([array]$goodSessionHosts.VMName + [array]$runningDeployments.SessionHostNames ) | Select-Object -Unique + $managedSessionHostsCurrentTotal = ([array]$managedSessionHosts.VMName + [array]$managedRunningDeployments ) | Select-Object -Unique - Write-PSFMessage -Level Host -Message "We have {0} good session hosts including {1} session hosts being deployed" -StringValues $sessionHostsCurrentTotal.Count, $runningDeployments.SessionHostNames.Count + Write-PSFMessage -Level Host -Message "We have {0} managed session hosts including {1} managed session hosts being deployed" -StringValues $managedSessionHostsCurrentTotal.Count, $managedRunningDeployments.Count Write-PSFMessage -Level Host -Message "We target having {0} session hosts in good shape" -StringValues $TargetSessionHostCount Write-PSFMessage -Level Host -Message "We have a buffer of {0} session hosts more than the target." -StringValues $TargetSessionHostBuffer - $weCanDeployUpTo = $TargetSessionHostCount + $TargetSessionHostBuffer - $SessionHosts.count - $RunningDeployments.SessionHostNames.Count + $weCanDeployUpTo = $TargetSessionHostCount + $TargetSessionHostBuffer - $managedSessionHosts.count - $managedRunningDeployments.Count if ($weCanDeployUpTo -ge 0) { Write-PSFMessage -Level Host -Message "We can deploy up to {0} session hosts" -StringValues $weCanDeployUpTo - $weNeedToDeploy = $TargetSessionHostCount - $sessionHostsCurrentTotal.Count + $weNeedToDeploy = $TargetSessionHostCount - $managedSessionHostsCurrentTotal.Count if ($weNeedToDeploy -gt 0) { Write-PSFMessage -Level Host -Message "We need to deploy {0} new session hosts" -StringValues $weNeedToDeploy $weCanDeploy = if ($weNeedToDeploy -gt $weCanDeployUpTo) { $weCanDeployUpTo } else { $weNeedToDeploy } # If we need to deploy 10 machines, and we can deploy 5, we should only deploy 5. @@ -96,43 +138,14 @@ function Get-SHRHostPoolDecision { $weCanDelete = 0 - $requestedDeleteCount = $SessionHosts.Count - $TargetSessionHostCount - if ($requestedDeleteCount -gt 0) { - Write-PSFMessage -Level Host -Message "We need to delete {0} session hosts" -StringValues $requestedDeleteCount - - $weCanDelete = [Math]::Min($requestedDeleteCount, $deletionEligibleSessionHosts.Count) - if ($weCanDelete -lt $requestedDeleteCount) { - Write-PSFMessage -Level Warning -Message "Can only delete {0} session hosts because {1} hosts are assigned to users and protected from removal." -StringValues $weCanDelete, ($requestedDeleteCount - $weCanDelete) - } - - if ($weCanDelete -gt $sessionHostsToReplace.Count) { - Write-PSFMessage -Level Host -Message "Host pool is over populated" - - $goodSessionHostsToDeleteCount = $weCanDelete - $sessionHostsToReplace.Count - Write-PSFMessage -Level Host -Message "We will delete {0} good session hosts" -StringValues $goodSessionHostsToDeleteCount - - $selectedGoodHostsTotDelete = [array] ($goodSessionHosts | Where-Object { [string]::IsNullOrWhiteSpace($_.AssignedUser) } | Sort-Object -Property Session | Select-Object -First $goodSessionHostsToDeleteCount) - Write-PSFMessage -Level Host -Message "Selected the following good session hosts to delete: {0}" -StringValues ($selectedGoodHostsTotDelete.VMName -join ',') - } - else { - $selectedGoodHostsTotDelete = @() - Write-PSFMessage -Level Host -Message "Host pool is not over populated" - } - - $sessionHostsPendingDelete = ($sessionHostsToReplace + $selectedGoodHostsTotDelete) | Select-Object -First $weCanDelete - Write-PSFMessage -Level Host -Message "The following Session Hosts are now pending delete: {0}" -StringValues ($SessionHostsPendingDelete.VMName -join ',') - - } - elseif ($sessionHostsToReplace.Count -gt 0) { - Write-PSFMessage -Level Host -Message "We need to delete {0} session hosts but we don't have enough session hosts in the host pool." -StringValues ($sessionHostsToReplace.Count) - } - else { Write-PSFMessage -Level Host -Message "We do not need to delete any session hosts" } + $sessionHostsPendingDelete = @() + Write-PSFMessage -Level Host -Message "Session host decommissioning is disabled. No session hosts will be deleted." [PSCustomObject]@{ PossibleDeploymentsCount = $weCanDeploy PossibleSessionHostDeleteCount = $weCanDelete SessionHostsPendingDelete = $sessionHostsPendingDelete - ExistingSessionHostVMNames = ([array]$SessionHosts.VMName + [array]$runningDeployments.SessionHostNames) | Select-Object -Unique + ExistingSessionHostVMNames = ([array]$SessionHosts.VMName + [array]$managedRunningDeployments) | Select-Object -Unique } } \ No newline at end of file diff --git a/FunctionApp/Modules/SessionHostReplacer/functions/Remove-SHRSessionHost.ps1 b/FunctionApp/Modules/SessionHostReplacer/functions/Remove-SHRSessionHost.ps1 index 798583c..46e0070 100644 --- a/FunctionApp/Modules/SessionHostReplacer/functions/Remove-SHRSessionHost.ps1 +++ b/FunctionApp/Modules/SessionHostReplacer/functions/Remove-SHRSessionHost.ps1 @@ -28,6 +28,9 @@ function Remove-SHRSessionHost { ) + Write-PSFMessage -Level Warning -Message 'Remove-SHRSessionHost is disabled. No drain or delete actions will be performed.' + return + foreach ($sessionHost in $SessionHostsPendingDelete) { # Does the session host currently have sessions? # No sessions => Delete + Remove from host pool diff --git a/FunctionApp/TimerTrigger1/run.ps1 b/FunctionApp/TimerTrigger1/run.ps1 index 3c3fe04..b74c0a7 100644 --- a/FunctionApp/TimerTrigger1/run.ps1 +++ b/FunctionApp/TimerTrigger1/run.ps1 @@ -50,14 +50,8 @@ if ($hostPoolDecisions.PossibleDeploymentsCount -gt 0) { Deploy-SHRSessionHost -SessionHostResourceGroupName $sessionHostResourceGroupName -NewSessionHostsCount $hostPoolDecisions.PossibleDeploymentsCount -ExistingSessionHostVMNames $existingSessionHostVMNames } -# Delete session hosts -if ($hostPoolDecisions.PossibleSessionHostDeleteCount -gt 0 -and $hostPoolDecisions.SessionHostsPendingDelete.Count -gt 0) { - Write-PSFMessage -Level Host -Message "We will decommission {0} session hosts from this list: {1}" -StringValues $hostPoolDecisions.SessionHostsPendingDelete.Count, ($hostPoolDecisions.SessionHostsPendingDelete.VMName -join ',') - # Decommission session hosts - $removeEntraDevice = Get-FunctionConfig _RemoveEntraDevice - $removeIntuneDevice = Get-FunctionConfig _RemoveIntuneDevice - Remove-SHRSessionHost -SessionHostsPendingDelete $hostPoolDecisions.SessionHostsPendingDelete -RemoveEntraDevice $removeEntraDevice -RemoveIntuneDevice $removeIntuneDevice -} +# Delete session hosts capability is intentionally disabled. +Write-PSFMessage -Level Host -Message "Session host decommissioning is disabled by configuration and no delete actions will be taken." # Write an information log with the current time. diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 46a3ccd..96bdb80 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -305,6 +305,13 @@ "description": "Required: No | Number of digits to use for the instance number of the session hosts (eg. AVDVM-01). | Default: 2" } }, + "ManagedSessionHostMinSuffix": { + "type": "int", + "defaultValue": 1025, + "metadata": { + "description": "Required: No | Minimum numeric suffix for managed session hosts. Hosts below this suffix are ignored when calculating how many new hosts to deploy. | Default: 1025" + } + }, "ReplaceSessionHostOnNewImageVersion": { "type": "bool", "defaultValue": true, @@ -548,6 +555,10 @@ "name": "_SessionHostInstanceNumberPadding", "value": "[parameters('SessionHostInstanceNumberPadding')]" }, + { + "name": "_ManagedSessionHostMinSuffix", + "value": "[parameters('ManagedSessionHostMinSuffix')]" + }, { "name": "_ReplaceSessionHostOnNewImageVersion", "value": "[parameters('ReplaceSessionHostOnNewImageVersion')]" diff --git a/deploy/custom/FunctionApp_Template.json b/deploy/custom/FunctionApp_Template.json new file mode 100644 index 0000000..8370a34 --- /dev/null +++ b/deploy/custom/FunctionApp_Template.json @@ -0,0 +1,1497 @@ +{ + "$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": "16749693425937561368" + } + }, + "parameters": { + "Location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Required: No | Region of the Function App. This does not need to be the same as the location of the Azure Virtual Desktop Host Pool. | Default: Location of the resource group." + } + }, + "EnableMonitoring": { + "type": "bool", + "defaultValue": true + }, + "UseExistingLAW": { + "type": "bool", + "defaultValue": false + }, + "LogAnalyticsWorkspaceId": { + "type": "string", + "defaultValue": "none", + "metadata": { + "description": "Required: Yes | Name of the Log Analytics Workspace used by the Function App Insights." + } + }, + "UseStandardTemplate": { + "type": "bool", + "defaultValue": true + }, + "SessionHostsRegion": { + "type": "string", + "defaultValue": "" + }, + "AvailabilityZones": { + "type": "array", + "defaultValue": [] + }, + "SessionHostSize": { + "type": "string", + "defaultValue": "" + }, + "AcceleratedNetworking": { + "type": "bool", + "defaultValue": false + }, + "SessionHostDiskType": { + "type": "string", + "defaultValue": "Premium_LRS", + "allowedValues": [ + "Standard_LRS", + "StandardSSD_LRS", + "Premium_LRS" + ] + }, + "DiskSizeGB": { + "type": "int", + "defaultValue": 64, + "metadata": { + "description": "OS disk size in GB" + } + }, + "DnsServers": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "List of DNS server IP addresses to set on the NIC" + } + }, + "MarketPlaceOrCustomImage": { + "type": "string", + "defaultValue": "Marketplace", + "allowedValues": [ + "Marketplace", + "Gallery" + ] + }, + "MarketPlaceImage": { + "type": "string", + "defaultValue": "win11-23h2-avd-m365", + "allowedValues": [ + "2022-datacenter-smalldisk-g2", + "win10-21h2-avd", + "2022-datacenter-core-g2", + "win10-22h2-avd-m365-g2", + "win11-21h2-avd", + "win10-21h2-avd-m365", + "win11-22h2-avd-m365", + "2022-datacenter-core-smalldisk-g2", + "win11-21h2-avd-m365", + "win11-23h2-avd", + "win11-25h2-avd-m365", + "win11-22h2-avd", + "2022-datacenter-g2", + "win10-22h2-avd-g2" + ] + }, + "GalleryImageId": { + "type": "string", + "defaultValue": "" + }, + "SecurityType": { + "type": "string", + "defaultValue": "TrustedLaunch", + "allowedValues": [ + "Standard", + "TrustedLaunch", + "ConfidentialVM" + ] + }, + "SecureBootEnabled": { + "type": "bool", + "defaultValue": true + }, + "TpmEnabled": { + "type": "bool", + "defaultValue": true + }, + "SubnetId": { + "type": "string", + "defaultValue": "" + }, + "IdentityServiceProvider": { + "type": "string", + "defaultValue": "EntraID", + "allowedValues": [ + "EntraID", + "ActiveDirectory", + "EntraDS" + ] + }, + "IntuneEnrollment": { + "type": "bool", + "defaultValue": false + }, + "ADDomainName": { + "type": "string", + "defaultValue": "" + }, + "ADDomainJoinUserName": { + "type": "string", + "defaultValue": "" + }, + "ADJoinUserPassword": { + "type": "securestring", + "defaultValue": "" + }, + "ADOUPath": { + "type": "string", + "defaultValue": "" + }, + "LocalAdminUsername": { + "type": "string", + "defaultValue": "" + }, + "CustomTemplateSpecResourceId": { + "type": "string", + "defaultValue": "" + }, + "VMNamesTemplateParameterName": { + "type": "string", + "defaultValue": "VMNames", + "metadata": { + "description": "Required: No | The name of the parameter in the template that specifies the VM Names array." + } + }, + "CustomTemplateSpecParameters": { + "type": "object", + "defaultValue": {} + }, + "HostPoolResourceGroupName": { + "type": "string", + "defaultValue": "[resourceGroup().name]", + "metadata": { + "description": "Required: No | Name of the resource group containing the Azure Virtual Desktop Host Pool. | Default: The resource group of the Function App." + } + }, + "HostPoolName": { + "type": "string", + "metadata": { + "description": "Required: Yes | Name of the Azure Virtual Desktop Host Pool." + } + }, + "SessionHostNamePrefix": { + "type": "string", + "maxLength": 12, + "metadata": { + "description": "Required: Yes | Prefix used for the name of the session hosts." + } + }, + "SessionHostNameSeparator": { + "type": "string", + "defaultValue": "-", + "maxLength": 1, + "metadata": { + "description": "Required: NO | Separator between prefix and number. | Default: -" + } + }, + "TargetSessionHostCount": { + "type": "int", + "minValue": 0, + "metadata": { + "description": "Required: Yes | Number of session hosts to maintain in the host pool." + } + }, + "TargetSessionHostBuffer": { + "type": "int", + "minValue": 1, + "metadata": { + "description": "Required: Yes | The maximum number of session hosts to add during a replacement process" + } + }, + "UseGovDodGraph": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Required: No | Switches to using the US Governmment DoD graph endpoints for the Function App. | Default: false" + } + }, + "UseUserAssignedManagedIdentity": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Required: No | Resource Id of the User Assigned Managed Identity to use for the Function App. | Default: System Identity" + } + }, + "UserAssignedManagedIdentityResourceId": { + "type": "string", + "defaultValue": "" + }, + "TagIncludeInAutomation": { + "type": "string", + "defaultValue": "IncludeInAutoReplace", + "metadata": { + "description": "Required: No | Tag name used to indicate that a session host should be included in the automatic replacement process. | Default: IncludeInAutoReplace." + } + }, + "TagDeployTimestamp": { + "type": "string", + "defaultValue": "AutoReplaceDeployTimestamp", + "metadata": { + "description": "Required: No | Tag name used to indicate the timestamp of the last deployment of a session host. | Default: AutoReplaceDeployTimestamp." + } + }, + "TagPendingDrainTimestamp": { + "type": "string", + "defaultValue": "AutoReplacePendingDrainTimestamp", + "metadata": { + "description": "Required: No | Tag name used to indicate drain timestamp of session host pending deletion. | Default: AutoReplacePendingDrainTimestamp." + } + }, + "TagScalingPlanExclusionTag": { + "type": "string", + "defaultValue": "ScalingPlanExclusion", + "metadata": { + "description": "Required: No | Tag name used to exclude session host from Scaling Plan activities. | Default: ScalingPlanExclusion" + } + }, + "TargetVMAgeDays": { + "type": "int", + "defaultValue": 45, + "metadata": { + "description": "Required: No | Target age of session hosts in days. | Default: 45 days." + } + }, + "DrainGracePeriodHours": { + "type": "int", + "defaultValue": 24, + "metadata": { + "description": "Required: No | Grace period in hours for session hosts to drain before deletion. | Default: 24 hours." + } + }, + "FixSessionHostTags": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Required: No | If true, will apply tags for Include In Auto Replace and Deployment Timestamp to existing session hosts. This will not enable automatic deletion of existing session hosts. | Default: True." + } + }, + "IncludePreExistingSessionHosts": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Required: No | When enabled, the Session Host Replacer will automatically consider pre-existing VMs for replacement if they meet the criteria | Default: False." + } + }, + "SHRDeploymentPrefix": { + "type": "string", + "defaultValue": "AVDSessionHostReplacer", + "metadata": { + "description": "Required: No | Prefix used for the deployment name of the session hosts. | Default: AVDSessionHostReplacer" + } + }, + "SessionHostInstanceNumberPadding": { + "type": "int", + "defaultValue": 2, + "metadata": { + "description": "Required: No | Number of digits to use for the instance number of the session hosts (eg. AVDVM-01). | Default: 2" + } + }, + "ManagedSessionHostMinSuffix": { + "type": "int", + "defaultValue": 1025, + "metadata": { + "description": "Required: No | Minimum numeric suffix for managed session hosts. Hosts below this suffix are ignored when calculating how many new hosts to deploy. | Default: 1025" + } + }, + "ReplaceSessionHostOnNewImageVersion": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Required: No | If true, will replace session hosts when a new image version is detected. | Default: true" + } + }, + "ReplaceSessionHostOnNewImageVersionDelayDays": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Required: No | Delay in days before replacing session hosts when a new image version is detected. | Default: 0 (no delay)." + } + }, + "SessionHostResourceGroupName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Required: No | Leave this empty to deploy to same resource group as the host pool." + } + }, + "TimeStamp": { + "type": "string", + "defaultValue": "[utcNow()]" + } + }, + "variables": { + "varMarketPlaceImages": { + "win10-21h2-avd": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "windows-10", + "sku": "win10-21h2-avd" + }, + "win10-21h2-avd-g2": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "windows-10", + "sku": "win10-21h2-avd-g2" + }, + "win10-21h2-avd-m365": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "office-365", + "sku": "win10-21h2-avd-m365" + }, + "win10-21h2-avd-m365-g2": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "office-365", + "sku": "win10-21h2-avd-m365-g2" + }, + "win10-22h2-avd": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "windows-10", + "sku": "win10-22h2-avd" + }, + "win10-22h2-avd-g2": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "windows-10", + "sku": "win10-22h2-avd-g2" + }, + "win10-22h2-avd-m365": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "office-365", + "sku": "win10-22h2-avd-m365" + }, + "win10-22h2-avd-m365-g2": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "office-365", + "sku": "win10-22h2-avd-m365-g2" + }, + "win11-21h2-avd": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "windows-11", + "sku": "win11-21h2-avd" + }, + "win11-21h2-avd-m365": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "office-365", + "sku": "win11-21h2-avd-m365" + }, + "win11-22h2-avd": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "windows-11", + "sku": "win11-22h2-avd" + }, + "win11-22h2-avd-m365": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "office-365", + "sku": "win11-22h2-avd-m365" + }, + "win11-23h2-avd": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "windows-11", + "sku": "win11-23h2-avd" + }, + "win11-25h2-avd-m365": { + "publisher": "MicrosoftWindowsDesktop", + "offer": "office-365", + "sku": "win11-25h2-avd-m365" + } + }, + "varImageReference": "[if(equals(parameters('MarketPlaceOrCustomImage'), 'Marketplace'), createObject('publisher', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].publisher, 'offer', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].offer, 'sku', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].sku, 'version', 'latest'), createObject('Id', parameters('GalleryImageId')))]", + "varSecurityProfile": "[if(equals(parameters('SecurityType'), 'Standard'), null(), createObject('securityType', parameters('SecurityType'), 'uefiSettings', createObject('secureBootEnabled', parameters('SecureBootEnabled'), 'vTpmEnabled', parameters('TpmEnabled'))))]", + "varDomainJoinObject": "[if(equals(parameters('IdentityServiceProvider'), 'EntraID'), createObject('DomainType', 'EntraID', 'IntuneJoin', parameters('IntuneEnrollment')), createObject('DomainType', 'ActiveDirectory', 'DomainName', parameters('ADDomainName'), 'DomainJoinUserName', parameters('ADDomainJoinUserName'), 'ADOUPath', parameters('ADOUPath')))]", + "varAzureEnvironments": [ + "AzureCloud", + "AzureUSGovernment", + "AzureChinaCloud" + ], + "splitParts": "[split(parameters('HostPoolResourceGroupName'), '-')]", + "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4], '-', variables('splitParts')[5])]", + "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", + "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", + "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", + "varFunctionAppName": "[concat('AVDReplacer', variables('rgpattern'))]", + "varFunctionAppIdentity": "[if(parameters('UseUserAssignedManagedIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', parameters('UserAssignedManagedIdentityResourceId')), createObject())), createObject('type', 'SystemAssigned'))]" + }, + "resources": [ + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "deployFunctionApp", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "Location": { + "value": "[parameters('Location')]" + }, + "FunctionAppName": { + "value": "[variables('varFunctionAppName')]" + }, + "EnableMonitoring": { + "value": "[parameters('EnableMonitoring')]" + }, + "UseExistingLAW": { + "value": "[parameters('UseExistingLAW')]" + }, + "LogAnalyticsWorkspaceId": { + "value": "[parameters('LogAnalyticsWorkspaceId')]" + }, + "ReplacementPlanSettings": { + "value": [ + { + "name": "_HostPoolResourceGroupName", + "value": "[parameters('HostPoolResourceGroupName')]" + }, + { + "name": "_HostPoolName", + "value": "[parameters('HostPoolName')]" + }, + { + "name": "_TargetSessionHostCount", + "value": "[parameters('TargetSessionHostCount')]" + }, + { + "name": "_TargetSessionHostBuffer", + "value": "[parameters('TargetSessionHostBuffer')]" + }, + { + "name": "_SessionHostNamePrefix", + "value": "[parameters('SessionHostNamePrefix')]" + }, + { + "name": "_SessionHostNameSeparator", + "value": "[parameters('SessionHostNameSeparator')]" + }, + { + "name": "_SessionHostTemplate", + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate'), '2022-09-01').outputs.TemplateSpecResourceId.value]" + }, + { + "name": "_SessionHostParameters", + "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'DiskSizeGB', parameters('DiskSizeGB'), 'DnsServers', parameters('DnsServers'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" + }, + { + "name": "_SubscriptionId", + "value": "[subscription().subscriptionId]" + }, + { + "name": "_RemoveEntraDevice", + "value": "[equals(parameters('IdentityServiceProvider'), 'EntraID')]" + }, + { + "name": "_RemoveIntuneDevice", + "value": "[parameters('IntuneEnrollment')]" + }, + { + "name": "_ClientId", + "value": "[if(parameters('UseUserAssignedManagedIdentity'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').clientId, '')]" + }, + { + "name": "_TenantId", + "value": "[if(parameters('UseUserAssignedManagedIdentity'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').tenantId, '')]" + }, + { + "name": "_GraphEnvironmentName", + "value": "[variables('varGraphEnvironmentName')]" + }, + { + "name": "_AzureEnvironmentName", + "value": "[environment().name]" + }, + { + "name": "_Tag_IncludeInAutomation", + "value": "[parameters('TagIncludeInAutomation')]" + }, + { + "name": "_Tag_DeployTimestamp", + "value": "[parameters('TagDeployTimestamp')]" + }, + { + "name": "_Tag_PendingDrainTimestamp", + "value": "[parameters('TagPendingDrainTimestamp')]" + }, + { + "name": "_Tag_ScalingPlanExclusionTag", + "value": "[parameters('TagScalingPlanExclusionTag')]" + }, + { + "name": "_TargetVMAgeDays", + "value": "[parameters('TargetVMAgeDays')]" + }, + { + "name": "_DrainGracePeriodHours", + "value": "[parameters('DrainGracePeriodHours')]" + }, + { + "name": "_FixSessionHostTags", + "value": "[parameters('FixSessionHostTags')]" + }, + { + "name": "_IncludePreExistingSessionHosts", + "value": "[parameters('IncludePreExistingSessionHosts')]" + }, + { + "name": "_SHRDeploymentPrefix", + "value": "[parameters('SHRDeploymentPrefix')]" + }, + { + "name": "_SessionHostInstanceNumberPadding", + "value": "[parameters('SessionHostInstanceNumberPadding')]" + }, + { + "name": "_ManagedSessionHostMinSuffix", + "value": "[parameters('ManagedSessionHostMinSuffix')]" + }, + { + "name": "_ReplaceSessionHostOnNewImageVersion", + "value": "[parameters('ReplaceSessionHostOnNewImageVersion')]" + }, + { + "name": "_ReplaceSessionHostOnNewImageVersionDelayDays", + "value": "[parameters('ReplaceSessionHostOnNewImageVersionDelayDays')]" + }, + { + "name": "_VMNamesTemplateParameterName", + "value": "[parameters('VMNamesTemplateParameterName')]" + }, + { + "name": "_SessionHostResourceGroupName", + "value": "[parameters('SessionHostResourceGroupName')]" + } + ] + }, + "FunctionAppIdentity": { + "value": "[variables('varFunctionAppIdentity')]" + } + }, + "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": "3734734378229800954" + } + }, + "parameters": { + "Location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Required: No | Region of the Function App. This does not need to be the same as the location of the Azure Virtual Desktop Host Pool. | Default: Location of the resource group." + } + }, + "EnableMonitoring": { + "type": "bool", + "defaultValue": true + }, + "UseExistingLAW": { + "type": "bool", + "defaultValue": false + }, + "LogAnalyticsWorkspaceId": { + "type": "string", + "defaultValue": "none", + "metadata": { + "description": "Required: Yes | Name of the Log Analytics Workspace used by the Function App Insights." + } + }, + "FunctionAppName": { + "type": "string", + "metadata": { + "description": "Required: Yes | Name of the Function App." + } + }, + "FunctionAppUrl": { + "type": "string", + "defaultValue": "https://raw.githubusercontent.com/stefze/AVDSessionHostReplacer/migration/FunctionApp/FunctionApp.zip", + "metadata": { + "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." + } + }, + "AppPlanName": { + "type": "string", + "defaultValue": "Y1", + "metadata": { + "description": "Required: No | App Service Plan Name | Default: Y1 for consumption based plan" + } + }, + "AppPlanTier": { + "type": "string", + "defaultValue": "Dynamic", + "metadata": { + "description": "Required: No | App Service Plan Tier | Default: Dynamic for consumption based plan" + } + }, + "ReplacementPlanSettings": { + "type": "array", + "metadata": { + "description": "Required: Yes | The following settings are mandatory. Rest are optional.\n[\n {\n name: '_HostPoolResourceGroupName'\n value: 'string'\n }\n {\n name: '_HostPoolName'\n value: 'string'\n }\n {\n name: '_RemoveEntraDevice'\n value: 'bool'\n }\n {\n name: '_SessionHostTemplate'\n value: 'string'\n }\n {\n name: '_SessionHostParameters'\n value: 'hashtable'\n }\n {\n name: '_SubscriptionId'\n value: 'string'\n }\n {\n name: '_TargetSessionHostCount'\n value: 'int'\n }\n {\n name: '_SessionHostNamePrefix'\n value: 'string'\n }\n]" + } + }, + "FunctionAppIdentity": { + "type": "object", + "defaultValue": { + "type": "SystemAssigned" + } + } + }, + "variables": { + "rgname": "[toLower(resourceGroup().name)]", + "splitParts": "[split(variables('rgname'), '-')]", + "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", + "varStorageAccountName": "[concat('st', variables('rgpattern2'))]", + "varLogAnalyticsWorkspaceName": "[concat('law-', parameters('FunctionAppName'))]", + "varAppServicePlanName": "[concat('Asp-', parameters('FunctionAppName'))]", + "varGraphEnvironmentName": "your_value_here" + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-05-01", + "name": "[variables('varStorageAccountName')]", + "location": "[parameters('Location')]", + "kind": "StorageV2", + "sku": { + "name": "Standard_LRS" + }, + "properties": {} + }, + { + "condition": "[and(parameters('EnableMonitoring'), not(parameters('UseExistingLAW')))]", + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2023-09-01", + "name": "[variables('varLogAnalyticsWorkspaceName')]", + "location": "[parameters('Location')]", + "properties": { + "sku": { + "name": "PerGB2018" + }, + "retentionInDays": 30 + } + }, + { + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2022-03-01", + "name": "[variables('varAppServicePlanName')]", + "location": "[parameters('Location')]", + "sku": { + "name": "[parameters('AppPlanName')]", + "tier": "[parameters('AppPlanTier')]" + } + }, + { + "condition": "[parameters('EnableMonitoring')]", + "type": "Microsoft.Insights/components", + "apiVersion": "2020-02-02", + "name": "[variables('varAppServicePlanName')]", + "location": "[parameters('Location')]", + "kind": "web", + "properties": { + "Application_Type": "web", + "publicNetworkAccessForIngestion": "Enabled", + "publicNetworkAccessForQuery": "Enabled", + "WorkspaceResourceId": "[if(parameters('UseExistingLAW'), parameters('LogAnalyticsWorkspaceId'), resourceId('Microsoft.OperationalInsights/workspaces', variables('varLogAnalyticsWorkspaceName')))]" + }, + "dependsOn": [ + "[resourceId('Microsoft.OperationalInsights/workspaces', variables('varLogAnalyticsWorkspaceName'))]" + ] + }, + { + "type": "Microsoft.Web/sites", + "apiVersion": "2023-01-01", + "name": "[parameters('FunctionAppName')]", + "location": "[parameters('Location')]", + "kind": "functionApp", + "identity": "[parameters('FunctionAppIdentity')]", + "properties": { + "httpsOnly": true, + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('varAppServicePlanName'))]", + "siteConfig": { + "use32BitWorkerProcess": false, + "powerShellVersion": "7.4", + "netFrameworkVersion": "v6.0", + "appSettings": "[union(createArray(createObject('name', 'FUNCTIONS_EXTENSION_VERSION', 'value', '~4'), createObject('name', 'FUNCTIONS_WORKER_RUNTIME', 'value', 'powershell'), createObject('name', 'AzureWebJobsStorage', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey), createObject('name', 'WEBSITE_CONTENTSHARE', 'value', toLower(parameters('FunctionAppName')))), if(parameters('EnableMonitoring'), createArray(createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey)), createArray()), parameters('ReplacementPlanSettings'))]", + "ftpsState": "Disabled", + "cors": { + "allowedOrigins": [ + "https://portal.azure.com" + ] + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Insights/components', variables('varAppServicePlanName'))]", + "[resourceId('Microsoft.Web/serverfarms', variables('varAppServicePlanName'))]", + "[resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName'))]" + ] + }, + { + "type": "Microsoft.Web/sites/extensions", + "apiVersion": "2023-01-01", + "name": "[concat(parameters('FunctionAppName'), '/ZipDeploy')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', parameters('FunctionAppName'))]" + ], + "properties": { + "packageUri": "[parameters('FunctionAppUrl')]" + } + } + ], + "outputs": { + "functionAppPrincipalId": { + "type": "string", + "value": "[if(equals(parameters('FunctionAppIdentity').type, 'SystemAssigned'), reference(resourceId('Microsoft.Web/sites', parameters('FunctionAppName')), '2023-01-01', 'full').identity.principalId, '')]" + } + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'deployKeyVault')]", + "[resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate')]" + ] + }, + { + "condition": "[not(equals(parameters('IdentityServiceProvider'), 'EntraID'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "deployKeyVault", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "Location": { + "value": "[parameters('Location')]" + }, + "KeyVaultName": { + "value": "[concat('kvSHR', variables('rgpattern2'))]" + }, + "DomainJoinPassword": { + "value": "[parameters('ADJoinUserPassword')]" + } + }, + "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": "17584014780822218958" + } + }, + "parameters": { + "Location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + }, + "KeyVaultName": { + "type": "string" + }, + "DomainJoinPassword": { + "type": "securestring" + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('KeyVaultName'), 'DomainJoinPassword')]", + "properties": { + "value": "[parameters('DomainJoinPassword')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]" + ] + }, + { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-07-01", + "name": "[parameters('KeyVaultName')]", + "location": "[parameters('Location')]", + "properties": { + "sku": { + "family": "A", + "name": "standard" + }, + "tenantId": "[subscription().tenantId]", + "enabledForTemplateDeployment": true, + "enableRbacAuthorization": true + } + } + ], + "outputs": { + "keyVaultId": { + "type": "string", + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]" + } + } + } + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "deployStandardSessionHostTemplate", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "Location": { + "value": "[parameters('Location')]" + }, + "Name": { + "value": "[format('{0}-Spec', parameters('HostPoolName'))]" + } + }, + "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": "10208603161915711494" + } + }, + "parameters": { + "Name": { + "type": "string" + }, + "Location": { + "type": "string", + "defaultValue": "[resourceGroup().location]" + } + }, + "variables": { + "$fxv#0": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.33.4.45862", + "templateHash": "13792602582245425536" + } + }, + "parameters": { + "Location": { + "type": "string", + "defaultValue": "[[resourceGroup().location]" + }, + "AvailabilityZones": { + "type": "array", + "defaultValue": [] + }, + "VMNames": { + "type": "array" + }, + "VMNamePrefixLength": { + "type": "int" + }, + "VMSize": { + "type": "string" + }, + "SubnetID": { + "type": "string" + }, + "AdminUsername": { + "type": "string" + }, + "AcceleratedNetworking": { + "type": "bool" + }, + "DiskType": { + "type": "string" + }, + "DiskSizeGB": { + "type": "int", + "defaultValue": 128, + "metadata": { + "description": "OS disk size in GB" + } + }, + "DnsServers": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "List of DNS server IP addresses to set on the NIC" + } + }, + "Tags": { + "type": "object", + "defaultValue": {} + }, + "ImageReference": { + "type": "object" + }, + "SecurityProfile": { + "type": "object", + "defaultValue": {} + }, + "HostPoolName": { + "type": "string" + }, + "HostPoolToken": { + "type": "securestring" + }, + "WVDArtifactsURL": { + "type": "string", + "defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" + }, + "DomainJoinObject": { + "type": "object", + "defaultValue": {} + }, + "DomainJoinPassword": { + "type": "securestring", + "defaultValue": "" + } + }, + "resources": [ + { + "[string('copy')]": { + "name": "deploySessionHosts", + "count": "[[length(parameters('VMNames'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "AcceleratedNetworking": { + "value": "[[parameters('AcceleratedNetworking')]" + }, + "AdminUsername": { + "value": "[[parameters('AdminUsername')]" + }, + "HostPoolName": { + "value": "[[parameters('HostPoolName')]" + }, + "HostPoolToken": { + "value": "[[parameters('HostPoolToken')]" + }, + "ImageReference": { + "value": "[[parameters('ImageReference')]" + }, + "SecurityProfile": { + "value": "[[parameters('SecurityProfile')]" + }, + "SubnetID": { + "value": "[[parameters('SubnetID')]" + }, + "VMName": { + "value": "[[parameters('VMNames')[copyIndex()]]" + }, + "VMNamePrefixLength": { + "value": "[[parameters('VMNamePrefixLength')]" + }, + "VMSize": { + "value": "[[parameters('VMSize')]" + }, + "DiskType": { + "value": "[[parameters('DiskType')]" + }, + "DiskSizeGB": { + "value": "[[parameters('DiskSizeGB')]" + }, + "DnsServers": { + "value": "[[parameters('DnsServers')]" + }, + "WVDArtifactsURL": { + "value": "[[parameters('WVDArtifactsURL')]" + }, + "DomainJoinObject": { + "value": "[[parameters('DomainJoinObject')]" + }, + "DomainJoinPassword": { + "value": "[[parameters('DomainJoinPassword')]" + }, + "Location": { + "value": "[[parameters('Location')]" + }, + "AvailabilityZones": { + "value": "[[parameters('AvailabilityZones')]" + }, + "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.32.4.45862", + "templateHash": "9402242463429439832" + } + }, + "parameters": { + "VMName": { + "type": "string" + }, + "VMNamePrefixLength": { + "type": "int" + }, + "VMSize": { + "type": "string" + }, + "DiskType": { + "type": "string" + }, + "DiskSizeGB": { + "type": "int", + "defaultValue": 128, + "metadata": { + "description": "OS disk size in GB" + } + }, + "DnsServers": { + "type": "array", + "metadata": { + "description": "List of DNS server IP addresses to set on the NIC" + } + }, + "Location": { + "type": "string", + "defaultValue": "[[resourceGroup().location]" + }, + "AvailabilityZones": { + "type": "array", + "defaultValue": [] + }, + "SubnetID": { + "type": "string" + }, + "AdminUsername": { + "type": "string" + }, + "AdminPassword": { + "type": "securestring", + "defaultValue": "[[newGuid()]" + }, + "AcceleratedNetworking": { + "type": "bool" + }, + "Tags": { + "type": "object", + "defaultValue": {} + }, + "ImageReference": { + "type": "object" + }, + "SecurityProfile": { + "type": "object" + }, + "HostPoolName": { + "type": "string" + }, + "HostPoolToken": { + "type": "securestring" + }, + "WVDArtifactsURL": { + "type": "string" + }, + "DomainJoinObject": { + "type": "object", + "defaultValue": {} + }, + "DomainJoinPassword": { + "type": "securestring", + "defaultValue": "" + } + }, + "variables": { + "varRequireNvidiaGPU": "[[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", + "varVMNumber": "[[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", + "varAvailabilityZone": "[[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" + }, + "resources": [ + { + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'deployIntegrityMonitoring')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Azure.Security.WindowsAttestation", + "type": "GuestAttestation", + "typeHandlerVersion": "1.0", + "autoUpgradeMinorVersion": true, + "settings": { + "AttestationConfig": { + "MaaSettings": { + "maaEndpoint": "", + "maaTenantName": "Guest Attestation" + }, + "AscSettings": { + "ascReportingEndpoint": "", + "ascReportingFrequency": "" + }, + "useCustomToken": "false", + "disableAlerts": "false" + } + } + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[variables('varRequireNvidiaGPU')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.HpcCompute", + "type": "NvidiaGpuDriverWindows", + "typeHandlerVersion": "1.6", + "autoUpgradeMinorVersion": true + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployIntegrityMonitoring')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[not(equals(parameters('HostPoolName'), ''))]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.PowerShell", + "type": "DSC", + "typeHandlerVersion": "2.77", + "autoUpgradeMinorVersion": true, + "settings": { + "modulesUrl": "[[parameters('WVDArtifactsURL')]", + "configurationFunction": "Configuration.ps1\\AddSessionHost", + "properties": { + "hostPoolName": "[[parameters('HostPoolName')]", + "registrationInfoToken": "[[parameters('HostPoolToken')]", + "aadJoin": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", + "useAgentDownloadEndpoint": true, + "mdmId": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" + } + } + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Azure.ActiveDirectory", + "type": "AADLoginForWindows", + "typeHandlerVersion": "2.0", + "autoUpgradeMinorVersion": true, + "settings": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", + "type": "Microsoft.Compute/virtualMachines/extensions", + "apiVersion": "2023-09-01", + "name": "[[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", + "location": "[[parameters('Location')]", + "properties": { + "publisher": "Microsoft.Compute", + "type": "JSonADDomainExtension", + "typeHandlerVersion": "1.3", + "autoUpgradeMinorVersion": true, + "settings": { + "Name": "[[parameters('DomainJoinObject').DomainName]", + "OUPath": "[[parameters('DomainJoinObject').ADOUPath]", + "User": "[[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", + "Restart": "true", + "Options": 3 + }, + "protectedSettings": { + "Password": "[[parameters('DomainJoinPassword')]" + } + }, + "dependsOn": [ + "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", + "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" + ] + }, + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2023-09-01", + "name": "[[format('{0}-vNIC', parameters('VMName'))]", + "location": "[[parameters('Location')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "subnet": { + "id": "[[parameters('SubnetID')]" + } + } + } + ], + "dnsSettings": { + "dnsServers": "[[parameters('DnsServers')]" + }, + "enableAcceleratedNetworking": "[[parameters('AcceleratedNetworking')]" + }, + "tags": "[[parameters('Tags')]" + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2023-09-01", + "name": "[[parameters('VMName')]", + "location": "[[parameters('Location')]", + "zones": "[[variables('varAvailabilityZone')]", + "identity": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", + "properties": { + "osProfile": { + "computerName": "[[parameters('VMName')]", + "adminUsername": "[[parameters('AdminUsername')]", + "adminPassword": "[[parameters('AdminPassword')]" + }, + "hardwareProfile": { + "vmSize": "[[parameters('VMSize')]" + }, + "additionalCapabilities": { + "hibernationEnabled": true + }, + "storageProfile": { + "osDisk": { + "name": "[[format('{0}-OSDisk', parameters('VMName'))]", + "createOption": "FromImage", + "deleteOption": "Delete", + "diskSizeGB": "[[parameters('DiskSizeGB')]", + "managedDisk": { + "storageAccountType": "[[parameters('DiskType')]" + } + }, + "ImageReference": "[[parameters('ImageReference')]" + }, + "securityProfile": "[[parameters('SecurityProfile')]", + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": true + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", + "properties": { + "deleteOption": "Delete" + } + } + ] + }, + "licenseType": "Windows_Client" + }, + "tags": "[[parameters('Tags')]", + "dependsOn": [ + "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" + ] + } + ] + } + } + } + ] + } + }, + "resources": [ + { + "type": "Microsoft.Resources/templateSpecs/versions", + "apiVersion": "2022-02-01", + "name": "[format('{0}/{1}', parameters('Name'), 'deploymentTemplateSpecVersion')]", + "location": "[parameters('Location')]", + "properties": { + "mainTemplate": "[variables('$fxv#0')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/templateSpecs', parameters('Name'))]" + ] + }, + { + "type": "Microsoft.Resources/templateSpecs", + "apiVersion": "2022-02-01", + "name": "[parameters('Name')]", + "location": "[parameters('Location')]", + "properties": { + "description": "Template Spec for deploying VMs through the AVD Replacement Plan", + "displayName": "AVD Replacement Plan Session Host Template" + } + } + ], + "outputs": { + "TemplateSpecResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.Resources/templateSpecs', parameters('Name'))]" + } + } + } + } + }, + { + "condition": "[not(parameters('UseUserAssignedManagedIdentity'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('RBAC-vdiVMContributor-{0}', parameters('TimeStamp'))]", + "subscriptionId": "[subscription().subscriptionId]", + "location": "[resourceGroup().location]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "PrinicpalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployFunctionApp'), '2022-09-01').outputs.functionAppPrincipalId.value]" + }, + "RoleDefinitionId": { + "value": "a959dbd1-f747-45e3-8ba6-dd80f235f97c" + }, + "Scope": { + "value": "[subscription().id]" + } + }, + "template": { + "$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": "852792330190262115" + } + }, + "parameters": { + "PrinicpalId": { + "type": "string" + }, + "RoleDefinitionId": { + "type": "string" + }, + "Scope": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('PrinicpalId'), parameters('RoleDefinitionId'), parameters('Scope'))]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('RoleDefinitionId'))]", + "principalId": "[parameters('PrinicpalId')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'deployFunctionApp')]" + ] + }, + { + "condition": "[not(parameters('UseUserAssignedManagedIdentity'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('RBAC-TemplateSpecReader-{0}', parameters('TimeStamp'))]", + "subscriptionId": "[subscription().subscriptionId]", + "location": "[resourceGroup().location]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "PrinicpalId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployFunctionApp'), '2022-09-01').outputs.functionAppPrincipalId.value]" + }, + "RoleDefinitionId": { + "value": "392ae280-861d-42bd-9ea5-08ee6d83b80e" + }, + "Scope": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate'), '2022-09-01').outputs.TemplateSpecResourceId.value]" + } + }, + "template": { + "$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": "852792330190262115" + } + }, + "parameters": { + "PrinicpalId": { + "type": "string" + }, + "RoleDefinitionId": { + "type": "string" + }, + "Scope": { + "type": "string" + } + }, + "resources": [ + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('PrinicpalId'), parameters('RoleDefinitionId'), parameters('Scope'))]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('RoleDefinitionId'))]", + "principalId": "[parameters('PrinicpalId')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', 'deployFunctionApp')]", + "[resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate')]" + ] + } + ] +} \ No newline at end of file From 7aeabbf40e796f6ef4be17451d0a0ce949a0b56f Mon Sep 17 00:00:00 2001 From: Stefano Zeglio Date: Mon, 23 Mar 2026 13:03:02 +0100 Subject: [PATCH 74/83] Update README for no-decommission behavior --- README.md | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ecfe0b0..a9068c8 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This tool automates the deployment and replacement of session hosts in an Azure Virtual Desktop host pool. The best practice for AVD recommends replacing the session hosts instead of maintaining them, -the AVD Session Host Replacer helps you manage the task of replacing old session hosts with new ones automatically. +the AVD Session Host Replacer helps you manage the task of deploying refreshed session hosts automatically. ## Getting started @@ -23,23 +23,20 @@ Detailed instructions on the required permissions and how to assign them are ava ## How it works? -There are two criteria for replacing a session host, -1. **Image Version:** Is there a new image version available? If so, we create a new session host with the new image version. This can be from Marketplace or Gallery Image Definition. -2. **Session Host VM Age:** If the session host is older than a certain age, default is 45 days, we create a new session host and drain the old one. +There are two criteria for deploying refreshed session hosts, +1. **Image Version:** Is there a new image version available? If so, we create a new session host with the new image version. This can be from Marketplace or Gallery Image Definition. +2. **Session Host VM Age:** If a managed session host is older than a certain age (default is 45 days), we create a new session host. The core of an AVD Session Host Replacer is an Azure Function App built using PowerShell, the function is triggered every hour to check each session host against the above criteria. To deploy new session hosts, the function uses an ARM Template that is stored as a Template Spec at deployment time. -When deleting an old session host, the function will check if it has existing sessions and, +Session host decommissioning is disabled. The function does not drain, remove, or delete existing session hosts. -1. Place the session host drain mode. -2. Send a notification to all sessions. -3. Add a tag to the session host with a timestamp -4. Delete the session host once there are no sessions or the grace period has passed. - - Delete VM - - Remove from Host Pool - - (If Entra Joined) Delete device from Entra ID +Only managed session hosts are counted toward `_TargetSessionHostCount`. +The managed baseline is controlled by `_ManagedSessionHostMinSuffix` (default `1025`). +Session hosts with a numeric suffix lower than this value are ignored when calculating how many new hosts to deploy. +New session hosts start at this suffix baseline and fill available gaps in the managed range. ## FAQ - **Can I use a custom Template Spec for Session Hosts deployment?** @@ -64,11 +61,19 @@ When deleting an old session host, the function will check if it has existing se - **How can I force replace a specific session host?** - On the VM(s) you want to replace, update the the tag `AutoReplaceDeployTimestamp` to any date older that 45 days. The Session Host Replacer will replace the VM on the next run. + On the VM(s) you want to prioritize for refresh logic, update the tag `AutoReplaceDeployTimestamp` to a date older than the target age. On the next run, the function can deploy additional session hosts. + + Existing hosts are not deleted automatically. Decommission/removal must be handled manually. + +- **How does target host count work with legacy host names?** + + The setting `_ManagedSessionHostMinSuffix` (default `1025`) defines the lowest numeric suffix included in target count calculations. + + Example: If `_TargetSessionHostCount` is `5` and existing suffixes are `0231`, `0678`, `0976`, `1025`, `1027`, then only `1025` and `1027` are counted, and the function deploys `3` new hosts: `1026`, `1028`, `1029`. - **What about AVD Scaling Plans?** - When the Session Host Replacer needs to delete a session host that has users logged in, it will add a tag `ScalingPlanExclusion` to the VM. The name of the tag is configurable and it should be the same as the tag used in the scaling plan. + Because decommissioning is disabled, the function does not place hosts in drain mode and does not apply scaling-plan exclusion tags for pending deletion. - **What happens if a deployment fails?** From dcb0f9639d169612be05f0bc05b09e7f7f8db33e Mon Sep 17 00:00:00 2001 From: Stefano Zeglio Date: Mon, 23 Mar 2026 13:27:03 +0100 Subject: [PATCH 75/83] Disable timerTrigger1 by default in all templates --- Build/Bicep/FunctionApps.bicep | 4 ++++ deploy/arm/DeployAVDSessionHostReplacer.json | 2 +- deploy/custom/FunctionApp_Template.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Build/Bicep/FunctionApps.bicep b/Build/Bicep/FunctionApps.bicep index 4aeb2ef..d947438 100644 --- a/Build/Bicep/FunctionApps.bicep +++ b/Build/Bicep/FunctionApps.bicep @@ -109,6 +109,10 @@ var varFunctionAppSettings = [ name: 'FUNCTIONS_WORKER_RUNTIME' value: 'powershell' } + { + name: 'AzureWebJobs.timerTrigger1.Disabled' + value: '1' + } { name: 'AzureWebJobsStorage' value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 96bdb80..a27fb1f 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -740,7 +740,7 @@ "use32BitWorkerProcess": false, "powerShellVersion": "7.2", "netFrameworkVersion": "v6.0", - "appSettings": "[union(createArray(createObject('name', 'FUNCTIONS_EXTENSION_VERSION', 'value', '~4'), createObject('name', 'FUNCTIONS_WORKER_RUNTIME', 'value', 'powershell'), createObject('name', 'AzureWebJobsStorage', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey), createObject('name', 'WEBSITE_CONTENTSHARE', 'value', toLower(parameters('FunctionAppName')))), if(parameters('EnableMonitoring'), createArray(createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey)), createArray()), parameters('ReplacementPlanSettings'))]", + "appSettings": "[union(createArray(createObject('name', 'FUNCTIONS_EXTENSION_VERSION', 'value', '~4'), createObject('name', 'FUNCTIONS_WORKER_RUNTIME', 'value', 'powershell'), createObject('name', 'AzureWebJobs.timerTrigger1.Disabled', 'value', '1'), createObject('name', 'AzureWebJobsStorage', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey), createObject('name', 'WEBSITE_CONTENTSHARE', 'value', toLower(parameters('FunctionAppName')))), if(parameters('EnableMonitoring'), createArray(createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey)), createArray()), parameters('ReplacementPlanSettings'))]", "ftpsState": "Disabled", "cors": { "allowedOrigins": [ diff --git a/deploy/custom/FunctionApp_Template.json b/deploy/custom/FunctionApp_Template.json index 8370a34..1ffd30f 100644 --- a/deploy/custom/FunctionApp_Template.json +++ b/deploy/custom/FunctionApp_Template.json @@ -729,7 +729,7 @@ "use32BitWorkerProcess": false, "powerShellVersion": "7.4", "netFrameworkVersion": "v6.0", - "appSettings": "[union(createArray(createObject('name', 'FUNCTIONS_EXTENSION_VERSION', 'value', '~4'), createObject('name', 'FUNCTIONS_WORKER_RUNTIME', 'value', 'powershell'), createObject('name', 'AzureWebJobsStorage', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey), createObject('name', 'WEBSITE_CONTENTSHARE', 'value', toLower(parameters('FunctionAppName')))), if(parameters('EnableMonitoring'), createArray(createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey)), createArray()), parameters('ReplacementPlanSettings'))]", + "appSettings": "[union(createArray(createObject('name', 'FUNCTIONS_EXTENSION_VERSION', 'value', '~4'), createObject('name', 'FUNCTIONS_WORKER_RUNTIME', 'value', 'powershell'), createObject('name', 'AzureWebJobs.timerTrigger1.Disabled', 'value', '1'), createObject('name', 'AzureWebJobsStorage', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey), createObject('name', 'WEBSITE_CONTENTSHARE', 'value', toLower(parameters('FunctionAppName')))), if(parameters('EnableMonitoring'), createArray(createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey)), createArray()), parameters('ReplacementPlanSettings'))]", "ftpsState": "Disabled", "cors": { "allowedOrigins": [ From e64b10fa17f07b33197ee2ab5c369212a6afb22a Mon Sep 17 00:00:00 2001 From: Stefano Zeglio Date: Mon, 23 Mar 2026 14:28:52 +0100 Subject: [PATCH 76/83] Bind FunctionApp zip URL to deployment branch --- Build/Bicep/FunctionApps.bicep | 6 ++++-- deploy/arm/DeployAVDSessionHostReplacer.json | 12 +++++++++++- deploy/custom/FunctionApp_Template.json | 12 +++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Build/Bicep/FunctionApps.bicep b/Build/Bicep/FunctionApps.bicep index d947438..8752ecc 100644 --- a/Build/Bicep/FunctionApps.bicep +++ b/Build/Bicep/FunctionApps.bicep @@ -34,8 +34,10 @@ param SessionHostResourceGroupName string = '' @description('Required: Yes | Name of the Azure Virtual Desktop Host Pool.') param HostPoolName string -@description('Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code.') -param FunctionAppZipUrl string = 'https://github.com/WillyMoselhy/AVDReplacementPlans/releases/download/v0.1.5/FunctionApp.zip' // TODO - Update this to the new URL under Azure Org +@description('Required: No | URL of the FunctionApp.zip package. By default, this is derived from the current template URL so it follows the deployed repo branch automatically.') +param FunctionAppZipUrl string = contains(deployment().properties, 'templateLink') && !empty(deployment().properties.templateLink.uri) + ? uri(replace(split(deployment().properties.templateLink.uri, '?')[0], 'DeployAVDSessionHostReplacer.json', ''), '../../FunctionApp/FunctionApp.zip') + : 'https://github.com/WillyMoselhy/AVDReplacementPlans/releases/download/v0.1.5/FunctionApp.zip' @description('Required: No | If true, will apply tags for Include In Auto Replace and Deployment Timestamp to existing session hosts. This will not enable automatic deletion of existing session hosts. | Default: True.') param FixSessionHostTags bool = true diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index a27fb1f..2140e03 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -333,6 +333,13 @@ "description": "Required: No | Leave this empty to deploy to same resource group as the host pool." } }, + "FunctionAppUrl": { + "type": "string", + "defaultValue": "[if(and(contains(deployment().properties, 'templateLink'), not(empty(deployment().properties.templateLink.uri))), uri(replace(split(deployment().properties.templateLink.uri, '?')[0], 'DeployAVDSessionHostReplacer.json', ''), '../../FunctionApp/FunctionApp.zip'), 'https://github.com/WillyMoselhy/AVDReplacementPlans/releases/download/v0.1.5/FunctionApp.zip')]", + "metadata": { + "description": "Required: No | URL of the FunctionApp.zip package. By default, this is derived from the current template URL so it follows the deployed repo branch automatically." + } + }, "TimeStamp": { "type": "string", "defaultValue": "[utcNow()]" @@ -444,6 +451,9 @@ "FunctionAppName": { "value": "[variables('varFunctionAppName')]" }, + "FunctionAppUrl": { + "value": "[parameters('FunctionAppUrl')]" + }, "EnableMonitoring": { "value": "[parameters('EnableMonitoring')]" }, @@ -622,7 +632,7 @@ }, "FunctionAppUrl": { "type": "string", - "defaultValue": "https://github.com/stefze/AVDSessionHostReplacer/blob/main/FunctionApp/FunctionApp.zip", + "defaultValue": "https://github.com/WillyMoselhy/AVDReplacementPlans/releases/download/v0.1.5/FunctionApp.zip", "metadata": { "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." } diff --git a/deploy/custom/FunctionApp_Template.json b/deploy/custom/FunctionApp_Template.json index 1ffd30f..4660da7 100644 --- a/deploy/custom/FunctionApp_Template.json +++ b/deploy/custom/FunctionApp_Template.json @@ -333,6 +333,13 @@ "description": "Required: No | Leave this empty to deploy to same resource group as the host pool." } }, + "FunctionAppUrl": { + "type": "string", + "defaultValue": "[if(and(contains(deployment().properties, 'templateLink'), not(empty(deployment().properties.templateLink.uri))), uri(replace(split(deployment().properties.templateLink.uri, '?')[0], 'FunctionApp_Template.json', ''), '../../FunctionApp/FunctionApp.zip'), 'https://github.com/WillyMoselhy/AVDReplacementPlans/releases/download/v0.1.5/FunctionApp.zip')]", + "metadata": { + "description": "Required: No | URL of the FunctionApp.zip package. By default, this is derived from the current template URL so it follows the deployed repo branch automatically." + } + }, "TimeStamp": { "type": "string", "defaultValue": "[utcNow()]" @@ -444,6 +451,9 @@ "FunctionAppName": { "value": "[variables('varFunctionAppName')]" }, + "FunctionAppUrl": { + "value": "[parameters('FunctionAppUrl')]" + }, "EnableMonitoring": { "value": "[parameters('EnableMonitoring')]" }, @@ -622,7 +632,7 @@ }, "FunctionAppUrl": { "type": "string", - "defaultValue": "https://raw.githubusercontent.com/stefze/AVDSessionHostReplacer/migration/FunctionApp/FunctionApp.zip", + "defaultValue": "https://github.com/WillyMoselhy/AVDReplacementPlans/releases/download/v0.1.5/FunctionApp.zip", "metadata": { "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." } From 0c0a57cef8babeb3cffbe02ac88e634159fac8da Mon Sep 17 00:00:00 2001 From: Stefano Zeglio Date: Mon, 23 Mar 2026 14:38:59 +0100 Subject: [PATCH 77/83] Use onedeploy for FunctionApp package deployment --- deploy/arm/DeployAVDSessionHostReplacer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 2140e03..1440c32 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -677,7 +677,7 @@ { "type": "Microsoft.Web/sites/extensions", "apiVersion": "2023-01-01", - "name": "[format('{0}/{1}', parameters('FunctionAppName'), 'MSDeploy')]", + "name": "[format('{0}/{1}', parameters('FunctionAppName'), 'onedeploy')]", "properties": { "packageUri": "[parameters('FunctionAppUrl')]" }, From 5463656f12e54fec939c26fb2083d0f9ce08d66d Mon Sep 17 00:00:00 2001 From: Stefano Zeglio Date: Mon, 23 Mar 2026 16:17:30 +0100 Subject: [PATCH 78/83] Set OneDeploy artifact type to zip --- deploy/arm/DeployAVDSessionHostReplacer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 1440c32..5b58831 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -679,7 +679,8 @@ "apiVersion": "2023-01-01", "name": "[format('{0}/{1}', parameters('FunctionAppName'), 'onedeploy')]", "properties": { - "packageUri": "[parameters('FunctionAppUrl')]" + "packageUri": "[parameters('FunctionAppUrl')]", + "type": "zip" }, "dependsOn": [ "[resourceId('Microsoft.Web/sites', parameters('FunctionAppName'))]" From 8dd17c60cba0434de5d980c567803f471afde65e Mon Sep 17 00:00:00 2001 From: Stefano Zeglio Date: Mon, 23 Mar 2026 16:25:33 +0100 Subject: [PATCH 79/83] Update Function deployment templates for PS 7.4 and OneDeploy zip --- deploy/arm/DeployAVDSessionHostReplacer.json | 6 +++--- deploy/bicep/modules/deployFunctionApp.bicep | 2 +- deploy/custom/FunctionApp_Template.json | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 5b58831..c0cddcd 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -335,7 +335,7 @@ }, "FunctionAppUrl": { "type": "string", - "defaultValue": "[if(and(contains(deployment().properties, 'templateLink'), not(empty(deployment().properties.templateLink.uri))), uri(replace(split(deployment().properties.templateLink.uri, '?')[0], 'DeployAVDSessionHostReplacer.json', ''), '../../FunctionApp/FunctionApp.zip'), 'https://github.com/WillyMoselhy/AVDReplacementPlans/releases/download/v0.1.5/FunctionApp.zip')]", + "defaultValue": "[if(and(contains(deployment().properties, 'templateLink'), not(empty(deployment().properties.templateLink.uri))), uri(replace(split(deployment().properties.templateLink.uri, '?')[0], 'DeployAVDSessionHostReplacer.json', ''), '../../FunctionApp/FunctionApp.zip'), 'https://github.com/stefze/AVDSessionHostReplacer/tree/migration/FunctionApp/FunctionApp.zip')]", "metadata": { "description": "Required: No | URL of the FunctionApp.zip package. By default, this is derived from the current template URL so it follows the deployed repo branch automatically." } @@ -632,7 +632,7 @@ }, "FunctionAppUrl": { "type": "string", - "defaultValue": "https://github.com/WillyMoselhy/AVDReplacementPlans/releases/download/v0.1.5/FunctionApp.zip", + "defaultValue": "https://github.com/stefze/AVDSessionHostReplacer/tree/migration/FunctionApp/FunctionApp.zip", "metadata": { "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." } @@ -749,7 +749,7 @@ "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('varAppServicePlanName'))]", "siteConfig": { "use32BitWorkerProcess": false, - "powerShellVersion": "7.2", + "powerShellVersion": "7.4", "netFrameworkVersion": "v6.0", "appSettings": "[union(createArray(createObject('name', 'FUNCTIONS_EXTENSION_VERSION', 'value', '~4'), createObject('name', 'FUNCTIONS_WORKER_RUNTIME', 'value', 'powershell'), createObject('name', 'AzureWebJobs.timerTrigger1.Disabled', 'value', '1'), createObject('name', 'AzureWebJobsStorage', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey), createObject('name', 'WEBSITE_CONTENTSHARE', 'value', toLower(parameters('FunctionAppName')))), if(parameters('EnableMonitoring'), createArray(createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey)), createArray()), parameters('ReplacementPlanSettings'))]", "ftpsState": "Disabled", diff --git a/deploy/bicep/modules/deployFunctionApp.bicep b/deploy/bicep/modules/deployFunctionApp.bicep index 5937a80..dfcb5ad 100644 --- a/deploy/bicep/modules/deployFunctionApp.bicep +++ b/deploy/bicep/modules/deployFunctionApp.bicep @@ -173,7 +173,7 @@ resource functionApp 'Microsoft.Web/sites@2023-01-01' = { serverFarmId: appServicePlan.id siteConfig: { use32BitWorkerProcess: false - powerShellVersion: '7.2' + powerShellVersion: '7.4' netFrameworkVersion: 'v6.0' appSettings: varFunctionAppSettingsAndReplacementPlanSettings ftpsState: 'Disabled' diff --git a/deploy/custom/FunctionApp_Template.json b/deploy/custom/FunctionApp_Template.json index 4660da7..812626f 100644 --- a/deploy/custom/FunctionApp_Template.json +++ b/deploy/custom/FunctionApp_Template.json @@ -757,12 +757,13 @@ { "type": "Microsoft.Web/sites/extensions", "apiVersion": "2023-01-01", - "name": "[concat(parameters('FunctionAppName'), '/ZipDeploy')]", + "name": "[concat(parameters('FunctionAppName'), '/onedeploy')]", "dependsOn": [ "[resourceId('Microsoft.Web/sites', parameters('FunctionAppName'))]" ], "properties": { - "packageUri": "[parameters('FunctionAppUrl')]" + "packageUri": "[parameters('FunctionAppUrl')]", + "type": "zip" } } ], From b2a0dea71983b1c0ba6d57b28b6a1ebbe07e93c9 Mon Sep 17 00:00:00 2001 From: Stefano Zeglio Date: Mon, 23 Mar 2026 16:33:38 +0100 Subject: [PATCH 80/83] Update DeployAVDSessionHostReplacer ARM template --- deploy/arm/DeployAVDSessionHostReplacer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index c0cddcd..0440af3 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -335,7 +335,7 @@ }, "FunctionAppUrl": { "type": "string", - "defaultValue": "[if(and(contains(deployment().properties, 'templateLink'), not(empty(deployment().properties.templateLink.uri))), uri(replace(split(deployment().properties.templateLink.uri, '?')[0], 'DeployAVDSessionHostReplacer.json', ''), '../../FunctionApp/FunctionApp.zip'), 'https://github.com/stefze/AVDSessionHostReplacer/tree/migration/FunctionApp/FunctionApp.zip')]", + "defaultValue": "[if(and(contains(deployment().properties, 'templateLink'), not(empty(deployment().properties.templateLink.uri))), uri(replace(split(deployment().properties.templateLink.uri, '?')[0], 'DeployAVDSessionHostReplacer.json', ''), '../../FunctionApp/FunctionApp.zip'), 'https://raw.githubusercontent.com/stefze/AVDSessionHostReplacer/migration/FunctionApp/FunctionApp.zip')]", "metadata": { "description": "Required: No | URL of the FunctionApp.zip package. By default, this is derived from the current template URL so it follows the deployed repo branch automatically." } @@ -632,7 +632,7 @@ }, "FunctionAppUrl": { "type": "string", - "defaultValue": "https://github.com/stefze/AVDSessionHostReplacer/tree/migration/FunctionApp/FunctionApp.zip", + "defaultValue": "https://raw.githubusercontent.com/stefze/AVDSessionHostReplacer/migration/FunctionApp/FunctionApp.zip", "metadata": { "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." } From 611ead00dfe32dc5bab799a8bd4b3ff5fd988fff Mon Sep 17 00:00:00 2001 From: Stefano Zeglio Date: Mon, 23 Mar 2026 17:15:44 +0100 Subject: [PATCH 81/83] Update deployment artifacts and package outputs --- FunctionApp/Function.zip | Bin 50597 -> 0 bytes FunctionApp/FunctionApp.zip | Bin 49223 -> 0 bytes deploy/arm/DeployAVDSessionHostReplacer.json | 4 +- deploy/custom/FunctionApp_Template.json | 1508 ------------------ deploy/zip/FunctionAp..zip | Bin 0 -> 43383 bytes 5 files changed, 2 insertions(+), 1510 deletions(-) delete mode 100644 FunctionApp/Function.zip delete mode 100644 FunctionApp/FunctionApp.zip delete mode 100644 deploy/custom/FunctionApp_Template.json create mode 100644 deploy/zip/FunctionAp..zip diff --git a/FunctionApp/Function.zip b/FunctionApp/Function.zip deleted file mode 100644 index 335593ee28dba212f61bf35ac8918de5ca8dc10d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50597 zcmb@u1#o1^u{AtmX2y{;VrG^`%*@Qp%*@OhF^-s-nVF>#!-$!ge$D#X-siXWzqY@Q zxP7~$BPzSIPUSh7mDMuhK)}eKemzJ#h%|nC@jp)xpYT2jIa=vEm|9!WIJ!W60s#K? z$!|}J3NW9*PQGfG!tiOBZhzcmej5h-6Zrob2JiR7{B=ATT{~S%LkB}Udukhd1G;}T z`ZAlB@w|$rH4+=L=f1KAeqLKZh~UX+4?#8=kqDI{Qp*XFosku$Imunw*Hq9x?+tfL z5(ZJ?tSU`rzT9n>6Z@!hu6y>%4(?A+9=IWClU#`?+=H2Jrx}^xB=vp+ljj#l$0%>7 z`Q7QbxRC<72${V)N(5YuWejp_bv{8YtcV>poGo=PDBl8ys_N^gr5jUX4Hr|jOuHK) zwUV@^YkkwR9rfgx`8ja4VCF}p%G)K_1sGFrCd#eSS;|@X~0a@@1?~ z^Qxo@Y=4E+n!S4dT+z(y?D_Tt+Nx*7^7(}OBF~P|9Av##yOCxY*g6wfpe)%pbVl7q zA2|HmW4DUBu`at%e=h?(C@&ftrW-OOx9?RvKGz|_js&!|X#w`7K`I#)DGX#G9?4Ho z4%+Pe>wuIF!l@S}u%`oTQ}N2_gK|<%zu605JJbFukz^s#be2WcBFvucu>co3x&8Ax z7OZ+MvH8MDSY=0m^YXT1VT_H~Fyzpc2~V`PoP~bOHsOgFx!`X1QWy#utD#hW!Tg?L4N}^;^Bq_#S|qayN(3M@szg^Sc2VEV6q3jg)tq! zgC5*~E?EsI^a;|>`w%l8xOrU0V?NvX_eR`~gfLqST8?-&B993olq7704Kq>j?z5wP* zepH-pex40qmf8|H+v&z&S@hAdZ-uoepsF-vRH!o-;BY4k`MjygVlXq<4@=n7_AY7g zuPhfzUsA>45>A-qp;1C0Lz{Bi*(uuXsopQZd@L$!sp~DV+6icN;B>yWK*M zwQ?&hc69}Sazvz(gQX3m#Vb^ma#L|jO{{s~K#h^ueZyXPI5H52qXQUw$olocLlu=? z3mLfW+`Gw#R6|4yGNqD6wP8a&^Z8F&=+~jGtUV|zoB4w?$BR>g?!bAdZ6YsUz-viHXrtvFn0xPz z{ej_gCdaHtyF?9yg2Hs=Ci#>s)npOfTFR76nfbAJdeb|$MlQyL;6@9@1UqTbZ?d@x zVi__EWQ_~}sfo*@V~qya0wozgPf^`)=~a@~N`4)zkxD z3jRzRh2+FW&Lqnk*~HqmN6oQj{^#ipOKmBf#|h0F9>`PxNXlCy*!w3LaWL?!V(2zd z&`+OwFh6~~|3zKDIEU>Ys0;D;oMU2b??7#4Z*BEQECb7kz@S@P`cMoR^Qc>N@;d5u_^R?y@^Se-d73s3_ zr8uYs4_u`+vo#i1yaJTq)?-_6x^i#&C+u^Ys#do56ETr@GuPGS&0GTwQ|Y?ype`al%)_ zS7Gu3_~pAV;9Li_%+-= zstBS#8cyEO-u^>Xh-mX3+ZXOnMrxEC)L#W4D?|3UMhb`Lvr0kX$(h-K4uSs-x_!z#6;dA_()_so?yrCR zF%*CH#~4~sIsVy+`6I7M{(FXW^{gEowEtk@s7(wlY^WVv9RB}2YB|OW*+UEesmuN< z%rnHr)E`DnKF-f2t^|s#&tllB*!y}j{>85yk_&F^{`FAPn4&xgYupZIh*Qogq<=dh zhy%6U!6f}WhdJk};kewqp1x9Xa17oQ06!Unm!L=J6B3mQtDe|J-e9^7m2?HyOEgCs zMmUe4DlL=%Wz&x*6KdoA z{vKaA5p<9}Jn(|%_sE~+ERdq{RPvDmLKfm#3B`X_j7dQAa;5|dUqM#*ZnWKRdT(R_ z7IKqb?PUh)a`8*@ogizHGsOGaB@-pv3e+|MCPBAxEzJ8W7H=cg=Ra#J$Xih5g%BPq zEAg>p#zVVIp;y!?SrV<5W5{z9t$bY@fNOI(*_>pQI$1jg`{fM`AeO0L-bnwOH$Gnc zpQTLmXT4$cKUu>c!}HHAYLS26n7~IUv38}B7m@oNOh59AKTI!vKNsyPs@6Y6;Jqd* zNeu}Au&@n{){O!gDdYX;iFthua0!qdPe3{8)|8w%_V+ri6`#A%U=8waf$&dTqAN>M z1bdh|kT3j$eLb883X}9Jm@?DFEH@p&E}AhY+kMhHJ+>b8*3TqPO^1-x`BuCd6~3@< z29+lCWSV`CIW%GqZ2wN8jrBrOxb<{`?aFww>E1fKR*d8{5HJ{_Fey@Jtk8EoqN&CXFo+L} zSi=jK>bwb)E;>~gsK4H0g%zi9FAnNj%0ZfDG%s)OOLxYFAv$esgPNJh*W`f&uI;Js zm-`gE);Y~%Es;b>%%34yLO8jRUx$W>YE7Di*yJ`AV!roK-So4iK7hLiQS}mQ63xJ1 ztJAjAo=|W0lUDN8@oL!jy#sc5Z;nXhG3a2nFKC!-Jixs$&d(k86b|=Chg;LejrgcO zU{<*j-e?o_hr1D=3_6pwlWzLpfr~p$cC&0gr3lbY;(uM_3>}6qp5sSaXS#GKi+Z>& zyn=BS+TLb>hEKD%dE0{H{KSEqabt5I043FdPXPV~F0^hBzyM5KyIF;|4Bw5S1-C)V z9OK8P!WjHx(zP?r3Zw{3J*b(%5Jv~AF2tB_iCw*VBQWH$qMwFjBYWNA*q$^f!Tt=^% zPBbT)uz828JHM6Xiem@!suW z+}&A87Tt)VKN<6;79Y{o9*X$<+<4y_B>1P-^VbONV#}c&FPi!ii`|KlJD5_7Y}Qf* z)igZ?WvxWDVH8EVH{~4V;#ZWb0n74Zp2q@&^MautrKsldZAu;G>heXL?Rxvxgrfwq zkyTm{Ww0m4-$l{`TA12Z8A=NeLEDQXW@IvmjSMe9q_njqK{v6*1Ie?AX7f&6m)sIR zZO}(n2WVB-5-P!O$v<`S@SN*{&G=24oiaPbzXx?L3BUcEEAIS zc||S>^~^ozB;C-jeSAxYLtZ{tmehH&yR+Y5$8`2%CNW1tVLp9}o9l;uZ!hpy5vhhJwlo)=lhfda1<@TvPz6!N@Y= z8BItyq)8t%kz_h-4O~vUvhbKQ2Z>LG$(7j^F>^p^F`P~1_A`ikh2zzVf$@7HQ{(3c zp#;*tnvo7}V|+Q2j%x8%F3)hkC`JeNky{7k)2HE&V$Qz-g@B>H=`U^h&*J_s_W7&Et5>zOUSWoRJJYrm0i09&g8y1Y)6dT6m`co_hF+bf zM;hkWV4@x<;D(4YIrOsOEG$+-D0U?-3uKBN%E^7d%E2MtuK*&~1y->-J>DBBF()%} z5hm>)kooog(^v2GBklEr^Va9B1E;+A+({P%LfX;5GCx}2&y7#msCFO-Nai9gxkOYl z4uE1m{R~NebOpe5Ln~p&1xoN|F^`Z!j+vPk7EW@$%D+FchrGm;I{RJ#(VcT_+{_Y% zs{+3Qu98I@8B6Djz-wxhJ+kLXYFx4=>9Aj?81@zkj+3)g%JiKxMLo5t8YNB45wm9F=hZw zmpN3B9(ChX{24rO1dB2{i+cYh4Auq6(=PsaY!!&is2jWE$2T^~rx3ps0Pw8$_?j4rqI#?NV0C+v)h9WEFJ3VtM5S;B@xATU8 zupB3v>}fel&zVNwlpo)QkcFxJLpTpY2kV^BQnyTEh{c5N<3D?dbw$Le$6cBTC`_QI zxCmA-WR+k5bb@!VL-ztlMw5I+_a!1a@5j+j%&D8M9b6z+ROU2LJ&<^a@E9-R2Y9`P z#6UU+5bHe%tK>Ie#q@c+M_#WcOztU97Jc0m$0j|475|DJ~fMNP#C9qwc(9u-q)W zCwYR%rhfl@Wvo%w2BQIieG&WK)Cd>Pq+^h0hF7kfHrUpbC{V-c!H8)@lAdvUXcS}Dr9Ye> zBn!dvW@}9>c#=p)TY^Av$CV`A(Y#?tkPOMA72~`B%ahN(#N2pk>Em?y*BTNFa>owz zh4**kS5YLd^jM^AQ&?m2m&+k*bhk>#K{jY3+9h zlGI#3sb`*jA%%0OXKRWj;VVc{Bl)p6;#XYn$y}AH z+W~P(G`Y&hR^w0C`}D20a#k*} zN~`eaVxdBu>`)?;qN|{dqrne5z-hiR6L#Xby1e!bAEh5(+8~Qcwb|&etgpi9*V^?f zL%k{GQ%PUqwdSvnGJpi}c}K15N^Cf~aS!X4BsF+T*BDe*o%|}uk`ID{p63P)!xgvW zU1?SuTiGlE4OXXS_sX;9$u+^~N){r=*@C{y7Wh&hGl)KhT*>fTD?aQW7qFFWbPMvo zw`%Vl<=tDplLk_piKpb9^{AV)BgRne^H*5EKw(o!c4C+dVj6){Tu$HXY1>tJZ_ zAZn>=Y^e0H-|{D^aZSZi`U?yETc$Q0T1?!*B1lD+8p;_dGHfVzNII)xfRd3jp9b^s z+})Mi<2jDIb`1?IzKCLJg@8(Dgz?MRcFJW`9P5-upH09rmIwD&UCc!8%qpxlp5n0~ zd#Hov)vN%7BW)M#tA~P>)|z&C;w9jgyERVpDm4E@(9kal=wRSKpQNGd;GQXi%mZSP zkq#JK!`(3H7^rn=jSzb!u0jM6Mua<6Zm-6Qcq8e5n7a>3L^8ETT|h3B@3>ajeXo~L zG8J9H>qbJL1@gf;%C|E|xo%QjzPmI?>+y0F0)&~vcc;;0QWJ>Q@1`celH#;q;*iM=hQc+ zmCp@B7SZV2(})Id;0wP16xyteQRoJ^T;G0n z;EM74n$k+*4+N9N`T@R)h#cjllf;qMAzko_3k7TiCVIL1A{?qenMc?X1#efy*`#CC zp?l%@WNfE9FblRSy$p9PtHvQk_3~Y|tepZxxr|A&vI-L+Vr<&F3^48**sF1L!m%jY zl>-hJ8bx7RAp(tZwI+?ZFJF0KyD6W$_e!Qv*%1oTOByS;IMJ92a@dpi9o_KtJI z<;m#f1;2G9OCUw|j$^fwMIBx7oXB_7{7;)!>}-z73!<@Ry(qg zU8~g2U0PEZu-`7nrP<+aA826h#5`u0l~gIgh#kcDb6g+D--YvEw4H?1X!P^LcAwnr z5b;wx$#WD`Ajv0*mQI(5#E9_mv)m=_c6)|ZBYV1ex$-?G7$NLrVofHJV34Feq4XL> z+47o_D>gD!P+pBxWiA_zAdxGL;@Snd1gnmxmOw*M$*lFw5E+$Y+PQizQ)Wq=zr4w? zQkt#T%uu%6C&=cfc2e}!M%W@lH-0@5dHwh26jC%_r!`<~Si zK(FnnJvfFKTYt^WG*N9|MtP&obknCaipXXW)ZL$VHSF%+^;+K*^Ws}^rVr=8@S%!V zs@j<6ev5et8l?0KgxK4FEN%`I-!7RAQ2cUyC3qxQ<%*irHE*V`i*W}xhsk!VP06Sf zb_$x+A9&-3G^osO?vW^e_nFhc%4{xOy}4jKL-?Y@Lz&@StAc2S31|f!7yiR$Zw!i7p+m=PMXh`(+)=NYIrBseTV`F0@;x4NG-qpj1UuwQ8!GGo1~?&o%@KwtH8F}5tQ7h}4uhWD5&0W9P4BMc@@55| zcPt1CDp%kf35JyLkmNkijPF6D!tPeSi)-w0Vq1iKdMBovMOAIpq`Q9R-;?p7h!i4& z@aW-ZE{Nbh*ydX{eHS*8=VBkJ8FHO!puCF2;OsqZ1OaJY#vb6rq4e3lRe}&is&>?2J&1l20(3xF1(U6rsV^Br*0=HRooh;w1Ab#p|V^g1&0 z#I?dbiLE$qe15L9HZ()bh&+`m6pvg3?v7<||CBU}aB&*id_GwzKCA-0Xpn;$LM+974XnU=&oe zEquR`C-Uf*^*SqN%DWUHie!7})_W)rSAoX0L2C$yN_UAV9>%+*IyjM$^JYnR^nAmk zDfeTV>s)jCgH}xSzh-d*;vh)f;Bg@{e117SYC~ zsGX==o9#9P;SH>6R^bmDq*Tz6l>7M2gqy$7&Osa-wI)sXkBL3jzAh+dgE>k94fDBu zDX=AAgdB&_-X@x@u2y`Pyo$bR<(@)(71)Y0!c-CSK0I#sgvY#L^9h!o^J!aix4l1V zvN`7v-1Pj`Cuux&_@f|3C&B!PR?u$!y^UnJ;=3^dc>^pQtig!t%F{WXY6=Sh*Hj}- zqTMO20$A`~_?CBP$*0earD7K9(%tmsQVrg}?Kr{!O~hh+Gz_Ie|Eq{4_#a2CKT+rZ zeZ6O1#nOIR2>xxNa-a{V6GLZ;rOZ3`JB)f zm;_!lKIIC;Vfj93-zkMCmIes5+dZM-d22)v|KJh`2{99kC+o8`X4JDQD`qQ@@@END zUmw*87GW~HZNPD6!9x)ISepi5IMgFbBT5I%3D?O}EtrA$Zj&Ox4Bnw!HoKn)+;DnS zvEw|{Hs#%ZK8$@3(J4Go9t|3ZvIOuR(%Ds?@}+Zwzl+k;Tf>i|Yr7RwN-0p>i1;~A zBC5tgAB^FcB);ZsAjIRvu^MUoMD_!x_9Jz*U`h|~zECdjiJXQrlHOSxECmoLSnZC@Idv7@}P!XV?pE3IXLIV+cR6$IZ8!a*G70i-K96 zYIppd0-qWgMk%iua_g2+(~zqqyj0NXwzcN@!s@|xqPkmaNhT#Q(%60ECj}8HiAEc@ zKtH0@!TWTg%NWuvoH|7XyRWU3sUY@E@=S~cc1yYkD)ouG^hXD5ayNZxa5FxzxwQf{ zWmDt{@V6PhFRY&X=)otZh?K?Tr1sAama({n#u4nj`>Rj{L_J)2mT5T^QY9STyzI~2 z`T*7&ayGnw z`U<$8+|eAO)N?JOHTBSbi1@D-vGDnWlp?yLn7CVy!AYo#6;ea>XvA~b`Fsmz(t=CU zyL(F;SxU16HY=^;Mh9P8=ZJPQtKHKXyuSSmqfmu@haU<)5O;TYC-L+psKrZSPgZ** z(&<)3uL;KIE`uwIF|boue4-33ASc-MIqfS%hba`7pr6#rnu6!@mPOPlJ{I%eyEXKZ z&exUXjRzES4mL8+ipvpC%!!MWa-#6!g|JW{CmL{|NQ|1eRU1AuYnEaVtzto#9D)Md z!!DjUiIaCoQB4q&5{hI?1Hs2VhB)-T0DdT@&CX6uSE0)A8=;xvy+^|6EYUUGP z!Z5DN3#LfNXG)Ehk``^23S{gcFpkqPmCl89mHX5%fx>knxsZApu$?a90TreX)fqm+ zQfa3#p}>ypgWe1+!+ON#?$JWGii1U%_);u0lE`i~K^A(nd3UPLXW)CPStBJX@ybb4 z^ohsmx!@s=^=?1x1OVJ z?isc1QxKyAciPXJt3U+W=gi6MOm@fu+I-}>h+sF>8bOXG_erM)K)Zk?P1LW3<*^M- z^gQET+DStMMyyERuIkjw=9lPh^vek3z7KXOA%Fd|Zth=bI!gysG#q9Pl*0j=QJv0%zhkhuXM zxn?mnlz}H7J@WXLxcAjh%6^;?j?@}AXzHV7Zg$vAgXuO9Lp_qi20^p*+YbAK*$x+N zoZ=w^tdY>qbEZs;MD;}OIqdnPr4`D}u7b-9BTa^5A~~M(K16~D%>!aTG3*(bs-1<~e67<) z?!#m`HN&~W_mxMRGzFwemd{mek`$TJ-OgICmo2AlM~qY^86BpHz?RXPAmxg+txw4@ zc={X;!>5N+z0E(w4H}rNzz#z^?d9>0BaXWw*BI#plMfiyuV4DF3rMY`&K*Lm6Mu3D zqghukS`ApG?kOyurqU4&lh(d7V$!?={*uAb4`dfTA2N6c?O&0K^#2P|`Co0Sf4GhO zdwPkKTao(M()Zk_Am*82ZV~!ShY#8uB1E9u&Y4X|kufvSuyO)x;`h?pYS}DcZjjYb zYd?|BP}oh7AxkLV#HZ)Mx>Z#Fh%F|mpCjNGAl6#~x+l>Z`uU5dQ1Rz{`ycMZr74y` zES)Q}$yUB;-*U@DF&3d1?~PXV1ghL#__Ne+hcHir8DQ#z9~w6}J&(>D|~_%pdt zhJv$LRPwner9(;-y@lmf&JIT0k&c0^s_ruI(cd@ zS`*G~Y0L%Uc)om zmR&#d11CH|0g32Ug8G?-3SwLE$u?>rL|Dox7^`ek3Bzv@tZ-1R4{f98?A8m?_DE+V zyhM;8=rAsgMD!7|XIjfaX0$?w+P*x3{4iu(Lr&TZ=|*bhb_5e}8Ava)1XvA!5ewRF z)^+^^Ogi*hOQ^xhLTiPe~g=AtwXwX1EC75h(zY3g#YBdKP zjpzXQ={VO1qkj`AudvK9oIG7iyI#k-5bwAc(~QvbgcU7lIo8egGx3NEKBxIf6ueYt z{_aQJaYLNhXfHg=WZqf2RA5+p0s6L|x6!EV0reQp6OL?D*flJT7%k-Up~(dK%phCqMT=)L#jpD^LnjA_*>c z$MzOw&?!UiP(33Yt+Xkl1+1qlA54YFs|QSMwuye34=Cj@h(#&pq?)uyTlIaNxT`H* z$z+38Q33XSMicA$ey1sd22D9vM?{<{y6{{1TVTTqCjO(@X!7Iw=Z^Ugx`E^jEv=ml z|717N?;X{y@=?)Xg??Mt=CuHb({ZeNX4h%FP3-_~GH52Yv_%i%HKquq6CN41Tdo-L z{!GMHY%p4!aX0aqUHH2QH{q?!4W)ig1e-ra%Z@pw1a&1bqonO_Tp=QE^rx)K!co$k ztLS7wx-{pOjhmw|@S1=quOD*y^*lr<8Th&gu^OCR!X~1z(;ne&YTw=*B^E-Ku!wNW&@Y~@z)gxsPVH~N8peO7(3)9DcvUw;g4WyIg!>oZWG>9Fc zmlSgtA)?%agf0c*;p507Tpu8MN|aK%1g3Q*$2~Hyi_J4?&4dz;DkkzKylbt-l)%75 zO7X>QM%(f&E_o&!kUuxIOc#BeJ;A0;p)+g1O)1yB zo{WGJ>j804D|VF8YbdO0ePolHR6np!_0y0Uy6@#9182Y|jUNW09CEw&J|r>U4mtm} zwSBJR(A)ZQ&?9k?KV8p9xX|G{i2a0BoZb^BJjZ;+GCy+7C1g|)2CLL^#G|+8g6L_s z({m7xm?dMX+HcA45IRO?K-S;lT?iucE?=UiwrWI3n1CTFXBKKg(nimbA&l9I=XxXT@&@?Qa!E5Pd>PI z=8uDpWgVJ)KZ?dKefvbdupVmZWR2Xb7P27T=JjGZ+jXjcu{&ieM8uA6dnlGk2+@f( z-RdDsw-FgVES@u1T-q!&Al4vGbh_*GSpnw9rD(q2&f5^xsQW#WfJd{S##A|hPqTHZ zEsa2BRyk2K4f7axY>DUB%4%oCl(Xea=YzA-AYwvjT2eR}gXM@})?%yz$hLN}PNPfA z!$d+z?ZR*hA=Se16)(b;x)MNi!kHV(+fsE!Z5oJ>5+S$Ga+uRaxll}btcCQMg&faI zF3Mbt4{AdgaX-BMJD~I1YQPq|g|(UF1I3)j5y!E?2izGhjHW>j-%5PkX)76E=60Qu zjr{OAH*d+bB@u`!k9gNrG*{Z+ksHw_tt97AY_Ju7P^7<`*j!yZ;Vi*|?RJkdjDk#b z?M7+)s!}X~0|(;1cWVIPvm*J0BQfkq5o$MQ%mEv+ELj*Mj$1}H)jAD}6#FTk1Rtcg zsFV`KY1n!-bC(P>ZWDl`;f=-t>z>l*5?5+sg2rNrnRChbWqrPvjO^dE#C!0A7IY{J zRv0^!J+y1E#)C!|0$+YRUUJB>k|awWMm*DN=Ajq8U$=hnO3XQ)gY=`Mj7j=fkz5+NV%m0_2IqmZc{Tv2198X zEa#HMV%v#(^g(P^ySSF#^!Ie4;3-1_(R$^4VD}z@G4(zZk}Cne@9dtLj}_J(Vb?WV z5L5BQLpI(#gG7d@(B*7!{(VupK5?&q~Iq(WLTo%yIAy@ys2dIE7znw|V*k;!B{c23R@r_@iR zL{DbP5LR2my36oKjOH%tVtzXwG8u9GVEA%(mtmwIGwbdYP-Qo}EjjQ~nb&#cEPcy~ z5BN!}oZMOx)RuYkb7gbQJBGP1lub{XX=)lv81Q@M1%U!xHTD7ehyra0(QQE_JK27l z&&zLZz&7@lLRKGw8v9=gYK8wta1ga}aI`Y~ckp0ozCsKC!ONGMz^Ob6EtUFMWi+Nh z_EBY#j9HbNs$l`ZT{FB~0Y7K%<_|=J_YtHiAwa&tp%i3x{p~z381e{ujib`1R z%qhMybTB&za?}$rl5F0Z!Z*~O46eO$rWtO{Rh(lf`gXPuKv?68#*;k2=%)0%dE_Jt zZ&`$@FmYmgK&)&Vl0Du9(#xgXn!F(AuB`4734sL+x?*kSR&*ldrZ~SGK=c~A4Iajz zXZ2k*;ZI5czKh`Z1#2GIr=cCPlS|mgR~8d)Z)cqZgcr|1hY@C~!%xwqJNXof78Qrd zDm$%W4{@>XijLKqjuhC5hFI5qiCoYK;ZCLuVY1{Ys`&cE7 zmwRAXVP4|4i*y4$VTAazpWNRdE&|R1v?)wdwIIIYE6RwPd;L7+a2;V&Fv#|T&___M zCx0+9D_^Q#@BEsq=A2Q<{F58es)f#NQm*P@`_nZJcViiTMMmwJIrhld%ITZ;cm(D~ z)s+djwk`?gZj@x$<*rOtf-Z)#UGv$`PIt(>m2H@`(GsFFI)8gaIS^x-?pW1ZDnL?>}aR^ z>jdl{_p$?g93T4MrosQzkpae4#b2LnzPis+8JpU!r9U%7!xUHZfSt}Cg^ z=W{{`MXFPaly=c5H^A&q$)ee*ZfI&s3 zDS-O}9tu`D%2@lKlbW1CU60)J?w>uUj*@d-A%v_-pejVaOZPjfC_+x#IKU5J^Ro3N z9Fg=M$XQQ)>zr&34YV%V{a&IsBi@wK%-t*`drDiTeP%UEP?i-Rfh~VN<-XqW<+Ik7 zfk1FY&@B|*)w)K}5(;43n^1Dd#U^)IqY@ZZ#9M%?;%HC3k;&pdrgM*2?~_LmP1Sn# zd3vVZ*w4Mj-eg0wO&mcm{q7%^C&@K|2XyV}xepv%$Av%2=v&-k6Ca>jR%{8-QeiAf zPtf^;jR)=%XrVWw%hU1eEFeoTxyjB;&kt*AuJFpN6Q)~h0o9g=jv1OHwNiHwmi(d7A z9jdRFJFk!EStU+?SFa&JB#tWV*jwx(HiD=?Tq%N~=vpzRiS5f16yEMrG<=Li#ubZ? zR})<-r7puNUClI}>0&^F+59Mt4;Z{#~ zQQ<2<(igz;sD1cti-vl1Zh-hBFtKuoH zhg9g|)4f}CEQjJjhCQ>rhvHCJnt7hyJ&NphtVnHk`O{ zU>;rkSxO7gL=4p7QLWY%7;?lARQRGNSSTyUgU3JQTDP#3q`c3hs%@EG73bV+S@d8H z@rPcd@3v+7WM~l(%@Y`y#K4Jil;Li!9oC$W#ZJA2MX=D6%&mLKDF!Llx33Sqvhn*m zZFwur>U`%@BrH0boAjz&agh;=rO&!GG-6E4lU?)EGG-3O?C`VTjg_38qv;H4pAPRD zj$VS^NG-c5$FC`yzUm%I6$;o5IfL9_-7mrs*j`A49g3(mNL5b z_RiLJ|FjJHd#`)SX?~PJfBk!d3l&xsG8mtFphPTcy__Q1N=g1P|91s?oi5D9A#}&O z5${S%F~nsvFWP&pGq(HfMG-7as2U%yH?-u&A)WJK<#x&$ja&)*G{(bgT!`LpV0O;d}RZOuf6UC=IdW)fRbQ{6(@dI z0K@T2b5s{9D>7Vtx_MoEdAZmv1+iWdjC%01pIfeUt%lB4ajH+~EwM1*HQ`d#0WJ%q zKd(O|V^A=fEZc%`UrXOBd9s}7o}Q*or-!wOAIiRm8LCtd=`i1o7McJE-rG`3Gf&<0 zMy%JWukRw#*lOAmsJrlMOw_>;R$T9q8qAW!(De!=+cT?9TaqyIkQAh?ZjWc-@5!$? zg*HEP`#ip}QwjIHMB`-+jY4OyK(q8^&#Yf;5$8Krl1Lw0)xwbHvdX!xcgay_RiOJj z!b#jvBU``?_$16%zOp)pVECfKr$lNK^ZOC@u6*<+FP2TE4H%TXPYqn@avedu&lwxH z^Glk7!^yhvtT@{hP1T;i@yDBBZO_sN;1d2O^!^BRy#K}0|I^gL6gB!$;e;1Buh1i` zAVuWDBhU7Qw~j+GuiTa9Gu!47t@F`GHiavl9&3B-_)4-G_L;s^dSKO9swm00r$~=X zFM~`DY0)^W11&=MQ@qoF23btw2}zOl0Iw#zo9%kGzI^&JfdlA%JC~eeQ!0LVnaHlL ztL_S^)ga^Pr1VqjI810TZ}s80P@or@pW zKPOtn|H4u7hE@i@>YW01x~5iA)()mdrux5W*FUEib*-0a;a?{zS!MGG&Eg|JPr+Fh z_Tz#oRmDqZB3o zp4(u?D^9J`q7KX|M4Q8mN)!)r_l~m$;Qf6#Ta|hYXK(KnHNZDZq&eGG6#*)fR|}s3 zJg6Lgq2fUHP@8AMS8SXo(GWBMBs#aG2(^heFb)wa92;2GzKO_14*&Ss7xCQ_Fb{VQ zR8;K9dcTP!d=g^8UXP=k#%rVe0gW6&EH9XvRWHI}GWfYdqVV~QyQ=kbo&u&ST`;Qv zPNh?e!7QXFpE!Z0--wcHkR@w^$4(#V%dnF_qZ@xh#(JlH7AuEjE<*@t?*T?OZ-;`% zDIVYVI?lGjO7zD5mf0S?ROw0*fE}cYs^ujLnBdqiJ1-Z0@hsuqM2;|wt40W>M{9S9 zicG1-saCdZAduAtiVRzH)oa_SxMi&w*YDOiGg8{iO2)RlDQh5WofY_dKW5hwNg7Tq zvwlOcasn*a=MSkk^0z&V4=DeiJd5MsMzFoUovDq3o`to(`G1pT{prTXzloavW%&1R z{>*|=jOLO8;)ND6-$VW;2TaIJ!h?(qj)`0y0xHYeDm5_jQf6_Ui}KR|89S7MHl9&j zS2?Tm{^j}AzSgeXFbB5|3D*M6&RKy6zYBtGlN#Q}ud)10o6aU$=#-pBBOG(`uT0XmN43}Ot4R7i~gKdRe>ly+Oh?UZ}b0~dxGeMhC7LVw~r4^^Z= z|7(H`ZhoZ${s~kzk$JDHwE!mekJz;d>!E$w!t&AvY>@2O1Q6UfzNiO zE5B{uXfS-U0{jU7!5`N@*PG1$AtwGCIQ;vR^A`lz(^z~IC_e<|p8WD}2W> z87QAGp-;M{f2LcB-ZWix4WW#~47O=#R8x$gZ{EUf?WykP;>szEC8DL&TiN;2RYtc9Ow`2M(>dU#;`;jmSo355Dg8>!Q!AZn+1@&3$>ek=8+%Tb%I7nO zYwr6p9La954OWIA8mzIqr_^?*;n|`s4L`vgSachRaC7yS!?!Q5dMXCwCQY6*TQ8_z zJv>}gJbL!+J;0!fJHZ|>g~d5C(_N1NSDBr8xNH(p1rwdX7~BE2b7rN^r7rg7;2qMs z)=goY;3NxI)1>fJWO^Zl68wOi&=N4lE*x7gznR)|o+X>!+5D z*bnfFcs$%^&EXnz_;+s&2|Ch5)ykiSpn2-eA)<&KoFF&r;3gOYVtp`Dz05>=>sQnK zn`x&WPF97%*{sU!_|-L*sH;QK zZ=NZE;Aa~7KD2$C%HB!@n)F48y&xI>{=@W_B1&Iy8kzKY;09g2<^fvI9k!%O(Yl{C zcw>rWc!A_7aWsvtq%3S~fLE+&zbMO5IpFVqgSyV);8)@1%-MX)OHr{(xM0KUFy zlnN6}e82g%C$je|#4izJJ83(0@<9Q6|FT*j@ER?Y_ey3<#pPCTWn2yl+`fM0P%xlWT|zx4G){*WXOX8 z%4!6;$4P!%#7t)LVBL;k_!U?go6iM7)u9C#ASk;~tBA{wL18ZdS&Vi226KUc+U0|= zBMWb;7em+J%b)(PFU70o+CXre>2g<`4Bma^C)Er;1sjiGu|{6Zrp;_Kwk+Xv-F8$4LPjqcb<$F}Xf^uBMOd(M6P?AzzQAK%Ex7#UgPTdP*ptg2aarR7mg z?gra*xeb=>ps zH1y6dSds-&+?y<&h;7?ciKur!2q~BeoN%QHR0b4yzbpf%HEw<>k>$9G2x=-dHCJQQ z&TDuOXlx1AiXcHQ$Ngmko`Yhv_8;yZ|8Eh$!2jI_?2Ufk1his%#DVCMgzg{1EtZ4$ zIX9FQ$!G-08XZxD4oDc?W`d35W82womNg&5-u%eFxPPx6t)?O%Qeb`uQ zByjyP#MaAUMWzi=dy)#6UJ&)v$DRZ|CK9A-kLJi?PL}Us6Z#`F_cKdG33~yBklBO~ zd;Y^ZWLX&k)QNDYQ)EvNqT$cmQhJW)4lGyuA7o5^JEg8jrBm&NXahHLp+94m=8cAd z%x#!xS=^u&Q12}j7OW<-hZdvY6e=pU6uMcs6Jk};B&X|Tx!7q z;P!A=1tf|@VVe#~)&xVldPHXu>L+s?Pa>TjF^kgTpe&!a_viin<7eya4rlG0OFuli zdB7$3P!e1Xzx35b1w}-nv(Nd046zDbjNF6H{mD6-vI_B62SNB3+M{E77hm^)xWiJ% zg=;~@!$?o3r*&hW_P)l*sUnf$Ij%mw2Z$_s9P*+YVu4Dl8&gUi(fg7_BFJ)!K#2k$ zV8MiISSWUP=7V>jrm?}Tq{iQ}YrN-bnP^iJ0GmSlM4WjIyN6n(>cfB&YIu0K9Q5@( z8_F49KcBRG%?Ve!ZRVOUYUNmg3r98ws~;HP*Src?>$K$tRcAcj4c4b0-Yd%-rwiC( z^KWM$E5Gi~v%mo45NAgkL3FUTuu*!0`gO^;)+b~rKTfUdA1C)$Sex+^-hWGU{;AdV zPo(&Nh->`=@;VyX+Wut|<@ZW>|GBCBvEWCs$jrq0??#IL*;IZ>Z$&Yr)cIQo>09$j zmm#ajn!6j@iJ8Y)^VVpHn)CS?$hn(Yn`?(T@QC7&<>o*c?vd;jk~J46A8_mu?VIfE zlO2E(p%Cx+?+Q>r$s;?y`f>$r%x-OszHr67Y{loy`e*j<%o216hQiVqasH9q_=f~&YUzPvdtNro)ja>CUsDwr|zkc~USo!(?RigV}cYx2><#>pXw%j*|R{^+zMP{g@KvqL5k>pkr^Zh&)yR0O+!?IJ8L_ z#Uh-j*$~M`#F1<42JL z_Qz!nCd^#8pAItYvhdRU^<#v)ab}QSc9N^qhR5NvG9tLnpO;UEn#^z*(f50)9ASmh z+;J-QnaA>fz{!H~7eU4b?aD#1L3n0Rd1Jzy$_}iMKJ#ff7$YYE`+(!%MkW)B;8&`J~%C zTmB??+ulD{E<_wZDtDG1%#+WbRg$r^vli3`;V+6@qxeGHYXO=+=HJVr(}p1;xe201 zg+g5JqBcoFZ&tIAs$@Z^ALE_2{yiu>Mp$52mJdl-hmJ45@N0?YGrbC1f$e3mJ_U`& zn6O0xadL6yFn&t^;#czEKy^g-{2zv)IgcVfk3c{P?6o9SpC_}|nvi~?*9eS53)sG5 zXymqq>cI&MlNtQ5-(DG*ot|`v!cLqxKXx_I`2JH!`K;u5HZOTm_;H$ixoNg=Axh@_ zQUbcwF3-!^W*_qPzlaKKdAV;Np0WIIA;@ocmw(&xf)sk95$T~jCR9|Hfbbf%>~?({ z_!TSq%S_usj|&iGhRjLA^ov(_7uYVuUI3-1>!45zrpUHyzpi^Yd%r%of#@_Hx9Qdk zESl5}eqMC?j7WcApi3mfl^fKu%XW=a#~aGe=(X^GhA@eRJ$?}Mf@9e0pEep<91B}s z)rQR%(GB`8u1g2O2dK&fD@~+8T|*X(MSGL)#3Lnao+M%=eCed8mv=#O9Gk=f6M=zJQkYg`Pfq8dP=UuGjOk#JzG)kBHY#~MG^X9ezw3z!v z4?%48`-Jl7;7lmhg?q$k`Wt!8ih-kXcSE@4dZ$`Xl*2oIpThU?-l|^A$HpiheP61&nStSk7}rR0)m4 zx+Nh$M!PcvDij)!t88Z~Y!M_7tJ4#9RTRd;F3tb6BPDprJ(mH5sW&45kbU zZU>EdhVXh!llaIfB0_xfC5bkrNlVxzm0jlZkUC=oeuJt@13RmFt?WXW_et$p%-jgs z)gPKPfdCV>vzJW`ON7CQMf7d^SRJKC#7}cJzhe5uh6O#xNAd*yx5?9Q^A!Ff z&VR&~zZTQ}6<+^zpM}aF?vn#fYea{h7eyR}1>swx0ulhitQ005IW*_t>Iw{XlvR^w z36YTs_?3~?icD+QWUlDIRsJE!8A<4O#(k`D=#aPj{VypoSv%lGX`4YnrPuO!ra+8U zsio;b?8RS3)wAsO=t7uXBy1(D`4|TFO|?kVOi#}MUjV1c)khki3Mw;n@l}J1R8-xP zo!eJXnY>XI$xTdoCkWNB*N%z&iuvGI)y^ASF!XJNW!0}Xm=eQ%Xyp{U9TcM{%2}jH zA0K#CA#rqlCVd=uj$zlwCt9mRszFz;0rXityu8N<$x z@mV_WYbu0aYE9&^X+)>cE@gNh19m}160RoGVjp#`n~NqYq(nPrPCrBl@>Nw4ccUWd zfAXiT3Ci&rLQW|x&G`3pyEJ4x4M|;t>cjz7gT{`m9n*r#FDuffJ)=&py z1g3$q^0cuRV(B>JKR~}$r?hS;OVB_&n<*zuFAv&Zj?Wg9iTVzriQ^K#b45S=B(Ght zeb|`mGr#01vEHNHrY&0!>+!16hm_6nL+pUkLT(&*otc{Hx^VGZWPtwNd8$Or*U4RYyDk=tPq;W_jYQ64HR`I@h10=Uz zuZjRq7@0dZe$LKr+p+ttq2Ow==UB#}7jj7V&-l^l50Xp%!(98faNz=*Emj`3xN9Tf zSgJ5-%F*zVJ+2&Ai4w*s7&oXW#2t!UWnxK< z5EP^jj(|$70z!!cVWblxr3->drFPv~>iHoDZEO}=eVK5CYXI+81TLS_8MhK%mFU7a zP*sQcviZ#rV~wQPdlM3eBX?Kmcyflc38z^0VJ3B2gnDdur17itvut4{qr zf-yGF*XZCWvb?3qWkocOq9tsxxZPknP9#Jm(bH$O8RXSx{2DM*z^@zNJS8h&CoF(g z-&{06)_AkM|48>1$2L0VKSE+3#NT1q|DO)V>ffe%e>#`}rjFbkbw0w{U)pk78Hi z%QEX@N-wP`(Du1N@^($CM-BX1@!6p&iek~Ik@o9-@K5PbChi;sSWRGcT6U_RagaNw z;Xf{@F)6Grn(TtAtW7Jq>>6vTHtaL(%w(*-8$l4Rq`kd@jlNSfrUtV%gmsH9k#$o znx18LFn_~Zc1_Gi6JW05A|Kjmfka9kXJH-53R<_axvF3E(TZ~-UP7TlZfZ3m#dIUS ze;8ogFKF|JSyX37t{9n)DEz=oCrb&Pr4ZJP9!T29sF`HuSZy zu$E%JKyIm$2Ggiy_Y-vvjF0F+&I!M=5^IQ3l+W;3ofgeEcByZ=1a$Anj&Ep(Bjg>d z38E)H!*+dEe(k)B%RR6>ICG?@1S3^-nAq-2!&6FqGoL_X#@)EJ%@ts~*u2k!Zj^#M z5AW&e9=W+ao`&ja`K6|X6p;B=YU+DgY}8V`MG?U{#OhvBt{3V()3-U?Aj}_c<65381%(OJH~&4}@2%b1|fEbIfqmqON8_T*T(a)m>ZXOzsrNEZeu3%?f`#%&NQ z&^e(qmyW8hdQ8pFmx@pK)T?2gv5B0XY^%Z>XMn#6+Q>%b0e0>rPIiS}8qxworfuX= zKc?r?=(}kWi~@#3e**J&#`Dm)JTdgXGPkEsOSs638mI(tqDEwEK`C|0Ao=pO{bT&@ z`;nUTc7D0RaCS6^Wy<+mQ+kyCw5mee+L!)|3ywpH+ZrKBkg+)NSiXIaM&{OL8N@{a zdv@)l;J(is>dU$hEKXXgMJzeT8>VFcjFV^+tAv0 z?rNz*sg$&;^_)#-TDctyno{Xs-Q(|K6B9kC%e&E2*pzmID6@BAiDioV3F{;f2R0;R zwwf;H%p}y%mI{ZhGp;GZ8OOuCK+!_^IZBMkmDdVcl5uFYLJ=!p29b^G0piQi!m1tn z11QanE|1D2S(|uTc@u??l8x-epg$^#qiplH7?auzcs8kiirC?Xh?s3pp!TFWX^zjW z!s1)kQ%8B1r!qDX(-FX^Vz-@91-yeV*4ug71wYp&yoNhfmaRb7{x6# zomI*2`GM4|w!^QeJBz(9n9hOd}P7@ACY9r-%Wmh zsdD{wf9uyU(l1i!ub=77$1|$1WSs5YA@C{p zl7`0Faxke@C_qvzs|GtpeZdk?H&|l|_UBJc2+&M@u)ZDLcqcjCKP>juw|ugvRXyMS zAlGFqODFuIa7_QB{PD{I{wLA+w~KQBWdSBe)<*XKhS~gk$MV~w&i}fgu$kotk5+&M5Un9F4~De@=f#mNtU9C3 zc~e>Ogzlie$S!O_!rhPtUf4{zce|rhn8;t~E4r!T2r-V$5rNl3xf@Y^^879Nk zmLsnfHHY4}b(73HlVpr|Xn(vl!#5e#rU-ZEr@wK} zsIDMqhZSEklUWS3X?V+kTf~Y(5>CloGHP~T@R*uI-_Y;tLDad(smsrL3)cwMzV+;* zRl+e)SujG8O{YT+Le95TN@uTpu`H@Ecca`#g-qYV! z(fvDvQ2gbee#aJ0m6xrTh2gxCJN3KaVv$kt|)&c-N8CV2N=?+f zm<}#63!oT!@Ov^U1L2qooMeGsJ4Z`cIYaBW&pQ}hSVJuDd66^o*zv-`_&txG`xJxP zJ{2>9UjSvU=|clF0@zKePL>O!Qklg1}cM$ZjQbzIc~ndM*xJu zb+Q|MGu3|1)y4@<*X-o-;997EoYI`w%HP!PqIm6Gv&&F%BHSU3lQ=x@)UIy1TgBJ{ z7(~Qy;R}21@+gZuMg>VZs9CSg8J*o?q&a%_|!bC@F2KtDoWi3hfN-HT~8IcAE{G6v; zMNtPr@>w6fHJgU6!$g}4V3crU7&pJs(WnVrrO{wYjWI6#if@u2@K=PgRcRjRIRxaJ zwAM@09r7utB8bZKb9)8~d0&t3DTlG_5@`2VM6ThJd|)od9ySi0i6wYX3RIi(Eo&b_ zZNufC5&#K{qk-e{DKDxT-&3qt{oXBHg(T6W72{bnHj%@Tk>M+#GuOUln#UG8^csDO z1yEvDTy->P@(6kmUdyQg+JkD{62ugCm{k%l^J1U5vtW;EN*9iGm8(H65){Yr)Q+>~gFiEC@2BOrMamZf;n&|Io$Jzx60H|5;nma#-Hjlpndx3g8v`yRTK|2n z6-^Q<`C5sMGZ;*bPk2Pfjl*{9(@Y7U<%E0o+d>?*&s;5+s#QkA4vdkfyntpar493LQ{~LP`XzC|)Va*L%u!Tqk6@n%iIcNYy>6kx z{R;rt0lhh0Zt8|zzLQ3SG=6+kjgZOMBnn`Fh#WE(`MTzIc>b^|gzD5)R65B4JFy1m zFz>X?vI*fk{1J8zJxfK16Hnqe$pM{{KgEQ1TEdZfVULVQsW=9##%U!y3Ednk7bs(F zCQN0~*+q~vb#NaOW*5k7(oRBM$IX``5n|O|Z^H!paaJmqu;EJ&wv?sqJJYYV`x3Oz zxVtWwf5;_tx!UPOe#E)pzm0Rh-HrSU`}qCvraET7&4R#_A>)^%MQk>&Z&f9o8Gbrt zm=$hSSvZQtRfQ26NYqc11Hd~{)-Tf(;#J@k<^B(*D ze*WIc8Hx7NzrdQXnM!kMQU@oA$(|B!G752=sv$bEv7V?ASEpZ!tYJ5zEX+ZQlu`9l zCJ5n#3y}huBpMcW+*lgTcp#rHr~iPzF!>OD#EHU$VN=>1gaW%hdYiCNC342UVaQT_ zBh8<>VLBK+&H-W?O3xC@@9KwtIat#?2)4(lG2Vxkr!vZmT#X{-Wz6^GJ7>1e<_;_= z8k;IJ%2Ym0Y%LhlJ`EP{*b#^lfFzs3 z-I>Un#MT}Fzl)#+)-7v*t1l94*qXJ08Y7T5Xd2Xpb$ECz^!A5~l6k_`x;1OEd;vyP9@bWhQGq@ z^NACRO@%VnJd%&>V+j4=g);m*vnr6uCaxnX$3zIEADAu4SbjTidm22KR(|Yg%95|D zYxe~NJwc$$#+ON7pI8_k={)ZfYH=Ts$`;viLg|L|A8vBOr}Fsa0N8`m9xy$XOy4m& zX_RR!4m%o z-=lwqVY^JygACU{0M~1lDtlnx9}C}@F^(4y+Z%UPYsZXarA4t+@ml+xhoHRz6?^4p zlH-pp9uBrLgiADsvvCffI+y#0f+VMt#MjAVMH*ht$oVq^ET4*&d3Iw?kOIJWUVKZj zyJeG2F&NyDic_$XoO6 z=M&cNj4EgY+4-qfB{6w~rSTP_Lg8rCSPRc`%{>Ev@)y%#nIN4QEAoSoEO?e(^`F15 z_Ke-AghCTif~5sUp0?xDy*^->^8R#HM1j~zATDS9(6DRP@W+i>7vKvCf|%yP zi3%z0-?zl$sXx=u!_CxoMqffXdfg{K_MY6+OFmRWh2|-fm?0-@d!fdBXm&LiIaaQC zs)!eC#56f+z>sIZF+{?~$;-)G@m-Y1Wgm{3Zp$q#ERb z2SbKkK`I?7PMWTmM{y84$ox4U{3>k61KCh=U?#6FN%YAqqwl zO=CswDntE-IY!i&8}U?nSGI_WoMqk&JqY@7cT&89coau;S0g$m;S9#!@j+f^5U10; z{vdQHqCjWpftmngW&wT;iQ*epbURzy?=&V({YlQ9KG=+H%I;=`U&$eh!FalIg%_r0 z-xfBiNR!2RLexV}`)Z|svdCiULb$jreyY`yC4SpvRmM1h@d33|eJ%}-qy+A&uT^1d z28{}ExMVbTxF0KSlUdgjHdSH;eLj4axwmP}*mP9E80i{(PpsO}z)LPyfpqzXTf-SNUUAjuA2+LJhPM|UPXJgnP;Gk<9 zT!i^4z0FQPH@)Sq9BPaA1s+SEpli%ogUvflbImcNsiToFtgP_$nME@srfVQp2w`qm zylQhH2&`*vSRZO(c)B}bxK}2nW1Efa9A2bFVBhQQ$VgD$e3Y~*U8bWs8jn3ozJ9e# zcb?8hbS8dD*4yM)1I&R2J3e)PDWkI4f0UL^`I#P0`Du9b)df3vcY6M_e(?~K(*0B8 zm-K14CEU6^Zt(6AR7H0KI^nUj2fqzUuN&%Re{7nQ>&?msO;J|^%f~ZKOH1#{V^gN( z=WLgSrk76c9FVmo`aCaoZ6;B$T*&CPESa{;a|JuI%Ts3+g6#?X@-?s5h&|{pGE=1R zW>)wY8dU+*>(j@;hLeBJHUfd6c!$17ls3Y z)hQ703<;r1(Vmk=IXSr&ufefv&t&b;PS(Y}3-PT@i#oGJYle1S_c#jsBKZ+xjVN8Y zgKl2h3oJuTDuTuX5ht!-L4&Ajl9yiV0&fT>Eq(Q8Iz7lG)ncGF28YNU3QKK1s{$?E zDX%CAZzREsn2N3`dok+09z^YX`Xwa7d(hF^2T1EA>{n;!?|=C(_KXs&y)gL4`~>>nCGSH2S&{9}@{yFDwVClp%1-m! zz-BCY-Fk@*X|Q^eJer^&S&Z-Kl%=TgW+Ju;7GTNRojxJcSHr-hsV`hT+Y;5Iprbb$ z8xWtm@RY+$ZrRsi{PO;F%i#WgLTAX7U`zzO2bIKilq$ub1~ITSoRN=}#La&Y)K;OI z`KqT=9%ZR!T_nFBx(}xw5gfxHS6H&W38|8ND{l@mo%(*1m}aE8;gzvLPed$YT7Ic@ zZ{SnZrntB4;2k|O75}QZlqVb$e!HS3|Ao}_zQ)0DT5Ipc4#%9Ec*h=;wn7h~u`4iB z6$5sz-_Plk_%(sbI9t9xs|yAa&3>(j87X`*62cTcK=5ZYqb0n+;zSV#pRAn0iw`v< zLa2R9aT(w)0^e%UV?JI+M==mFg(VDK{_Zl2Sdbx>F=2lu67l{LvvB;lLpp)7wRK*6 z+J#3@@q>9Z)HU(3SC;u*2x}23g{FCtBgA-0LQU8mB@JK*htnahQbDAI2I+`ifu!Zq zR@@7;HikX;dU1h=DZNFHj<((FY2J6%)KeE9fNNNpupESqd1}E3^}6Y$3i)NzTX~z4t#55D z;Vrla4EHJi;@nD#RlFMc=fl`FxOhTe3Ss&?0?}g_MTWvkt?{A{V^Ok}cfzfv!M2!L z?}B(+5e5d;^%oX(G8}=vzT_L%Y<|X2Hfh!d4x9d-2XVrBFq8OLMP7SlAXrw6e40`u zhOW)OIpaYoFU1q_?a8%xWaqAIbb41g!)*~oV{HN3#yNYds2)G+&aP0!G)I?FkI@mVUM%f-J=|PO zUK3=e$j*!JTORdGO00XjBe1MjEd=(_uC(ebs?Q%+*R!@&mh7XmnhE)LSNFG{)cn_c zL{>rJj|nKg4}l9ecO(1Xzrs-ptG}*rXY|)CGJc9Q(b+ZYGDTfFd?{?oBzHb|h97>e zscd!FOVO8xZHt7b$j(?H$#v%H1;Cr0>yO?iFK>?)OY4RRRx(IwWXh+egM*saFb!&S z%%6LU9wR|2f2b#ZGqnr>v0gRrl{Pt((y?M`|30{~XA7ZnTGJSB-m~i=VOyElArt>> zq*S0>(i@2Fiiigp(YkR3j$Ru|UmJK=?+VO0X-cWpon70P`q*CJQnHW!jVrA{J+Vn5 zKf5!ZL}cLs&PUC&Z?<K-x$Ncm}{p)Cz;I6Bo9CAM1J8>uI-il(wUj(mx(YEq_PNkYLbW=7L42 z^{oT(63VzX#lLY>ksM)+$3fnB5zMu~2UlO=i?pqCig3;I6jiMcNlN4y+n`TU3LZyI zT3e-vTQ#R4GF#pChZX;!aMJwxU5U89R+$`Q8);RF9+W4zDjm&+37*~blmSf+?F)ps zHS}ydoW9HVC!`g=<u$WsF>cm_tZsA{Z0DsIf49Ze8Gn1B2kg4$4lrJqLkQ3*)lQP{n}zTC%VO zbp2@bLwaCh+m!sRE{6|NflLAbvL1fK2Xt&$+0Jgx5^l9!3eM}Lhadl&rwO_kPdHUN2EHdCRwpL2H>)BE>jZ|D{U)7XZ-W3IfsdWA2`2>3~e;i`qQ>GnU`IAiTF3i$V zNoyWTN*2tnKO%%x466rfX=WATGu1w3v43=HwNUHqkk5Lj&aWaL%DoeM|3?vL@LyP( z|I@U7dsygSo#vmW_1kbK>SI)Ii5HG%S%#jW1X8|c)?!If3D7DI13@pkh3YP(L;t6f z%YvEdaKi1R$PuOEd@2%L$Qi(9+N1a0{zUcqxrZGGhRL`3AAO$oU!ua++vB6TGK)!7 zKZIUVb|^Q+$b`lCKENM*{01M5l+l0RJ0{>?AaVV zo%El#r7=X+)u$*ZPg=|o-A=YfFwDwr=kL@fT&JWf<`!AGLf25?uV-W86f#i^AZ+oU z9~3hs{wixY zur70FH;AP7R@QoNTAV!k-_Q>W5q}<22kaZFPA*As5Q38CVSL zv(FBO+yxr$uuG5{Zepi634STHaB&Tg@f`JpmurCeQv9UAxDPM+sOm=coBe6{%D*j)9bcbl#W?UB8g=+9}8uGI#2Qx@O6tCWMGCniczFUT^V^Dke z#3pV@#Dmm#TQ9q!|-CZk0F%4t5HJr9&(RC(j|@ZI$q#DKdQ1 z6sxy)djglRov&z zKOnv;r$1gf^hBzaZ%*bT%oH0X8R>64@_*_mV--%ip;YIHoKo!NKsa-3wWI7Wp0f*` z(HVECYitZb6uv|964;)d+vsH`y2lEA1m+l7IUi)TyTMWq%t*z6qjINPpdd}eQlM$c z>S>TR1v<=waEjYy9Olej227cB3t>5(XeJ9r8Mz&weS=u(-A6Z<{F43Z*AfJzEN|rh zRRNin!3W!X!Fc*~5MGPmO2vV^PM19Cc5qD5X>+hKluQeM>M~cLrV7k@EPeDVzu9PU zyo_sSIAZr3@?H{d&cdTJ=Ez;fynO4~amphmKWW+W5iQdd%!EG+76YIIrnib_(c2?_ zz@f>eF@k9H-H?b`!X7o_fr61rp{le{!}p_u9(!4cO)A_)GzX>`rF?&hq1^rx#3M$d z<_>2PJzCHw!AcO9kSqQyMBD88*s2S}* zX&T9+AL@0GoKW?HKQB$Q%mHNc;P{vQc+;gxh#;R8^$d2P!j(ajrm-iaw9(7OcPpix z{nc)iOI}v^1dy|gy^&$D&f*g8b3u+)%ydN%V2}CDt7{-hVWxV+7RKQB#8H3Zuo4+B zc$YU_(L`UpeSTdVaJd8>-V)1l)f}$p?b73p#3MA^lMxRX{6WKgNo2GncPQmrgyMh zHRO=FZ$PC#e76kB8$7{B~QpDATP=uf> z#IYih(%q@|Q)9{%OQYpk%Om|vFQ9>j#e&fy98cT#>FXw#QCaHA^>gOx{HY_y)`h-N z$mYXptLowL2Xj__3cz@MX1Lm#Zva(#%B+4o+%O+vi;hworR>6FK6DUKhNn>Yq@*zrNvZ?g12uAE;1vrbPmRSTGVdWu=PC_f4!$_pSHMRC_$O zjUu>8zm)clVyI}hNBdFtY`iZnJpT|$1CL1Y(eHHN2sJgfFKGy zYUGaorgg5`w%xq>UlToy7DHuAQ1VIcryokQ9d2#ECd#_+k7?DKEG&T^w?T7BRP=mv zdweHBT8HADP-o}$0=X-@5i@$R*NxCK`-p?)3$R<4usH)`Vj_9euLxUd0$5=&99Mx6 zF^{xBTmYTGf|%@!`XMV&VfhV-?2?TlKvv)+{Cu^F#J4G%=__wNb}NZUsrc~Eqp$J_ z5XeuHAaAfKnKMfZZL-L+n5w!%!K_65Kr|{%bK_Ku<0GD?LQ&;8t$~`T2c7KsQn~E5 zAc$?$r;=>w%@-)cUyu_PYjEzX+aB&}ogN`}3j^Lmik$2~6}FuPX^s+HL;D_2@7x)i zR!TEr=I1p~QqA6+&Ly;Zb+dq2KAUGr790yX#OdcKpG_Iiad}Xk-pJjjXCHDc>G0FD zlgxV>-`?v|W?>1^OGjdj!lwd<;nzV>J%f#m1(rlp?8|)beUx{yqF3EMq0N6`36@IU zb<6fr?Pf)6k360+rCCRbZv&mjC5e?CuJ+EA@bpc2_FXy)>S)V+9~aVy}6NAc2G^C2ca*7o%P@C zk2Qn@p}5}#-QEFH9%0sO7NwJQ4K;(kR}6i^BcnEpSE1;{n?jpG!f@zzoOGaP70qs7 z3P~>WK6;nTBUI2UtiSOucM-r~O&srM`@w5+uRIazJ&o8dzlKvAp75DN!z;52jb5S= z_z-=_W5;~&)&S=l-rHyo9hk;VWb0`M)>5Z!Q>F!x>!P#gEKQt%PPp6CC4a_&W)UDv zUZzVE+WIVtmJ()L%ZpJ@Ud;aQn{WfLn*Cqa| z$s=JKdn>&^R2x42;;$&C|BQT-;`yZe=-`AF&dB-ist$wan2@dbYWPBxPox-2so0%& zITCG|Q9guobUDL_DTTUe9^WVGO}KP$zt*^x8{LKPku{|6*B=^X=Pn(}m%MV>SPGe_ zJm#f#iF{VprcjDTJmkashnm%{IkTerG7ls$F!LlArHv%!jqD*L4U0?hA z5Dt2+kF)WoQZ{HxhAklHiIZ{A@o#rZCippB?*Kw!?4NVE)|^DY|{+ zCchGs-#DR4`PyG6wEB!p;-SBYwxXD)s1OiZk z*M6JJdB^p_P_b(JF1iN*m4eo2K>Y!et}K^%63Vli>=X^xXOv4x%NQ`SZFGYzBIuv$ z#|QibP<rIJKCwi8~X3^b@9lgNJNw%bu*mA*Gye>+})>=YD%0t2&MK-83x> z_%7al{WQv0Uk}vhVFMT4z9nsHl*`ZxhfqK4E$SSP-JBXxnNou!-wTbsfo*?I2d0^$ z#usCe7T`46jB@~}yp++Xg{G_|Jl_<=c0br_A8iij^$OTW9i=|$U!5}vx`Kt@N)cEK zn@M)*M6ArlWE;RghBuTbR>}?qzmL;n1{WT;#NVvs_}+HcoklRlPZr2N96lR?lJ270 z4p=o3Uz+JHC)mVK6ONpt4xXg)+b16u|cE4CR*QA0#7T%W6jt8zhjoCc>JNi^jAk785m)XJ*NgSq7&$Q4(yI@=V5u z$7(Z(-L5zVXLH6?-_SpNLk+ft9gTB07U(IQ|3){%Q9NBOhRAju1WfiFyIByiE=Wd_ z957?ev-0YDS_ZP`760D)7UQdc;-TC!nblV+eWIHnw_*wY0K|P|S%zFJg`V(levw1H zUU#grgogDy!{8zKuOtGZU9t*d;5henJCYXPkZZS<09RNcMEwD5yj?=36f94h?y)$k<%Jn7(u$3ZF~82foO_XT z(4?@HC)OVjWB(cM&EmXT?={dA6D?4Z5YpKQJGnd5y`HXXDbd4Xdq)%BC2=j*;nXa# za9cPO;f6IC6MhPJe^t(A zfnsaQ4}->wh^N3o*=R%<#zMvD*~Hgom~uT-{#%rbIjLwgcL>(A>O^ zJd5F`jQQ52>BJD{(6S_7l45Ke+4q4;$EQ;>-;e}6Rl)fr#B&SbO2Uq(1#*aT?`xgs zDXGi?nTG3V9?#~iJ4k#mFtmWDpT|51#}Ljq6LOf7eYvcuiEUSQ>`;TL0gUxR^qJH- zeFb7T&2>3_9f31W8qw|`5C*{V#i)~QMS!zJMRkra?n^YwmepK#Fd=s^uiSl;33X>u zFEOambYO+Q+!fAm9iM|FHU>(?sUe92l81E5iHC!Qmx9yk4jC+&JD)ylgPe_6iOipg z+lL2CpB(wR>i`zGJC)4V-f#;@Gu_s6V71Y*>uzA_NyDP`f&fdX7VJi55z-jP4UF>2l9J2&agHN@*4@fL-|J&wH~t<1qho0Omk_s}cnhRH$HR2<^B zyiO75tS32Ru}ElhD+04kwiU?$mNz-(Jlr+G1*Y*+g-56b2(OnvI!AWkR?NO1(T)5c z(e1CYL)3phXGJqBqYoObnTd&!J^f$G(EMI5Nc8Xj>7R2>H0BOA*8dKN{%J<#d-Aff z;=`j7KSm{JmFwg~L9dXJ%6f=oNoeU=k;%%C;?pwJ3krY`X-cU+L8bTx3WPJv%)}Wf zFhs~FNHAPq^NKTE!yyd(KrbaPWY?_(g#?9EhbsY2t_F#()olEub^J*_U_tj|XW~9S z|9)fx@85r;|23EW^V?{BJeaaV_witg3RhhUQL{76A$}si7&v>PwHY*b;mKnJ3?aHW3rvfzzH#&n7~(^znd3z+-@S%JdUdAt&E zwBH2qX2rd&Y0K#?m*0E%2p`e!4;CSX$m%TXdm=LA-4d=tVvQ!op|*lR$(gzLAz(XP z2w58_qzmxFuR5-^2vi=Fo&{Y_xP$OUyQ-ZcyIVA3&ITZq*w&K|6?w!T_AZC+ zmcKfTkKIyE`BRF+LTCn}5pLz{BU(dx_JF{+))GDud2N+k!uz==MXCpZNTyrB$Q%Y+ zCKe=%)R_noQr=JqU$GFKAHD`28aAlfOLtu`C)Kv~S-Hh5&wS&&rInGs6(;UFKyz>` zuypO7lgavC>fEGOcX*y7xz#+fkOLEgW#|uX!=FN%O~xr8k>bE)E`3HiWXP2_zMeAI zCrD(K*pQak#R1c;@>4zRHqq#7+3Cz3W40!@W1eJT?T`P;(8xuos2e^m()HiMf1JOu z5GU)u1B`##N2Q9!2Vg|nA1vz?m$=I7u*Uy_0mN$ql_) z_ZT$51AcoO$M)3f(!zne85ODCP$06V_7ltcQ94}S|YmwbSPyo_SepJWX0U= z)ya|B>s2teg&W(^G8=X~-d{5|Ja(-hmCR6x7^;!C4;pU&v|wwQ=yBUFI2CGYEo9k@ zgfyZ)IQm|WPfQyQoc0NumcEvpM6P>ZHYb9D704CF_YDV$6-`^zYQvP^tDICys(a?J znxZmz$hg{@EmC`nuMGVjguE=5b}zNLnNnnp>~vMPXblE7I-O9Zmgaafth>Zhn&|)2 z+SNc)nXTa?B%!(ajgnDu^An{g^3#bFQjGrGDUpOb&S7x;jA`mL&7G@D5~a}0?FYA6 zvVg>$pam{RG-TtA6uCSz8`)Wj8Q&fQ=7>fN93JHGELt+STavflUE@4Mf3zk8p( z-{MrcGHAlT^+6ec#TUs5D>C6Y|Yk0n{o3#)?9zS zSgZEVKkk~Wn9wlT(E2RHR(o-8>70eld3zJ0t-Qb75&Ou+BzynIe{V?5H_ADs+w!3J zSBKQ)oikR6y3!_|nN<>ZpnOU}UzPFx7q=7DLuJ0JhbVRw_fJt;oOrEl7ifS?}ND*07Cw_)@2 zLl1Hud{cC=YL$EoohC;;)3wcBk%HeHwQ)QSI)G64PgC zFHY>Y$h7I3uaQ3U-VghHp63fZ><)*RBnLINsJRcAmDsn28m89GJe+N?ru1>TV^E>y z{A0#j4fYx>wVpOF^K;Qd4}&vN%b%s@m5Q%RKHJmoKSyKPje>urc-KDmvu~OI^8zc0 zO}$fZ$b;c~b8cAWd1ZJe8yD0LTvGAAATIJXNDB8q>T@Eco#j`{>idH$!bBcl zcSbnxJ^w>qw?$RWw$&B4j%-pNs!TI05dY$m{+Hm5G1DgdJs*GP-rs80&68LNOiX`Uqx<% zx#)l*IRB2FwVC*%_9v}#|0pZp-SoeW4YoUiH8wp;io3MIw&{AVZ+BVaH!V+pJ*W2h z&C_M3s+wB57*nIalg#; zL!-HQH*;^LKDpWIS#VtO*Tmh$@xqf^4rcbc4wxFMFZQ@Sd%2<3i_`9!-5cZl&32fD zI#s(&t@+-lC$MiSQ65Hz`r7S6<>CA9DO(TCnX_HONH4XmVTnx3J5fZmq8+m8$O4QO zUX)p1{sj1`jMG=ufB8H099vT`<<(ya1PUSXtG7fPPCSVnU8$%GR0t)cIC5M6^rUv> z`0)ZkjR_hb5GRu^hvG=N7`-q}(MzMn8L=A8v_9Y4hU6uv2n4#AmP<%CDcn8^^-`s; zf{@%vh`5MEPYjhx_%a#DRR{+(fLyZA;3(@Ou|%ICD3)qt3rs9=8TK_?QY_8kA}jk7 zBdA@!q;wS;%_j7j2UN+VC)uhBj|vk1AtArlT>+(Z?Sslu0|dDGb{ZD@oi;V{*GF;t^e9%IFwlfMl$?@FoGN z5PcS6kW{re*?_#*L%R*6C`YFlq-aj(I%9Y1Gc@2b$TaIgPm|f=%hjSP;{t% z1*MY}K&edn8F!^#5pglGschJ4DJCFO&xY(KdnpQk$uxn$7ekY%%h?13&kG`wSb;J~ zKx}M`S44Pp0?LE5MJB>SwGI*WoBb7!_95$d8={i{3{NJy{?5@QukfryWZVgfKBBP5 zh*x~vToCD~-pDpa3r{}sjvt2Tycu6aA4tUFXmR9-_Za(|GMESw*)PKYMbF|32!t{h z+dwMy)fkj&>vsSJTj)mjk7BS7^OSgxNyV>D{0ii#nFUOpt|g4F+t=qIUxfHBeDbVoE_qtXR~N z!44o(2*X`iB75D_nN1-^ZBpX~bc%!xoONQ1l=N#R(Y%JN1(5`jzj zYv)PbhXMy$i1ZfZ8^dQ%6WX(}sw-Xx&KnD{B(`I~1gX7*nB7)-@yEn^@{p#1{7h>x z7?NfDVQ^y=qgaS;UM~jFM8btDxI%+OO|&~hx~UC$itysE?Ck(RG;FdCokg55i}?BS zvk3bv3|U<8`6}nFTN!B*Wm1sy6%vVf0TaliE@%_aEtE;_rZEU)7?41o3{MP+djM}F zN@+|OfI!*@+%r9ae1W{#P}5f4Fv-$Q&iTL|BH{5Mt}tE_AkdX4bYTiv81V7yui>S5 z4CV<2H$a{Pd_L%}b#B@5YCGVY+4QrRhjhi29^jo>x6xb zVWBFMCV$9MC)fv*Bq%65)1R@z2q1G4Z1#ymXx{u*5NO&bQ+!VZA@$r8KUH*!Ug|pQLS{=jWv8c=`D)n zgn?|yz;^JksUv>)a+057PMtl`?1R=I0)X%c?9hgvCti%^Wepq@^RrC`Jg_4ezDwIB z=7on%mpBG2XF($me3d#D#~%x4lfb3~`1xUSDlZr;dWbMs1im82zmRa)&I<~Mo(K$( z;Ok=_ETw4ggiHCUG{{JE*yY5nKX-wI@ZG^vo?H5 zfM@N;SsaE*S3N0z;KATpFX3ZZvCkUz!+ONwxE*1#)@vw(1!w18PSS!pp|F`$bJc-0NR zAR#JX90unofLBbWW8=v>!mNei5*?GxkY~tSCFYop?Z+5(D7m`wT8%pT5?3HthW;I$ MArL%4!UTf<14#>4sQ>@~ diff --git a/FunctionApp/FunctionApp.zip b/FunctionApp/FunctionApp.zip deleted file mode 100644 index 6b6f372b7441b8ec614fba59bf431bfad125fa69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49223 zcmbq*1yo+ivUQN)1a}D#+}$C#yA#~q-QC^Y-QC^Y-66OW-1*7e_vYSr-^|?1|5&R( zS*+FPJG-iD@2;voQldb>h#!9b5w#Pj{`TR&Ucf%!d=R(LwKLVTp|W#?`~U#_^B=#x z$jd>006qSyYP5K+YV`A6>i1cQ|7{k`f0;#A&&I~c!d%G0##Tnp(o{=F&-xE%KKWlW zgM9$|U%!v|Kg_ISsAX=TXKG8rK8&0MnGHL*;vAzHKDrNJ1gBQ z-OJlQ$}1|W92Yjp(3<3c^gb_Mg#>tiKc5shAvQnH%a;dIrWDcj3LylR$2rfi% znOaiXI@tr$VPiRMi)bp^qLNt7kON5#yoqsL2$r~ z-n|^E8IYCvV~klt53wD=I&oZV65jaYD2)=>yG0ga) z-^)kEpt#wS`~)u{%DpyP?>0R)G5`xWi7)rk{j@lE#d%H;)kx`LysQ%m60P`Z8UYia zS~=$DyyT0v;p_6AG~{GWDRKkxj};Yon9^gQ94ArBYZc50R?E<2*$P*_uJyyTI-YD! z&`X@G9fSS^0S$<0@+SzXzk~4p;lsbLPQw2Hg1()(j_v!B{bL{ewRQUSm#@M19uzE` zC}f3X{>2yHq5qG_w<#%Gd>4XspQs?#!vny;)YD(r_oF9|@tGs!_R_`1L$o^qVW(MB zaA4ct>#&f2>O}dZn|lL}=vB(^)8zG68TPV!KP7d{=fI6y4N)f=?TZg!3w6o}`H2 z71~Tkf635uGL?SqrGI&UJ+ut6?u1@=C&1l4>M3 zckfGA+Jzn}bxr*@W1+8!{jnU|lQ}>3$#yNWnns($2oM-QLofxhb0WSB4ieNDHVUxF zY|KaIbW>dSu_WJvx%gA`5ULSOLu0B?w^5%^ZuSvZ@YHgv+Vs8wwtH+2i{;X3qPNYf z8gAUfJk!t39(ETD^+kkOP)84YD&M15I^kby;Pr(#;~@<=khBqRdg6eI+D~*bZ9XRP zQBUB0U1Sd$f-RcmMObI}VOtu0e^qb^?I5tdO$P;=Vq^Kb1;hS<4Lj}H^3E4hq8%3x z>=jI4-3EXTn6PHE5@#8<3rQVjgPJkYn?;E}U}3_dWizylZGk+{5*wMiWraGjG|DAp z=cf1Q6yOtI!Q16kSIdq~YLnG$fHq-QQFyJt(LSPppj9NZpk&r((R_%oo@bxP&CGMB z0%GBY4%~RZAt96^`A!#nzhJ%JB3^>zt+fa&7O?5pyETs0TCR zh=X&re{#LwJ#cB!;BdTuwES@Ad_U&mpdgK^Pu7=+eqDnL?_>i>cz$lMZ}BnUyZh7E zP>mw9!5w$1x?foiHP7HE{Qhl2l+LY-^3JyWqibAKQ(g^kS zE)~L5rwLI{XONFbqGf-}Ttk2ViMp_5Jo{1S6af z3&q7L<#_(vWSRa`CxO;V?43xQ-`KZVa`F*MmbnC2J6Zr3(y_6$-2z zb6K%gJFDdxA=h%vJKxVx9K?eWEIG4(YBC{-%2sRP zV)Kf48awMwyogw1&vtJXAvi*MwumiNz*PM{URn)q5{Hmq0#X)vu{Nr;fG^f4*I=44 z=a}dt6TWa*95AQ(;$QMn>$BxDK2fiG*pfi^>{C|4HSKy1VVJJO+0$~QDTa;=!}WJ+ zrf*49lVv$VkcLj)TgBzmezxh9xx1C+4^g`>*rPW$8<&(T;tN@QcXt4J)za`6*(bRM z+bv+_nXrRR=g}@8QPJOQ^&mD0`1yIH#FkIexzVkiyHpbb{9Uk?*R8_t{3=k;Sj?_# zt_|p#yMR|0ds1z4^Efgj&J$fErIFUV7)day8aIvlo}B|(Z1OJXS$e#=IQQbV&^`g` zeTSpVuNY_E#N4KpSZv2e2A~)SD;?5~e@6PBVo=J$!jw->$LOax{IhWX-(2%kt<@=;S*$R^ zzMg4V2?5T2`-1yYN!7s^U2mx3$L9=>G%@(R;UFkdjW2R3Dh*_W z8N|+cx5~yQ+9&r>rW3S$b!x09Ol($a_##-+$2a}!-G{FpsYe>?2j?wMTL<>JZ`l)$ zaQM_Cex=^jz@Hl)F_Eo5!XcOlIc5`3NZA64eD~HPUg-3N>4H+gjP?`b&0riRh8Q(A zDJYm=f02EAWDR_dEOGF<0HQf(+qj+~2vG)p0bC^sJu;BY6M|J!Eq!3k6<7VioRFPS z&8&ZHVHpqJIQ6vAxzcX4PB!Er6dM z%^EvE%1X50kNi6(;Ws+Ew2ClFdSs)zP&Da-`KjU8_C=q;{Dv{eBQhxWpMyU+0=Zeo z9FML7k?40}wlBo9h(89>C*zb7#?5+TM2cRs!I;;Hk}o0z2=!xX&;fvDyv0;UiWSzG z#{{U@5UoDS;C*qA9407%(Iu5@*t?n21N_8xqQ;t%rSO!lAFufEI*2Gp=@ZC)5HwJ0 zkCMD)7)dB1a2NC0RiraCQYHF_A)nkhYLX*=IbB9E8bAkFJ1bNVa99Mxs8dC16Yab8>D4j0e+ylswn53Dj+0ZmxmuF*NUVe+AJ zk$>~XC}uQF`98@3Z4<`#2(90hn+TenVf`qJ7uMM4v#*FT!d$Q4&$lmR)6+5_>2R>V z`5I|>jj~ULsWU8YVUp^`Q(;Js0cGE}r^?ZA3f@ai(|a z#BPacL5>7Dlo|k^LLlxIy@x_JdR4OEa4%i}nmbctXv&p9G}7$<5qnHQ+y%ws(=dV_ zX@q>VJ78Jj*_X&`cXb`CPM;b*LVnJu{@#$jF5F78#FcK-lx;E#blx%qxULMdB#GO$~mqys1lNjp)@X2^ut+?%oEZhBdXF zJyH!#5m&ea{Rw=QD1|7lxc6qsI5oR3?wnZScV;Mh#z$_4dduqUW!t*Ul8swVaz-?< zVCT9-1RK8k$?6ie$O|RJN{~ZPR2vsq>;t;m3^aX>-_rw?JfrL=i+@R0E%D+I)AOnp zNg&Ckp377dNx+qpphWOyZNM$R+LO90QLzSM7i)BqjjGH7;F`E&xUZnZ(VpSQakMU{ z@>f90d39HBCXB{YMWJfdIT%Sz^2<^sNGj~VIKNE(2A&@=)f_S(@};cI1{j4ljj9oN4a-qZ^V9NXQD~DXWc4>ZSav4`@H2A z&ybiD;vHzl%NIKQWspK5Gir4R%SZrLDYloKsNbhByRK*dFJ(4AYS>-#>JX#3MiLD0kck;p) zq)a(2l3n~<6^Hsyg_{=Vj^O%~%W!1|9p5Y>H#J$QhG@@Xb!mfxt_ljqZnb6>&9A=x z|B4TJ|1KYjY1!)8*b19z8R#kKS^p{{t|^&Geqn-rP1m47iHts2 z{8*mx4e5*+@lz0HU@Eh`uY$ex@uHjzP*C8HA#Lp zvc|Gv5Lg=VtcG;u;9&Lx0=NUCneMOk)Ew1bR*gLbg71p(^EEka83j-eoeV0~Vb!D9 zdmwBQI5)Li6wHHiTnAs*0?zQ8-rP+)MO<{{F2_MeM>N(^YOaRaFGN5?Anj}2FH^>7 z_3}IN<_9lkooot2N||4h_2Xg_&C~D05P&dIQKef2=ID>bhET$cK83RyzRkbg7kHAF zCFd4)KR?XQ@U8^PC_YXhGT>fbE}F`IPThTdt&ro`TnX1)G2B2bOD>ZlSUG)|SUe za>g)QhPcC{(>>COMyA0`AC9Vv%@O~-9b1Ig%ZOSWw?BX+${X-iNa!dll_;9H7U6oUNh0{w7wb9kT;j+TT`m*q=$OlQS&mmcvX z3Tu3RT5$u#W_v0lel{D@J}j6a`JOROm|Q9C+<@2iL@|W0o-vFTlJG-xqhl*dGc(D7 z6BEwr^`cWMHiNg2F=!?_;{qM!o+>*gk}IW}*&h~Ux~wJHi z&>{yheQZ|;vbQ0;7p*6O)vA5GpSn)2cL;bX?Pb~W%MoM~giEH1g(8J`c$seFcDvky zs}S9s-JN(IV)fy6(lI9Dh|q{q9+7(V!>zcDNaY(C%E>Q>E7OPqI8uU1N=^np%gKZ}`o31caL4*O3ZW5{ShGJv&I^v>FD;QS zBg>gelsWmKCCh0PZ7571lwP9?cF`TKZONkS*Wi5*`CvcbsB2!K%pVz`Mv4{9tg3{e zw5s7`S^5y$O$Ol^KGO&m$>r;Sm9uAl1khtOVgrUI!qQhgJw;H{n^xAKIo0?fg)FQ| z2zmGWZ8fV)j&{qNd~Qq&*3{wL7anBM3S~=^?D)uM{{eDuKk&T`h@vJx(e2_HU->V` zm;6Wkl}^YBopZ)IT4=X0v*;|x8szi}!KWY@eSX*82m^|&Ca!U^x1ZT{&5dVMRhsh0 z(gZKsT@~rx)XNE07=ZRrYNNp+XHlAgSTRBeIy3KObU8hvoo2}M9;BWUSu&cmrgEEX z>3lq+wEe2^=*}Lz`biKP;%_IB?;9ooap1(^FGIr+X?ZopE153ljzHaL$0oJeXQ*gq*o{0P>ai1 z6UsU*O4gQ4ry#w$B}>LWk7X6QPW)g2qMz43LgvkO(bEr~jf#RAP54Ht7G#=*%2gxf ztf@?Bx+ah&;COuMRy1DEZ!6oPMbM+dOpwyta%fYX9}`i#sQF2`vf`T{;1opmk~9*r zGeC15bn6qvDop0BEL?krR-=Ca-%lIMf{nfg5jBKJuVq{4F?fgTrSn~Q!V(Sws}0jV zeiI=}!bqYmVAJi+9+tg^QS<0bENhbEdcy!WrEmhy5~E8B2~5m&OUv;m7IZQ9T3lm| z7TLn*(LOQSEUav;BHr~j$w|b8B#;a9$DxItz94{kCz<$6+D=Rax5Zv!W5ilzKUpQQ zftfq%P(0$?v^~J_L&>v!^H>4!WR1qFyoC;j7BpFMKXtz+bA6`C%p)}#IOR&o3xH;U zU8UeglN~Ulrhv27&~rhIbrX23)LIggxV3^kv8`wiTwacpRunz-&|IZUB-d*ZN(3NJt&W*&44Oi^?ZdXpNH5mIWUxlTyK(*fewLy3LRaB z!g6)@Q%Ol&zuuzrpVi2G8rn#qh$VcEdba3&Z24rm!L#W{!80-e;71}GA-9iVuI>1$ zb<8wak}wn~m&i?b8qAg6P27Jm#hKJcr_{?KT9b?MSlR}#(iD;46dtZZRG!=fq zf|N?EbZ5^4xXBeR$*0ncFpzY8Uf;iPTkvipSLnez<7Gy~hMD&Waxi&o?tUk$O{CKF^$reZf>P}6s!IM%{4(OQg>w@Ag>Nfd zA6-er>;(ej*)f730#SKMIoa3McZGuGsYn%{c;t(9n~ zJjVczwEh#!C*5J?mB(`&FNj>!h9IO|htInaQ;kS&jn;t!u2N<>UmB)e$KB`Dh9jGlHK zqfcu_h0kY{3Sk~=#Eu|GmJ&ReZ9%9%e(Z2K$M*szf)$QQy8LLne3!6qpF|Kv1%%w? z5?lYYHO!BD@B;_|J{^NA&VedJB-^XE9_iPvvnYK@yy8z%fSt!;iR8mi53e z$VcS*3hK=lDHsU>s8^U z@nBsgGb=yjNoEJ+2vgNrz>cA6I2Vyi$dO$Oc{@zND@Q{ejA9way<}~`#o$CU>#Kf8 z^ai8!CU!DqNDb+_P%P^Xn}X4o+*una0T9Yx?WOi#Oo<7S0e1!KhVAv{=qe$8}oRykG!-ncs}GHKIsz5OLZycB{NZ=KE_{7jvMM}9~j$P4Ux9e-VB8Vh-mKf_h!g1eLNSuI5`;XX}j(L7=lc$t8c z3^LWa)-+d8HPA*-dt)Kapa4o7wU78HCnO=(VCn4VO|Uv}mrC#>l6VWNR$k8fYYTbu zN1H}j2Ks#KC9MOcy0~51qXQP1>)sTYY0ppDHGI{jlcce*H)&qa%x?Rr0VhWA! zHcz%@QP>3rp{zaotB`mE-5j}QDOu$b#cUqjtWRA!02V^o%!4kHXkW(3^L4`82DpT1 zvvGvvmRd7PW+siRD-hJoCnq{7aI%drKP`3Nv_?D=lOC2VYxe33k(~ zTvF-W{3Rp4#4Fsg(-~GwU%3aBoE3b z#oP5f?JYosE)W%?olwu1gyry%hSw}Q7V+7;(Q}u~(-P;70Tgiv&^O75&JvBwijJ1B zCv)cpHtx zAX?K%G8@84=0p8B632=7eDV+9?Nl*W$Y34Fj*w}l3TxGIIaWkZ)Fvn?<|7st*Jhek zEDZd(=OU@$I9BswF75V<)ZuUtCTg@^G9to za&?u<>epQt+ohAn;+s|ChY40t$ml&kiY7cMb$Uba*cIrBn;x|Gb6-b)~b}wEzw-- zl;X+c40I|We*LN_Chbxu(|!tfBh9+5hL@25^km)WP#kI5;rLb;!|KWLjzPe6l?*M%>(TUC1=+@Z^Vk1z5yV{O}sL_tjg%W{e(& z*aF*sGPzHAX2@8T;l>Y5C5+7yPOT(%(!48}zSGeV*kwI^ zNEwIFk}p1)Z*GXa@OYtW2ZY`tnA6aeJM%Yr>Zc8yhlw)Z^kxg*Rvs)< zo$3wnCznLrlU#Jai{2fSzv2>s{{xpuX<2KT>Dj*LM*d3p{x_?H$*f4c*W}&y$q2co z8Jh(@)8K+M1q$G4wXtW?kflxc*RPy>GW33KX)$Z!Gttecudx|Vr7P&dOOwWzZRF8* zW!@^Rd%zSC*U94Z_7&+V2H6v93Htm+O`zy=p3Q>GP)U**5L3s>Orp70N_=*yFxny{ z{hj`*PIRBQC6meM1hyQonPpivH%SHA3{G;-F@RJ>eVTGvuFPQi(wpFUa$9O0=8~ff z94^LH*Wh(93t&qhLBA$VCu5(pqdS-}68S}eH{jH$Vom^0fIxJRPEat2KGTVSimhF6X#kqv zq%pwpjuN~D&7Lo4zuYB{;mC^PbyL`m1BeS=$EG>=Zp+;akJ$_KPy2s@Qvo*x`0&97 z=&$TA@ptVnr)T!wFJ!AHYpJL6UuiKJJsS%hL)*qC8N-cfG3!_}^LY2v9Lit_E;xh(6?-965TS6GRPjPHFB z3mQhC7Q_j_We=|0b9|t`dDo%?AEg@)|Ba>F271 zVoMA_87Y6|9F=%jRo_Xc`!O<5W9H-fwKlLkLORfe*_j3BHM4Gd0V^a{4uRlBjPi+z z0(_hQ(JH(@P*B1?0HbtM0nK|6v|!*{FUm&u*^N7d)sf~#NHIU1|6z0rk?;dV_msM= z)JVA|rA=8V>0#iQs*I#D!nMT8%`iIPGLUw9G4MCsMGPqC8K>rKjs&x$F{$gxKp9EDwIR#&saz$jHZw4es z(dKwcKp4!n+61(~^{vM`EhcxAP@sUmi!oTuv1 zn>bxdxmw4#5N*F0RSVU2`y`y-e5{q}ZRi>ua8C7|AYiH1Imb1=C>e1$$v*NQQo#=^5OQM&5m7QdK(q2OCI?m7lJzseLCL|Lt~w`KCDkR`@i z9TvB|i0n*In|V%8~PvBk(VGMfDV>(I`UfP&~mMt+Xnl z_^zia9!v(xs`!p?whDil^DSZ1jY2A7r!V<>YTt18 z1sR<>E&`-9TrIdLRrXFnL*b|?*AVA#@vpXGb;F$eH`n)r1tZIk3~_*Y9o)yk`RoX| zZLsVrVN&qWc9exs<2LLCsbk++Too1iQ;G7R*Ffg$MUGI5i`ewxk?uYQE&1W#V#&f? z9l*PZm5@97rF14nKQOL~%+ae)2jP#%$8pELsjo&BLqms2@I-G$Sn(__xh3k7J~g&Z znD)h^`@eNz9so-3y7uSdxHX6Op!e;bU{WX17}sMbm8o4#ghGmRf3*EpWGAIvUr^cd zz#=oDa$u9}ttvHm*TX{sMu$rrGXz9F=zQmKNMy1dcpksCeXeQS)AD@KEq0MNRmX!r z-|jVl`G`@J+8r%8%XrB&H+;n*pkE&RNul|OOMA}|-pzQY`yd29L&`|G&y3eLXp}^k zq_5ed;G@)=Z1J~mtNQr(v1r0F#zBTet+Z@ej(dA02_d{(Z+>_7?zJ7D`3D3>G81Ki z1n)bmD>GH^uZPKmncN>&`fhvekAA#>tO9YtW4ynY~^ zUk@^~w?ORq7C0~3>i%pt(|M|Mu{&ucK){M>btsaK58i<>)#55hvk?|CB$_o)RMI5S zFH$c`aJp;%Sq^&Phj5h|3)VpKBAp>SP(7XOl&;6%}7aMj1g972_ypRI%IF ziYf>Aq_gE84hLr?{)G5Y)Wk4Sy33(M%taXa5Up(_9r{1e599D5Gzvn<1e6QLR^0KM zYl{I<@u#oNZc0?-HK@P?iv^rN%b-scWvy5M(4YEv_*(4gLR~=zJ-ch4sPB!-zIj8UAr41Sam2l@ zq_)!bhS-2IVJ<$4WQi%iK$iMuXnA>MkG1p(bhm4aZsg;5=We)$monKr7_cAqTbC*T zE;E962m;-X1inU7+AOdh(~_wH{Fqr-V~zcgP?5LdNx(sBvr-9Ow5pYR6K8RM!!{lm z3eHG0u+}MUHerPtI!F|ThzW<3cgE*C@vy#4Gn{*GD1O`00J+ga=|k&!3mhm^0kGxQ z<0acHb8(WyA^0=xCN5gx9~@nxD-cUn4)-jK!op!1l{^E8pcoTJ`1ycSRv!u41Z0W} zjLl{epb`i_k!Z9k57dAn<(M#3HwCzR>t%D|9I{SDTN#dmB7+`Wt&m`CnlqRoJ>7V< z=7G|@C*92MoEZiiRR{AVq1v0xj#P3Pl5nL<{JA^SrjK5f44eSF#wtf+^vxuhYBiTO zzN%v+OsOHnoeBXd1t132SESmNQ!neis3DGO|6ipd*YE>$X!Y+!Z1plLFwRy|{qkS4#U$s!aRWfjOqSgQ|{-NdJ z<4>6cG1lY1|DJLY|4W%H^2;3bhcTDm+}2u)PtV>+NAFi8Qk;yq$-7i;dm{5EE}Rr{ zp)kv0_y!b)B#X!IEVLG6u3TeZQxzNewm!6%<2*S{pgWk7#&j-DD6$>5N9)gGzKd<< zL3>9d43;#=7olCo1A6D`7g^^yF23UH^~UO!{!nhw9(+~31wI)=ILIZcNi6l{0Gemh z%k`}@&R=MV0#(KmOLn=PPK#xgw)w47r+?e7;!S^2t?<3|Fd2J9834Ad*aCHSAvkzF z6TwkdB!#a-e!Az2@w7H(EFRQ)QRWJsh0F194#%GNVwbgJHNutyH|Rb1T5z*Tk}>uT zski(5pb~NURQiKX#2!j{& zqm@azF?>eMURm8G5CHWZaKhNkF7JTLPI7oY0PoRr9yp9d&FH;s#GMcWd=tX$4NyC< zNkKVeB^9%YsVKtV-p)Aj4Jn$23dT=Yft{pEwf8I#E-VU`R zAE}RSbSR7|NMo?La(+ztSES)@E_HL{L$&yg0Ux*L1>Qqw9qGNkaoJMcddJtqZw_e{ zjNduo&6{bQCuAz`w?ABAaW<6VmZ#O6nP3i&uAIJljD@0aR9+f_X;{&63206uwQWoU ztfG}$_kN$A|JJP}uvg?BH@X6R`;XMEzM_78(Yps_{O&>TBJO{klpy#I9%N+xzMVGL zGX2BD7=L`sgZAH!dC>prC3Gyz^^FYdthN5UtsH>s-w&4j>@@uMqZ9o9pI7`JKj6>e z;?JA@?-W9v+(`KhHSF3xnE-_hqZzPEFQ7A313V5NTwoqT1cJ|Y(6b>)AeBxR(GSSb z(`x{4XfTo>co*#bRF)ri6&u);B?~^coe7nmpZl26e8Wqp-E8p;t21l6ik)47gGBfc z6f${5xJ0Wy4tF3)_#$&C(ltMAUO2V=)=uG0_y{XHmSHQ>;BL+L+YD z*(4x+N?odPWXuA zbSdm47eM@9viKtrUCr^AzGaGfG~VinP%1AuoQyTaT7rG@1Md3hBdUEuC#R71QdIy$ ztZ0d9%Y-jTwgsePq~i(1l2JM;zPxxReA)MPu&z$#ye_nRl`!>9r5X>OFub6BZ?Th5 zAG{oXr4X8|bH#uvsyCNkaJyGt?;#8kTO=mtoA6QzWhq9^r%I}$5>GyY4j+bV&HZnM6v~m=e!};i<@`Id z|8pnx?~cpy|LM4YxzGGphgFLbH%Fv_^}DkPCc>?PK&a9~`G{K;LLar*Z>HEFYTH7t z=tOA4h>S_1DAa@twy1pjRP^|KxxLS}Dw^bUNP#Lk)w4yzbSUbt*FDp7C<=+8mh0xx zEzfGri~tzh&e|Vhp)e0cKt~x8-eO^jCPN5Lfh&B1 zfwXcwaJ(SXvW2N2;c+HWWySC!KkID8r2R<`cko&Ac3Y}fiW&~yB$kdr1dJd{5$5{J zcFo~f;MKCx6E?TeGq*9aHL}+e(y}qM)zUWoQ%(3kTuJi3 zHQ{X}ukR5IEW4iwF9{MN758vrdosmP>F9XA@BG`kuCy#$fY$!l_|rX@YO$Ru?EnQ| zgL!Y#8EDtXiOn6dm`2x0GZX; za=MgCza7I`1k{|oR&R8Qv2T7AQ6AP9`P(`La%e^EqOJPWC=+RXM3RYfNu+y{ji`1I z17sZtx4SM8sOaI?%HG zBU$~4wjFl)-L_W0Tl)V*H2(zuXK%ZVmbtElnUt1|je~{tpXMn46>K*dwf7t)qT9Zr zBLzk!A}EiFpI8)fos2xmN^#yXZ;l+TW+(dMAgW#Mut$ZN2>i0KJN2FV8Oz=Fq7Vi; zWH)hRclS$}VfQeG+(JYs(dN>JOH3;87dD_KSa}YN2s#xKL#l{aQn~Je>*A zZsN=C9r{Zj&E)(aw!wt%mV15&>;gquL!`aZMkcapH({4movW|7CSST7A@@4Pq@{HX zY9?BEKSp&_oly7Ge`NuPsk!O_=IL9fgA}8S5+!_-1I2Pnu~QK!E!10mynb1He!kc( z`Dn4kAAav`GrL^jR0Wl(WM3ECQ*5fsZOEal30&$&dtP@)LMNv`QMv{0vX;76{Af1b zH8n+Q>_xU+sl=L!2g> zp!|Bb#6X5PnwGmC$)0gl%95C|t2jS(Ra*=bZ+Bk#DU`{X^XIXRoeG$zB`S9lC?pyi zIjW^Ed&Yeti&!}r2|~RrmGgsc%Sz{39>qr;m3}U7a3|4&4J^LbU=z?^c}i<-17Hga z9^)ttP40$SJM&N*-I+EOHlUGmKh$%i%C!4)Kc#Kl%q^+$4<%~BGGlF*H&(g*#tpCh zHQh_^eir+?Qu`xcWB&UK{g-KhA$;UL{RqoVuKiZ{3H;2UsA9quU?G1$Z8XK ztZ@yjE;l6;#A-hFzBWt@uK|3U{oyM-UOIhzpr+NgNf1n%WSm4wX#DqVS~D z0qVdok8CKeSQRammz^g?ok_Jkmb?$j-PR_W^7e9KvB6NT3=-2wo@!%yGZW{54FQ*; z?$S7do{UI6V;G zj}h6$g~*N6e$nudA(+6*HjM<1GPuW2Uhr>jfVtSSAi^R?7W<7%Ars*9Hri}uRPGyP z_b8;`BDn#S%-W&069G@{LPqS zU3YqkpNH&y=$(0E)7Cp|GML%Kv*`jsdJfPsx!dJjPjPs1YS~*0Do`8xnrFJTlO-#N z0Co_{E0>qZpaY`5>^xt1$1sI>5ZFP}uj<1Y9j)EQ$ulGyB%52Ye1xddm8aXHsao4k z#x8A1yLz+0nwHR5Rxq&QPFnl8)=`eTw=lC7M^t}mmhqe8D#m^S{roQahJP<7ynFEf zk_)uI>bN#K)<%}L+NKsdCVzbB_SY1{e`+iIU7q}p>;3DOUs)dV5gZag+)x50dx-He z!1#AaVSR;P!2*ZAYn$OlLfYiy}<7?W#G)X)PH`G9Omnq-;;I8%5Mb}Rl0a{!1vG|@IL-r zFjD+I5BR?r|Cd?hANIDPGJQ`GzAMCEF}a$|nE47bZ2K_@2#*)OXR4V`x^uDi6irn% zzLf1WrcqFMV%DwZaq^SsxSBh&Z>A8|sdglvtVQ^y1V{QCdWRLIu+=r=9*Q~J++GBiK+IMHtb3jPiMAQoOh*I;$5H{%yj-#7^An3 z$!+#SGlg5K-uzjgP%XhjOjMo^U%$L)E9sINHo8r3JtKd0b#+v7?cTR>1%)i?0KG>S z6lF_Kbvg!IWpv=;u#7|IkFy7*a{<`Snvpn{xY(P8wN2?H!ys z^#-y>iA5W|0D(Zq52~cI1=~L5#EhF+T=l+OX9T&an_SjsJ-{vGa&?(8foaI%-M!Jn zYflmWR`xgu#Z_kl9!_X$53yMbGfwXt<%yQ;ZYS9L_K+bvMLD1>SW8siom=$ ze{!fXQQvR0fU!X^iSE5F4iJ+beYJ*g`6Iy2lkNBmA9bP2?W5@0D(|%5H^au#LD{{< zrJi$%(1_a@j7XUYy0X5wu}}}A=Vof&Q}F8Z!4{WuM9rTszV8K)SC=) z*{*wNBRCQ!q1(Xfjj9PM;ETA^JncM9-nEW?U!9@|I@{&F=NMw~zNe7xL)o+{?X85O zNL~cm@RQ)~-%ouhB=-WNl1iNeuGi9T>Zf+wVM(YEuKiw(Gb&Gl<41}VP1WE;nzISo2e?TqwnM4_d&ht{+6g;(!T zXM9}F&^W2UHHItRe42<+GAdp#VsLtKlPM zemR$xnbTgfnytXiR)(I6-_TG1as8#FDYdrs4jZGTNd0|FtNA&{h!!p)C(^kvZbs3( z^UV!RX8j>+QG9jb6dfs*L}f=IG3S9yN4vZNI6i_{=Y3>F6x>uPf~v-qLJpC`-rn@I z-XM?6ClS_^=gqaYl+u5DKwC!fUTH=F{IdJeryFN&0Lx0Qt(DJ2_%1wBoz5%^a2GlL zn`OeN59A}!dE0}vGJ3fh76$CJUs5*th&`Jv<_L5>==7?#2$3wK2m)CxY* z@ujGNTavNMoR_X?D6O*1iBW40PTL*NPHp%6f+?{t>7BvyiSV{Xx!||Xdwy9X-V^pD z-ZGzD_vaPhq`IxIg;K1SA%68mhQ`Wt>e;pTymd|fY9WM3C7*wq-nCzt%KAN~& zXZ_ozx7Pc0%d16pi2%_c^4~oKo2>Zpux%>H5mWLJ*V!WT9}vEVL)KX(ewqTuTTZVh&nMI?Q zkPA&06GY;hKAG4NjVEC!ycCBZq!91{CUmfhiCkxU4p-V)PA*%3GG2fjR{q zupyL3$eG))bD&wGHZ<556&DwWgPyJ@U1|NRr<10P%wYMOM)vvqX4X~MU?gMEn!Y|B z)k~lCc1sSBZ*<4I{@OIdd&Q~a)IQ&ty<2ICORjpeP0#^ZMOY9=5Nym%EaYDye=aHZ zjR{GzcVl(@Zm>TR%cLLv`Q)j8=_3FAnNh!sS6e+x%Reky{F+GqWykpKI`5erBLnll zI&t%_A^YbOrNU?vs=ZD4wavLDijfqhjGgtZgpH%jxhhqJjJdsZq@9h-jn#u}IEAo@ zvoaxd_Xzj$h#L#y4_No`_YHRTi4Q>VkqP#^cX>%6Wsq!NJlXv=XTN_ReP)k%{vMq< z>z&%WGmF>87x;-%kL|a;h^Ol;x79nMuJ7Z|*TXMItlw^1&r$2W_fC)U=a*j*i2w1% zxh<`w^|^&*B;2j*y~{PU&5ONdERC(K^xe}1-K~VBjJYMWt&OA9wI!_G1>D1jiZ{wn=OpBAtE6r2Fb@I%7CkAuVlRBl-xz^@ghHwj>%6_m(R*ANPAX_ zM=kDg)$>p!RAc>yP3`#J1midAB6_I6yyU*}7?89CQ2`e2vNP9sc)Tf3jh`HDF`!d1-sdZq z9bk;$E+rm62p+IJtf@iayn3N zgiVLG-%W1&i9g91yL6vnEN2K-3X~@wBGPY%+R}TEy}H2k*5x=NQY;Hou$nXeEi>N! z%LmYH;ZaDRo{vzV!}i+fJjD`Z6+hanKYLcYoiyasxTHj?nl*H1gVM*$&ja z!s#Zd|D7dQw!u-hM!VqPM02iyr9>aAXikHl*0-z_9H7{T9TXM=XLFbpIhtnFtZa*u zi)=5y36xyyQL}NjGlwUwFlBjX-LD7STL`IA?wO$51SDt7 zyPHv?1zk{V>!UI`GC_%h@+2XRQRPCSyvY~s2=}CoLBHS#0p4LLZbShMYVMr8j6&5X z8b#(@%OC#Qq?9US0wys8@dc^FxCyR0hI9in9|S!ffPi9HstC(rCezpJ z5tq;^dB>r6Enm=8vRVSQU*3X=s_4*$5O8!zz{HIFoFFTx{Y*#n}4OH8N zqS7)DPMw<7u4gTeTxoByVN2j~E`sEMF=3E)!P@Qu^M&v;pu}`FBuefS@pe_lhKs%X z%e~V_jr!vj%^KcCgX(^mMLQS-ngbn8d`b2!zouR0E5vH9Kpr}`g?m)^NldKq1HWf% z-EQxs(a?g(PbK9oSll69TpRlv3rf?xo2x>@I4&2{)aI?k*UHX;Pdm zBxt~FZRc1d?Akhm0n_l2oqh*y?xI2AgiQD|dz2yy5o94vu%1 zcVkQ@mQuYNvtrvx#b(7f zwf48qJ^P%!*IM^WYiaMFv^m?GF~-v$^xj9Hh%t2Lm{*LxXuLD|i-rNH3WrsGln>*N zawpOj?2B+Ybg*g z0ksqDcmN_;rbG>p+h2&p)y`#(M$Xpb&96lyF}{g6C6-$qtjK!n_o}fcf6GkB6*PLp zFUd5AQ3?wjP7v6Xk*dUCN~hp<&{$*$tH(5niB&X&z_(x~XB6FbJ`TzWorZv($+AdBNs4 zOkZtV&~tniJy3sI^!(M#|KC9OCxHBCAH9F(RE5f)!IA?`YfOip4@De>1);f70SN$M zUJ4VB9E$UJV;!10(yGa`gvdw*?8ZoIU8b#jI#+b?CjS`tf+S>w@epegD){~JkT*Fx zb04fIbtmv^>8(7T=~u?8l+v_7_F~>~^-Q}1x?pA(30nzkeulwAQ!Ua|)AI|!SHM|v z^|8k1g35GVeAS>L6;-z+=Z@M7r!ZTSQ*G73)gT+U0Q#&RUf!EN;VH1s zZrfcH8}gUW$3%9iC5!SJNMjT|TNY|`N>84ZvOhry2zMFdos=)iTBZk36L-Q3zn+p+ z$H4$N=&$6O@(I;Eu}wJptvv8>EiOw) zCUOK=6UQZP|Av0_Szfzf@3=A7XK~e2VyjoVU0b#u#^X(;A1RCDhu9INh1?|27Be-| zZQ*irajS)`=H_Yb z4EiyU(k@$xx$DIGLdN{oCI_Q!IfP09&pM3QdaIFRg;^KyAlj$lo z_nGaurw>P8ffQm_ax34XJpw_|XF(-xY0Q?s(0mbiWirpxo47sxj?bn4;BUjefr>#I zVH_NRTCcmpD&D_nfaKQaRpIXmE%U%8z}eMfJMp+X98^vA62mzBN)F);iyxKtOLEn3 zlz-qXMNmn-?QB zom@=DxIslB_E_X5150X*pdf8%3`A-J5K{arMj9bfnh>Z|O832`o-cCX_D-P{@0254 z1K5BfQ2C6`q?Pc7L^sBfsyaCDPO~A#CP}gP4g?NKvlIsKlq~%HBqo}HP5*`ej+DU} zylD0e^CXoEHH!O9m;M341e@nuRL~4r-s<$4A{s~0Dz;ec&mcNZBt#_9^B1)_}@cfl6BBC7C@_J7Y*P|zAW!Q>b2#G?aswd0PF|J%UW$v`P_Pq z1?i2Ndoa+T;sMU$Xe$RYhJDgFM+%yfjD={hM|6o^PF+!qLR$Ik$Adw1gUWJi!r4IN z63Xd!fFSB0`>i^MSSQuI?`JQ4#~fIlbw&oR)>b)}rMzRezf>>gHi&JO-K;1pFeFtP zo%ERMEu5d?BH5MrGtK&$(n@Ozw0*9Syj>IPQ3J9oU>&L=DVB{IX|o@LR-{9ixN{U> zG=bD<*{N1yA@f?7J;HF*X}s_?Z};t;TniR>Xr`(%hs&St8Pl4QuQ5 zz66!~!O}BmQNicY6RV+bs(S<|LU(;_()K4IUeZkTi%QcuRC{j0s`&Pv*jj8saz+6@ zcm52Qr0d5JrQ{~#S?tAD5M7>pUPjgeb8i1*mJF8a0mSV9$ZncsI)2QU<7wQ~nAJLceLX%@=n%x(KDY>yM8O*4nD@UUKk#n1=4eZv8p;uYibx1)RMhL5y3db>Ryv?m+C#! z_Bh`MFCqTU%oe`Me5J0DztC%4qRPv9828Se3Jy8h!v0F zKlEs1ZflW2To$xv*G>%ThviUT)BVLFWo$cv3=OwC3yKb(Mf}#(mK$)RU{uLwXM;R0d-PYOY&mXDvuFn}j2c&16IDP{qFD7H<7T z62K$irXdZZxRs`>D(NFXfST2I^bK`?x$hOzS(4&fN44EQN*oiG=(;(0$-gfN3U<&# zEsYH(vOFjHYX`rVJbpXiDu9FsZQBb9CyrQLkKHBs#pF=;A3>E1>`{2`Gf`vzFZupw zh5NsZ8UM;@`nT)Uzn?k%Q=;?x^FNQbsQ#jG{>StGFGc3xnJNG7+Wbxe|L>cAb0q$! z#U%6p-Pp&k^IgzI%~xg z`*QF+q7h`Ay`RHiGwvk~jq~N8Qf-icq*_)Dc8vN$B_M7v#uV%?Uz!l0nEGM9clO|& z<@EfpI8@*D$(mL5eE)-%mcAw(|C=B&`(OQw-^=_zX=#7eI{U{mm>5|b+5ZQ=@84U5 zzf$o2>wARFEI%n|jz%W-W{z$&-&_rhZ2zGD{-=J@e_s`=xDDw(I;7$1rD8Tc5>W!8 zO$6ql&^DmFShA%JXS4-xDl7hw>mMCgv7W$d3~17{FQ@DuxA!0KuVH%Q5x~sBt+VKq z5D77q_$5?O>=dn|WVqULcLk7DY!faW+Nk4=Vl-ZHw1E(e{s6CDM@HCQ#i0LR|U%k{fOzXD5}C) zR&9;)2*?;D5>IHDAN*475_yV6$(w&|5lnT4YAX%g;&al7`3lG zWM|Gl9oeo3_u#9){m7`UAZLdaS2CAT{B_6ho&mRr6^A5@lDlNw?6Ke}C5OIY;JXJ= z*D|NB0P8(mBV@JTwf#MD%-E!=%GcN^X0Gc4c&f9&R2VXG%UndjVc{K z8x}jCb>RQ)CH~LD_*Z_ff3^=4e}|ku+XAcqpEbz2@|yLUFq~IXmwpdi3{pzK9+KD& z%Qs>X8X(o=F!L}0l(0mm1T76TbS?6sD?uA;z}AukfiSGkfhyJA-#TS~VD)+gQ-n5$s`ATggQ5#&Ar9;dd)TT} zJJBUUtYxeTQcn)_J^SNM1FQc#7v%adkIIYqrEYpMdfK5!Zw2cEb(Q}=9~Ni znLb^%@z%U?q`!_S<=)?`ASHZm*S?kMIK0hgr+ms~M~6T~N96gCxgbe9ODr-c7H~gf zIZqY9J21v}qpECy#;4*CoQlwbYP9oMi;jH}hQd2Q)=Jc8ro6 zy^BVAlUtu-yH?c5!)dE!98GXin|s?`U^Zymf8b zrK>m*?vut!9A9>6SGPWFVC(`6A!4|4lI0%(3kktu*4KY9XWLn`Y9&YNkb15#rX*o+ z&mMkw2m=7Cy3K)y?DyEfe?pMfj_ue`jpXP}V(?lF>l9K$Yw_IN|&8aB-j z>SF9+UIwX@i|`ROe-TK**-kgzxkC^nz+s;coL*?PnG!@~8OB$~8h9E-*d zau_l)d<9g-W^;ylOrb-cQF9D{6072dqd}8L;4k6LoSLr(kZrp{n8FV8O5$Z+>~jwm z?2%1r!ZEILHONImLVBEx3`P%oNZRzZqoUGV683q( zni`>c5acv7+zV-=!M=&sk1VvINkS&wDzR|}fvWKfkLkE^eBaF-ds<_aE&3>`E9G-b z3G-h`S6zi@ob0>!k1aK>Xdp;wA=>rTff^fd4Bi`ggg+UvcsOHGGNwz76B=q1Qh{msOn9 z@5X=N(hNC>Ju3b5$(JAtrjShMnF4^b3O_30-4T6pdqw-R0WkME8utRtT*0SeKDer1 zx4(XEl6Jm7ar#}dfZIZzlQ8MC6UD#)%MW9k
y_QXnF>z%g$~P;sWdazy9kM=|A{ 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 MuM4{6+eZBB7aYmRfB*mh diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 0440af3..6c09232 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -335,7 +335,7 @@ }, "FunctionAppUrl": { "type": "string", - "defaultValue": "[if(and(contains(deployment().properties, 'templateLink'), not(empty(deployment().properties.templateLink.uri))), uri(replace(split(deployment().properties.templateLink.uri, '?')[0], 'DeployAVDSessionHostReplacer.json', ''), '../../FunctionApp/FunctionApp.zip'), 'https://raw.githubusercontent.com/stefze/AVDSessionHostReplacer/migration/FunctionApp/FunctionApp.zip')]", + "defaultValue": "[if(and(contains(deployment().properties, 'templateLink'), not(empty(deployment().properties.templateLink.uri))), uri(replace(split(deployment().properties.templateLink.uri, '?')[0], 'DeployAVDSessionHostReplacer.json', ''), '../../FunctionApp/FunctionApp.zip'), 'https://raw.githubusercontent.com/stefze/AVDSessionHostReplacer/migration/deploy/zip/FunctionApp.zip')]", "metadata": { "description": "Required: No | URL of the FunctionApp.zip package. By default, this is derived from the current template URL so it follows the deployed repo branch automatically." } @@ -632,7 +632,7 @@ }, "FunctionAppUrl": { "type": "string", - "defaultValue": "https://raw.githubusercontent.com/stefze/AVDSessionHostReplacer/migration/FunctionApp/FunctionApp.zip", + "defaultValue": "https://raw.githubusercontent.com/stefze/AVDSessionHostReplacer/migration/deploy/zip/FunctionApp.zip", "metadata": { "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." } diff --git a/deploy/custom/FunctionApp_Template.json b/deploy/custom/FunctionApp_Template.json deleted file mode 100644 index 812626f..0000000 --- a/deploy/custom/FunctionApp_Template.json +++ /dev/null @@ -1,1508 +0,0 @@ -{ - "$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": "16749693425937561368" - } - }, - "parameters": { - "Location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Required: No | Region of the Function App. This does not need to be the same as the location of the Azure Virtual Desktop Host Pool. | Default: Location of the resource group." - } - }, - "EnableMonitoring": { - "type": "bool", - "defaultValue": true - }, - "UseExistingLAW": { - "type": "bool", - "defaultValue": false - }, - "LogAnalyticsWorkspaceId": { - "type": "string", - "defaultValue": "none", - "metadata": { - "description": "Required: Yes | Name of the Log Analytics Workspace used by the Function App Insights." - } - }, - "UseStandardTemplate": { - "type": "bool", - "defaultValue": true - }, - "SessionHostsRegion": { - "type": "string", - "defaultValue": "" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "SessionHostSize": { - "type": "string", - "defaultValue": "" - }, - "AcceleratedNetworking": { - "type": "bool", - "defaultValue": false - }, - "SessionHostDiskType": { - "type": "string", - "defaultValue": "Premium_LRS", - "allowedValues": [ - "Standard_LRS", - "StandardSSD_LRS", - "Premium_LRS" - ] - }, - "DiskSizeGB": { - "type": "int", - "defaultValue": 64, - "metadata": { - "description": "OS disk size in GB" - } - }, - "DnsServers": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "List of DNS server IP addresses to set on the NIC" - } - }, - "MarketPlaceOrCustomImage": { - "type": "string", - "defaultValue": "Marketplace", - "allowedValues": [ - "Marketplace", - "Gallery" - ] - }, - "MarketPlaceImage": { - "type": "string", - "defaultValue": "win11-23h2-avd-m365", - "allowedValues": [ - "2022-datacenter-smalldisk-g2", - "win10-21h2-avd", - "2022-datacenter-core-g2", - "win10-22h2-avd-m365-g2", - "win11-21h2-avd", - "win10-21h2-avd-m365", - "win11-22h2-avd-m365", - "2022-datacenter-core-smalldisk-g2", - "win11-21h2-avd-m365", - "win11-23h2-avd", - "win11-25h2-avd-m365", - "win11-22h2-avd", - "2022-datacenter-g2", - "win10-22h2-avd-g2" - ] - }, - "GalleryImageId": { - "type": "string", - "defaultValue": "" - }, - "SecurityType": { - "type": "string", - "defaultValue": "TrustedLaunch", - "allowedValues": [ - "Standard", - "TrustedLaunch", - "ConfidentialVM" - ] - }, - "SecureBootEnabled": { - "type": "bool", - "defaultValue": true - }, - "TpmEnabled": { - "type": "bool", - "defaultValue": true - }, - "SubnetId": { - "type": "string", - "defaultValue": "" - }, - "IdentityServiceProvider": { - "type": "string", - "defaultValue": "EntraID", - "allowedValues": [ - "EntraID", - "ActiveDirectory", - "EntraDS" - ] - }, - "IntuneEnrollment": { - "type": "bool", - "defaultValue": false - }, - "ADDomainName": { - "type": "string", - "defaultValue": "" - }, - "ADDomainJoinUserName": { - "type": "string", - "defaultValue": "" - }, - "ADJoinUserPassword": { - "type": "securestring", - "defaultValue": "" - }, - "ADOUPath": { - "type": "string", - "defaultValue": "" - }, - "LocalAdminUsername": { - "type": "string", - "defaultValue": "" - }, - "CustomTemplateSpecResourceId": { - "type": "string", - "defaultValue": "" - }, - "VMNamesTemplateParameterName": { - "type": "string", - "defaultValue": "VMNames", - "metadata": { - "description": "Required: No | The name of the parameter in the template that specifies the VM Names array." - } - }, - "CustomTemplateSpecParameters": { - "type": "object", - "defaultValue": {} - }, - "HostPoolResourceGroupName": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "Required: No | Name of the resource group containing the Azure Virtual Desktop Host Pool. | Default: The resource group of the Function App." - } - }, - "HostPoolName": { - "type": "string", - "metadata": { - "description": "Required: Yes | Name of the Azure Virtual Desktop Host Pool." - } - }, - "SessionHostNamePrefix": { - "type": "string", - "maxLength": 12, - "metadata": { - "description": "Required: Yes | Prefix used for the name of the session hosts." - } - }, - "SessionHostNameSeparator": { - "type": "string", - "defaultValue": "-", - "maxLength": 1, - "metadata": { - "description": "Required: NO | Separator between prefix and number. | Default: -" - } - }, - "TargetSessionHostCount": { - "type": "int", - "minValue": 0, - "metadata": { - "description": "Required: Yes | Number of session hosts to maintain in the host pool." - } - }, - "TargetSessionHostBuffer": { - "type": "int", - "minValue": 1, - "metadata": { - "description": "Required: Yes | The maximum number of session hosts to add during a replacement process" - } - }, - "UseGovDodGraph": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Required: No | Switches to using the US Governmment DoD graph endpoints for the Function App. | Default: false" - } - }, - "UseUserAssignedManagedIdentity": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Required: No | Resource Id of the User Assigned Managed Identity to use for the Function App. | Default: System Identity" - } - }, - "UserAssignedManagedIdentityResourceId": { - "type": "string", - "defaultValue": "" - }, - "TagIncludeInAutomation": { - "type": "string", - "defaultValue": "IncludeInAutoReplace", - "metadata": { - "description": "Required: No | Tag name used to indicate that a session host should be included in the automatic replacement process. | Default: IncludeInAutoReplace." - } - }, - "TagDeployTimestamp": { - "type": "string", - "defaultValue": "AutoReplaceDeployTimestamp", - "metadata": { - "description": "Required: No | Tag name used to indicate the timestamp of the last deployment of a session host. | Default: AutoReplaceDeployTimestamp." - } - }, - "TagPendingDrainTimestamp": { - "type": "string", - "defaultValue": "AutoReplacePendingDrainTimestamp", - "metadata": { - "description": "Required: No | Tag name used to indicate drain timestamp of session host pending deletion. | Default: AutoReplacePendingDrainTimestamp." - } - }, - "TagScalingPlanExclusionTag": { - "type": "string", - "defaultValue": "ScalingPlanExclusion", - "metadata": { - "description": "Required: No | Tag name used to exclude session host from Scaling Plan activities. | Default: ScalingPlanExclusion" - } - }, - "TargetVMAgeDays": { - "type": "int", - "defaultValue": 45, - "metadata": { - "description": "Required: No | Target age of session hosts in days. | Default: 45 days." - } - }, - "DrainGracePeriodHours": { - "type": "int", - "defaultValue": 24, - "metadata": { - "description": "Required: No | Grace period in hours for session hosts to drain before deletion. | Default: 24 hours." - } - }, - "FixSessionHostTags": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Required: No | If true, will apply tags for Include In Auto Replace and Deployment Timestamp to existing session hosts. This will not enable automatic deletion of existing session hosts. | Default: True." - } - }, - "IncludePreExistingSessionHosts": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Required: No | When enabled, the Session Host Replacer will automatically consider pre-existing VMs for replacement if they meet the criteria | Default: False." - } - }, - "SHRDeploymentPrefix": { - "type": "string", - "defaultValue": "AVDSessionHostReplacer", - "metadata": { - "description": "Required: No | Prefix used for the deployment name of the session hosts. | Default: AVDSessionHostReplacer" - } - }, - "SessionHostInstanceNumberPadding": { - "type": "int", - "defaultValue": 2, - "metadata": { - "description": "Required: No | Number of digits to use for the instance number of the session hosts (eg. AVDVM-01). | Default: 2" - } - }, - "ManagedSessionHostMinSuffix": { - "type": "int", - "defaultValue": 1025, - "metadata": { - "description": "Required: No | Minimum numeric suffix for managed session hosts. Hosts below this suffix are ignored when calculating how many new hosts to deploy. | Default: 1025" - } - }, - "ReplaceSessionHostOnNewImageVersion": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Required: No | If true, will replace session hosts when a new image version is detected. | Default: true" - } - }, - "ReplaceSessionHostOnNewImageVersionDelayDays": { - "type": "int", - "defaultValue": 0, - "metadata": { - "description": "Required: No | Delay in days before replacing session hosts when a new image version is detected. | Default: 0 (no delay)." - } - }, - "SessionHostResourceGroupName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Required: No | Leave this empty to deploy to same resource group as the host pool." - } - }, - "FunctionAppUrl": { - "type": "string", - "defaultValue": "[if(and(contains(deployment().properties, 'templateLink'), not(empty(deployment().properties.templateLink.uri))), uri(replace(split(deployment().properties.templateLink.uri, '?')[0], 'FunctionApp_Template.json', ''), '../../FunctionApp/FunctionApp.zip'), 'https://github.com/WillyMoselhy/AVDReplacementPlans/releases/download/v0.1.5/FunctionApp.zip')]", - "metadata": { - "description": "Required: No | URL of the FunctionApp.zip package. By default, this is derived from the current template URL so it follows the deployed repo branch automatically." - } - }, - "TimeStamp": { - "type": "string", - "defaultValue": "[utcNow()]" - } - }, - "variables": { - "varMarketPlaceImages": { - "win10-21h2-avd": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "windows-10", - "sku": "win10-21h2-avd" - }, - "win10-21h2-avd-g2": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "windows-10", - "sku": "win10-21h2-avd-g2" - }, - "win10-21h2-avd-m365": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "office-365", - "sku": "win10-21h2-avd-m365" - }, - "win10-21h2-avd-m365-g2": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "office-365", - "sku": "win10-21h2-avd-m365-g2" - }, - "win10-22h2-avd": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "windows-10", - "sku": "win10-22h2-avd" - }, - "win10-22h2-avd-g2": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "windows-10", - "sku": "win10-22h2-avd-g2" - }, - "win10-22h2-avd-m365": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "office-365", - "sku": "win10-22h2-avd-m365" - }, - "win10-22h2-avd-m365-g2": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "office-365", - "sku": "win10-22h2-avd-m365-g2" - }, - "win11-21h2-avd": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "windows-11", - "sku": "win11-21h2-avd" - }, - "win11-21h2-avd-m365": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "office-365", - "sku": "win11-21h2-avd-m365" - }, - "win11-22h2-avd": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "windows-11", - "sku": "win11-22h2-avd" - }, - "win11-22h2-avd-m365": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "office-365", - "sku": "win11-22h2-avd-m365" - }, - "win11-23h2-avd": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "windows-11", - "sku": "win11-23h2-avd" - }, - "win11-25h2-avd-m365": { - "publisher": "MicrosoftWindowsDesktop", - "offer": "office-365", - "sku": "win11-25h2-avd-m365" - } - }, - "varImageReference": "[if(equals(parameters('MarketPlaceOrCustomImage'), 'Marketplace'), createObject('publisher', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].publisher, 'offer', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].offer, 'sku', variables('varMarketPlaceImages')[parameters('MarketPlaceImage')].sku, 'version', 'latest'), createObject('Id', parameters('GalleryImageId')))]", - "varSecurityProfile": "[if(equals(parameters('SecurityType'), 'Standard'), null(), createObject('securityType', parameters('SecurityType'), 'uefiSettings', createObject('secureBootEnabled', parameters('SecureBootEnabled'), 'vTpmEnabled', parameters('TpmEnabled'))))]", - "varDomainJoinObject": "[if(equals(parameters('IdentityServiceProvider'), 'EntraID'), createObject('DomainType', 'EntraID', 'IntuneJoin', parameters('IntuneEnrollment')), createObject('DomainType', 'ActiveDirectory', 'DomainName', parameters('ADDomainName'), 'DomainJoinUserName', parameters('ADDomainJoinUserName'), 'ADOUPath', parameters('ADOUPath')))]", - "varAzureEnvironments": [ - "AzureCloud", - "AzureUSGovernment", - "AzureChinaCloud" - ], - "splitParts": "[split(parameters('HostPoolResourceGroupName'), '-')]", - "rgpattern": "[concat('-',variables('splitParts')[2], '-', variables('splitParts')[3], '-', variables('splitParts')[4], '-', variables('splitParts')[5])]", - "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", - "varGraphEnvironmentNames": "[if(parameters('UseGovDodGraph'), createArray('Global', 'USGovDod', 'China'), createArray('Global', 'USGov', 'China'))]", - "varGraphEnvironmentName": "[variables('varGraphEnvironmentNames')[indexOf(variables('varAzureEnvironments'), environment().name)]]", - "varFunctionAppName": "[concat('AVDReplacer', variables('rgpattern'))]", - "varFunctionAppIdentity": "[if(parameters('UseUserAssignedManagedIdentity'), createObject('type', 'UserAssigned', 'userAssignedIdentities', createObject(format('{0}', parameters('UserAssignedManagedIdentityResourceId')), createObject())), createObject('type', 'SystemAssigned'))]" - }, - "resources": [ - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "deployFunctionApp", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "Location": { - "value": "[parameters('Location')]" - }, - "FunctionAppName": { - "value": "[variables('varFunctionAppName')]" - }, - "FunctionAppUrl": { - "value": "[parameters('FunctionAppUrl')]" - }, - "EnableMonitoring": { - "value": "[parameters('EnableMonitoring')]" - }, - "UseExistingLAW": { - "value": "[parameters('UseExistingLAW')]" - }, - "LogAnalyticsWorkspaceId": { - "value": "[parameters('LogAnalyticsWorkspaceId')]" - }, - "ReplacementPlanSettings": { - "value": [ - { - "name": "_HostPoolResourceGroupName", - "value": "[parameters('HostPoolResourceGroupName')]" - }, - { - "name": "_HostPoolName", - "value": "[parameters('HostPoolName')]" - }, - { - "name": "_TargetSessionHostCount", - "value": "[parameters('TargetSessionHostCount')]" - }, - { - "name": "_TargetSessionHostBuffer", - "value": "[parameters('TargetSessionHostBuffer')]" - }, - { - "name": "_SessionHostNamePrefix", - "value": "[parameters('SessionHostNamePrefix')]" - }, - { - "name": "_SessionHostNameSeparator", - "value": "[parameters('SessionHostNameSeparator')]" - }, - { - "name": "_SessionHostTemplate", - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate'), '2022-09-01').outputs.TemplateSpecResourceId.value]" - }, - { - "name": "_SessionHostParameters", - "value": "[string(createObject('Location', parameters('SessionHostsRegion'), 'AvailabilityZones', parameters('AvailabilityZones'), 'VMSize', parameters('SessionHostSize'), 'AcceleratedNetworking', parameters('AcceleratedNetworking'), 'DiskType', parameters('SessionHostDiskType'), 'DiskSizeGB', parameters('DiskSizeGB'), 'DnsServers', parameters('DnsServers'), 'ImageReference', variables('varImageReference'), 'SecurityProfile', variables('varSecurityProfile'), 'SubnetId', parameters('SubnetId'), 'DomainJoinObject', variables('varDomainJoinObject'), 'DomainJoinPassword', if(equals(parameters('IdentityServiceProvider'), 'EntraID'), null(), createObject('reference', createObject('keyVault', createObject('id', reference(resourceId('Microsoft.Resources/deployments', 'deployKeyVault'), '2022-09-01').outputs.keyVaultId.value), 'secretName', 'DomainJoinPassword'))), 'AdminUsername', parameters('LocalAdminUsername'), 'VMNamePrefixLength', add(length(parameters('SessionHostNamePrefix')), length(parameters('SessionHostNameSeparator'))), 'tags', createObject()))]" - }, - { - "name": "_SubscriptionId", - "value": "[subscription().subscriptionId]" - }, - { - "name": "_RemoveEntraDevice", - "value": "[equals(parameters('IdentityServiceProvider'), 'EntraID')]" - }, - { - "name": "_RemoveIntuneDevice", - "value": "[parameters('IntuneEnrollment')]" - }, - { - "name": "_ClientId", - "value": "[if(parameters('UseUserAssignedManagedIdentity'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').clientId, '')]" - }, - { - "name": "_TenantId", - "value": "[if(parameters('UseUserAssignedManagedIdentity'), reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[2], split(parameters('UserAssignedManagedIdentityResourceId'), '/')[4]), 'Microsoft.ManagedIdentity/userAssignedIdentities', split(parameters('UserAssignedManagedIdentityResourceId'), '/')[8]), '2023-01-31').tenantId, '')]" - }, - { - "name": "_GraphEnvironmentName", - "value": "[variables('varGraphEnvironmentName')]" - }, - { - "name": "_AzureEnvironmentName", - "value": "[environment().name]" - }, - { - "name": "_Tag_IncludeInAutomation", - "value": "[parameters('TagIncludeInAutomation')]" - }, - { - "name": "_Tag_DeployTimestamp", - "value": "[parameters('TagDeployTimestamp')]" - }, - { - "name": "_Tag_PendingDrainTimestamp", - "value": "[parameters('TagPendingDrainTimestamp')]" - }, - { - "name": "_Tag_ScalingPlanExclusionTag", - "value": "[parameters('TagScalingPlanExclusionTag')]" - }, - { - "name": "_TargetVMAgeDays", - "value": "[parameters('TargetVMAgeDays')]" - }, - { - "name": "_DrainGracePeriodHours", - "value": "[parameters('DrainGracePeriodHours')]" - }, - { - "name": "_FixSessionHostTags", - "value": "[parameters('FixSessionHostTags')]" - }, - { - "name": "_IncludePreExistingSessionHosts", - "value": "[parameters('IncludePreExistingSessionHosts')]" - }, - { - "name": "_SHRDeploymentPrefix", - "value": "[parameters('SHRDeploymentPrefix')]" - }, - { - "name": "_SessionHostInstanceNumberPadding", - "value": "[parameters('SessionHostInstanceNumberPadding')]" - }, - { - "name": "_ManagedSessionHostMinSuffix", - "value": "[parameters('ManagedSessionHostMinSuffix')]" - }, - { - "name": "_ReplaceSessionHostOnNewImageVersion", - "value": "[parameters('ReplaceSessionHostOnNewImageVersion')]" - }, - { - "name": "_ReplaceSessionHostOnNewImageVersionDelayDays", - "value": "[parameters('ReplaceSessionHostOnNewImageVersionDelayDays')]" - }, - { - "name": "_VMNamesTemplateParameterName", - "value": "[parameters('VMNamesTemplateParameterName')]" - }, - { - "name": "_SessionHostResourceGroupName", - "value": "[parameters('SessionHostResourceGroupName')]" - } - ] - }, - "FunctionAppIdentity": { - "value": "[variables('varFunctionAppIdentity')]" - } - }, - "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": "3734734378229800954" - } - }, - "parameters": { - "Location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Required: No | Region of the Function App. This does not need to be the same as the location of the Azure Virtual Desktop Host Pool. | Default: Location of the resource group." - } - }, - "EnableMonitoring": { - "type": "bool", - "defaultValue": true - }, - "UseExistingLAW": { - "type": "bool", - "defaultValue": false - }, - "LogAnalyticsWorkspaceId": { - "type": "string", - "defaultValue": "none", - "metadata": { - "description": "Required: Yes | Name of the Log Analytics Workspace used by the Function App Insights." - } - }, - "FunctionAppName": { - "type": "string", - "metadata": { - "description": "Required: Yes | Name of the Function App." - } - }, - "FunctionAppUrl": { - "type": "string", - "defaultValue": "https://github.com/WillyMoselhy/AVDReplacementPlans/releases/download/v0.1.5/FunctionApp.zip", - "metadata": { - "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." - } - }, - "AppPlanName": { - "type": "string", - "defaultValue": "Y1", - "metadata": { - "description": "Required: No | App Service Plan Name | Default: Y1 for consumption based plan" - } - }, - "AppPlanTier": { - "type": "string", - "defaultValue": "Dynamic", - "metadata": { - "description": "Required: No | App Service Plan Tier | Default: Dynamic for consumption based plan" - } - }, - "ReplacementPlanSettings": { - "type": "array", - "metadata": { - "description": "Required: Yes | The following settings are mandatory. Rest are optional.\n[\n {\n name: '_HostPoolResourceGroupName'\n value: 'string'\n }\n {\n name: '_HostPoolName'\n value: 'string'\n }\n {\n name: '_RemoveEntraDevice'\n value: 'bool'\n }\n {\n name: '_SessionHostTemplate'\n value: 'string'\n }\n {\n name: '_SessionHostParameters'\n value: 'hashtable'\n }\n {\n name: '_SubscriptionId'\n value: 'string'\n }\n {\n name: '_TargetSessionHostCount'\n value: 'int'\n }\n {\n name: '_SessionHostNamePrefix'\n value: 'string'\n }\n]" - } - }, - "FunctionAppIdentity": { - "type": "object", - "defaultValue": { - "type": "SystemAssigned" - } - } - }, - "variables": { - "rgname": "[toLower(resourceGroup().name)]", - "splitParts": "[split(variables('rgname'), '-')]", - "rgpattern2": "[concat(variables('splitParts')[2],variables('splitParts')[3],variables('splitParts')[4],variables('splitParts')[5])]", - "varStorageAccountName": "[concat('st', variables('rgpattern2'))]", - "varLogAnalyticsWorkspaceName": "[concat('law-', parameters('FunctionAppName'))]", - "varAppServicePlanName": "[concat('Asp-', parameters('FunctionAppName'))]", - "varGraphEnvironmentName": "your_value_here" - }, - "resources": [ - { - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-05-01", - "name": "[variables('varStorageAccountName')]", - "location": "[parameters('Location')]", - "kind": "StorageV2", - "sku": { - "name": "Standard_LRS" - }, - "properties": {} - }, - { - "condition": "[and(parameters('EnableMonitoring'), not(parameters('UseExistingLAW')))]", - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2023-09-01", - "name": "[variables('varLogAnalyticsWorkspaceName')]", - "location": "[parameters('Location')]", - "properties": { - "sku": { - "name": "PerGB2018" - }, - "retentionInDays": 30 - } - }, - { - "type": "Microsoft.Web/serverfarms", - "apiVersion": "2022-03-01", - "name": "[variables('varAppServicePlanName')]", - "location": "[parameters('Location')]", - "sku": { - "name": "[parameters('AppPlanName')]", - "tier": "[parameters('AppPlanTier')]" - } - }, - { - "condition": "[parameters('EnableMonitoring')]", - "type": "Microsoft.Insights/components", - "apiVersion": "2020-02-02", - "name": "[variables('varAppServicePlanName')]", - "location": "[parameters('Location')]", - "kind": "web", - "properties": { - "Application_Type": "web", - "publicNetworkAccessForIngestion": "Enabled", - "publicNetworkAccessForQuery": "Enabled", - "WorkspaceResourceId": "[if(parameters('UseExistingLAW'), parameters('LogAnalyticsWorkspaceId'), resourceId('Microsoft.OperationalInsights/workspaces', variables('varLogAnalyticsWorkspaceName')))]" - }, - "dependsOn": [ - "[resourceId('Microsoft.OperationalInsights/workspaces', variables('varLogAnalyticsWorkspaceName'))]" - ] - }, - { - "type": "Microsoft.Web/sites", - "apiVersion": "2023-01-01", - "name": "[parameters('FunctionAppName')]", - "location": "[parameters('Location')]", - "kind": "functionApp", - "identity": "[parameters('FunctionAppIdentity')]", - "properties": { - "httpsOnly": true, - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('varAppServicePlanName'))]", - "siteConfig": { - "use32BitWorkerProcess": false, - "powerShellVersion": "7.4", - "netFrameworkVersion": "v6.0", - "appSettings": "[union(createArray(createObject('name', 'FUNCTIONS_EXTENSION_VERSION', 'value', '~4'), createObject('name', 'FUNCTIONS_WORKER_RUNTIME', 'value', 'powershell'), createObject('name', 'AzureWebJobs.timerTrigger1.Disabled', 'value', '1'), createObject('name', 'AzureWebJobsStorage', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING', 'value', format('DefaultEndpointsProtocol=https;AccountName={0};EndpointSuffix={1};AccountKey={2}', variables('varStorageAccountName'), environment().suffixes.storage, listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName')), '2022-05-01').keys[0].value)), createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey), createObject('name', 'WEBSITE_CONTENTSHARE', 'value', toLower(parameters('FunctionAppName')))), if(parameters('EnableMonitoring'), createArray(createObject('name', 'APPINSIGHTS_INSTRUMENTATIONKEY', 'value', reference(resourceId('Microsoft.Insights/components', variables('varAppServicePlanName')), '2020-02-02').InstrumentationKey)), createArray()), parameters('ReplacementPlanSettings'))]", - "ftpsState": "Disabled", - "cors": { - "allowedOrigins": [ - "https://portal.azure.com" - ] - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Insights/components', variables('varAppServicePlanName'))]", - "[resourceId('Microsoft.Web/serverfarms', variables('varAppServicePlanName'))]", - "[resourceId('Microsoft.Storage/storageAccounts', variables('varStorageAccountName'))]" - ] - }, - { - "type": "Microsoft.Web/sites/extensions", - "apiVersion": "2023-01-01", - "name": "[concat(parameters('FunctionAppName'), '/onedeploy')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites', parameters('FunctionAppName'))]" - ], - "properties": { - "packageUri": "[parameters('FunctionAppUrl')]", - "type": "zip" - } - } - ], - "outputs": { - "functionAppPrincipalId": { - "type": "string", - "value": "[if(equals(parameters('FunctionAppIdentity').type, 'SystemAssigned'), reference(resourceId('Microsoft.Web/sites', parameters('FunctionAppName')), '2023-01-01', 'full').identity.principalId, '')]" - } - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', 'deployKeyVault')]", - "[resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate')]" - ] - }, - { - "condition": "[not(equals(parameters('IdentityServiceProvider'), 'EntraID'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "deployKeyVault", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "Location": { - "value": "[parameters('Location')]" - }, - "KeyVaultName": { - "value": "[concat('kvSHR', variables('rgpattern2'))]" - }, - "DomainJoinPassword": { - "value": "[parameters('ADJoinUserPassword')]" - } - }, - "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": "17584014780822218958" - } - }, - "parameters": { - "Location": { - "type": "string", - "defaultValue": "[resourceGroup().location]" - }, - "KeyVaultName": { - "type": "string" - }, - "DomainJoinPassword": { - "type": "securestring" - } - }, - "resources": [ - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('KeyVaultName'), 'DomainJoinPassword')]", - "properties": { - "value": "[parameters('DomainJoinPassword')]" - }, - "dependsOn": [ - "[resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-07-01", - "name": "[parameters('KeyVaultName')]", - "location": "[parameters('Location')]", - "properties": { - "sku": { - "family": "A", - "name": "standard" - }, - "tenantId": "[subscription().tenantId]", - "enabledForTemplateDeployment": true, - "enableRbacAuthorization": true - } - } - ], - "outputs": { - "keyVaultId": { - "type": "string", - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('KeyVaultName'))]" - } - } - } - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "deployStandardSessionHostTemplate", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "Location": { - "value": "[parameters('Location')]" - }, - "Name": { - "value": "[format('{0}-Spec', parameters('HostPoolName'))]" - } - }, - "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": "10208603161915711494" - } - }, - "parameters": { - "Name": { - "type": "string" - }, - "Location": { - "type": "string", - "defaultValue": "[resourceGroup().location]" - } - }, - "variables": { - "$fxv#0": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.33.4.45862", - "templateHash": "13792602582245425536" - } - }, - "parameters": { - "Location": { - "type": "string", - "defaultValue": "[[resourceGroup().location]" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "VMNames": { - "type": "array" - }, - "VMNamePrefixLength": { - "type": "int" - }, - "VMSize": { - "type": "string" - }, - "SubnetID": { - "type": "string" - }, - "AdminUsername": { - "type": "string" - }, - "AcceleratedNetworking": { - "type": "bool" - }, - "DiskType": { - "type": "string" - }, - "DiskSizeGB": { - "type": "int", - "defaultValue": 128, - "metadata": { - "description": "OS disk size in GB" - } - }, - "DnsServers": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "List of DNS server IP addresses to set on the NIC" - } - }, - "Tags": { - "type": "object", - "defaultValue": {} - }, - "ImageReference": { - "type": "object" - }, - "SecurityProfile": { - "type": "object", - "defaultValue": {} - }, - "HostPoolName": { - "type": "string" - }, - "HostPoolToken": { - "type": "securestring" - }, - "WVDArtifactsURL": { - "type": "string", - "defaultValue": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip" - }, - "DomainJoinObject": { - "type": "object", - "defaultValue": {} - }, - "DomainJoinPassword": { - "type": "securestring", - "defaultValue": "" - } - }, - "resources": [ - { - "[string('copy')]": { - "name": "deploySessionHosts", - "count": "[[length(parameters('VMNames'))]" - }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[[format('deploySessionHost-{0}', parameters('VMNames')[copyIndex()])]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "AcceleratedNetworking": { - "value": "[[parameters('AcceleratedNetworking')]" - }, - "AdminUsername": { - "value": "[[parameters('AdminUsername')]" - }, - "HostPoolName": { - "value": "[[parameters('HostPoolName')]" - }, - "HostPoolToken": { - "value": "[[parameters('HostPoolToken')]" - }, - "ImageReference": { - "value": "[[parameters('ImageReference')]" - }, - "SecurityProfile": { - "value": "[[parameters('SecurityProfile')]" - }, - "SubnetID": { - "value": "[[parameters('SubnetID')]" - }, - "VMName": { - "value": "[[parameters('VMNames')[copyIndex()]]" - }, - "VMNamePrefixLength": { - "value": "[[parameters('VMNamePrefixLength')]" - }, - "VMSize": { - "value": "[[parameters('VMSize')]" - }, - "DiskType": { - "value": "[[parameters('DiskType')]" - }, - "DiskSizeGB": { - "value": "[[parameters('DiskSizeGB')]" - }, - "DnsServers": { - "value": "[[parameters('DnsServers')]" - }, - "WVDArtifactsURL": { - "value": "[[parameters('WVDArtifactsURL')]" - }, - "DomainJoinObject": { - "value": "[[parameters('DomainJoinObject')]" - }, - "DomainJoinPassword": { - "value": "[[parameters('DomainJoinPassword')]" - }, - "Location": { - "value": "[[parameters('Location')]" - }, - "AvailabilityZones": { - "value": "[[parameters('AvailabilityZones')]" - }, - "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.32.4.45862", - "templateHash": "9402242463429439832" - } - }, - "parameters": { - "VMName": { - "type": "string" - }, - "VMNamePrefixLength": { - "type": "int" - }, - "VMSize": { - "type": "string" - }, - "DiskType": { - "type": "string" - }, - "DiskSizeGB": { - "type": "int", - "defaultValue": 128, - "metadata": { - "description": "OS disk size in GB" - } - }, - "DnsServers": { - "type": "array", - "metadata": { - "description": "List of DNS server IP addresses to set on the NIC" - } - }, - "Location": { - "type": "string", - "defaultValue": "[[resourceGroup().location]" - }, - "AvailabilityZones": { - "type": "array", - "defaultValue": [] - }, - "SubnetID": { - "type": "string" - }, - "AdminUsername": { - "type": "string" - }, - "AdminPassword": { - "type": "securestring", - "defaultValue": "[[newGuid()]" - }, - "AcceleratedNetworking": { - "type": "bool" - }, - "Tags": { - "type": "object", - "defaultValue": {} - }, - "ImageReference": { - "type": "object" - }, - "SecurityProfile": { - "type": "object" - }, - "HostPoolName": { - "type": "string" - }, - "HostPoolToken": { - "type": "securestring" - }, - "WVDArtifactsURL": { - "type": "string" - }, - "DomainJoinObject": { - "type": "object", - "defaultValue": {} - }, - "DomainJoinPassword": { - "type": "securestring", - "defaultValue": "" - } - }, - "variables": { - "varRequireNvidiaGPU": "[[or(startsWith(parameters('VMSize'), 'Standard_NC'), contains(parameters('VMSize'), '_A10_v5'))]", - "varVMNumber": "[[int(substring(parameters('VMName'), parameters('VMNamePrefixLength'), sub(length(parameters('VMName')), parameters('VMNamePrefixLength'))))]", - "varAvailabilityZone": "[[if(equals(parameters('AvailabilityZones'), createArray()), createArray(), createArray(format('{0}', parameters('AvailabilityZones')[mod(variables('varVMNumber'), length(parameters('AvailabilityZones')))])))]" - }, - "resources": [ - { - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'deployIntegrityMonitoring')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Azure.Security.WindowsAttestation", - "type": "GuestAttestation", - "typeHandlerVersion": "1.0", - "autoUpgradeMinorVersion": true, - "settings": { - "AttestationConfig": { - "MaaSettings": { - "maaEndpoint": "", - "maaTenantName": "Guest Attestation" - }, - "AscSettings": { - "ascReportingEndpoint": "", - "ascReportingFrequency": "" - }, - "useCustomToken": "false", - "disableAlerts": "false" - } - } - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[variables('varRequireNvidiaGPU')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'deployGPUDriversNvidia')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.HpcCompute", - "type": "NvidiaGpuDriverWindows", - "typeHandlerVersion": "1.6", - "autoUpgradeMinorVersion": true - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployIntegrityMonitoring')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[not(equals(parameters('HostPoolName'), ''))]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'JoinHostPool')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.PowerShell", - "type": "DSC", - "typeHandlerVersion": "2.77", - "autoUpgradeMinorVersion": true, - "settings": { - "modulesUrl": "[[parameters('WVDArtifactsURL')]", - "configurationFunction": "Configuration.ps1\\AddSessionHost", - "properties": { - "hostPoolName": "[[parameters('HostPoolName')]", - "registrationInfoToken": "[[parameters('HostPoolToken')]", - "aadJoin": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), true(), false())]", - "useAgentDownloadEndpoint": true, - "mdmId": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, '0000000a-0000-0000-c000-000000000000', ''), '')]" - } - } - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'deployGPUDriversNvidia')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'EntraID')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'AADLoginForWindows')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Azure.ActiveDirectory", - "type": "AADLoginForWindows", - "typeHandlerVersion": "2.0", - "autoUpgradeMinorVersion": true, - "settings": "[[if(contains(parameters('DomainJoinObject'), 'IntuneJoin'), if(parameters('DomainJoinObject').IntuneJoin, createObject('mdmId', '0000000a-0000-0000-c000-000000000000'), null()), null())]" - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "condition": "[[equals(parameters('DomainJoinObject').DomainType, 'ActiveDirectory')]", - "type": "Microsoft.Compute/virtualMachines/extensions", - "apiVersion": "2023-09-01", - "name": "[[format('{0}/{1}', parameters('VMName'), 'DomainJoin')]", - "location": "[[parameters('Location')]", - "properties": { - "publisher": "Microsoft.Compute", - "type": "JSonADDomainExtension", - "typeHandlerVersion": "1.3", - "autoUpgradeMinorVersion": true, - "settings": { - "Name": "[[parameters('DomainJoinObject').DomainName]", - "OUPath": "[[parameters('DomainJoinObject').ADOUPath]", - "User": "[[format('{0}\\{1}', parameters('DomainJoinObject').DomainName, parameters('DomainJoinObject').DomainJoinUserName)]", - "Restart": "true", - "Options": 3 - }, - "protectedSettings": { - "Password": "[[parameters('DomainJoinPassword')]" - } - }, - "dependsOn": [ - "[[resourceId('Microsoft.Compute/virtualMachines/extensions', parameters('VMName'), 'JoinHostPool')]", - "[[resourceId('Microsoft.Compute/virtualMachines', parameters('VMName'))]" - ] - }, - { - "type": "Microsoft.Network/networkInterfaces", - "apiVersion": "2023-09-01", - "name": "[[format('{0}-vNIC', parameters('VMName'))]", - "location": "[[parameters('Location')]", - "properties": { - "ipConfigurations": [ - { - "name": "ipconfig1", - "properties": { - "subnet": { - "id": "[[parameters('SubnetID')]" - } - } - } - ], - "dnsSettings": { - "dnsServers": "[[parameters('DnsServers')]" - }, - "enableAcceleratedNetworking": "[[parameters('AcceleratedNetworking')]" - }, - "tags": "[[parameters('Tags')]" - }, - { - "type": "Microsoft.Compute/virtualMachines", - "apiVersion": "2023-09-01", - "name": "[[parameters('VMName')]", - "location": "[[parameters('Location')]", - "zones": "[[variables('varAvailabilityZone')]", - "identity": "[[if(equals(parameters('DomainJoinObject').DomainType, 'EntraID'), createObject('type', 'SystemAssigned'), null())]", - "properties": { - "osProfile": { - "computerName": "[[parameters('VMName')]", - "adminUsername": "[[parameters('AdminUsername')]", - "adminPassword": "[[parameters('AdminPassword')]" - }, - "hardwareProfile": { - "vmSize": "[[parameters('VMSize')]" - }, - "additionalCapabilities": { - "hibernationEnabled": true - }, - "storageProfile": { - "osDisk": { - "name": "[[format('{0}-OSDisk', parameters('VMName'))]", - "createOption": "FromImage", - "deleteOption": "Delete", - "diskSizeGB": "[[parameters('DiskSizeGB')]", - "managedDisk": { - "storageAccountType": "[[parameters('DiskType')]" - } - }, - "ImageReference": "[[parameters('ImageReference')]" - }, - "securityProfile": "[[parameters('SecurityProfile')]", - "diagnosticsProfile": { - "bootDiagnostics": { - "enabled": true - } - }, - "networkProfile": { - "networkInterfaces": [ - { - "id": "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]", - "properties": { - "deleteOption": "Delete" - } - } - ] - }, - "licenseType": "Windows_Client" - }, - "tags": "[[parameters('Tags')]", - "dependsOn": [ - "[[resourceId('Microsoft.Network/networkInterfaces', format('{0}-vNIC', parameters('VMName')))]" - ] - } - ] - } - } - } - ] - } - }, - "resources": [ - { - "type": "Microsoft.Resources/templateSpecs/versions", - "apiVersion": "2022-02-01", - "name": "[format('{0}/{1}', parameters('Name'), 'deploymentTemplateSpecVersion')]", - "location": "[parameters('Location')]", - "properties": { - "mainTemplate": "[variables('$fxv#0')]" - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/templateSpecs', parameters('Name'))]" - ] - }, - { - "type": "Microsoft.Resources/templateSpecs", - "apiVersion": "2022-02-01", - "name": "[parameters('Name')]", - "location": "[parameters('Location')]", - "properties": { - "description": "Template Spec for deploying VMs through the AVD Replacement Plan", - "displayName": "AVD Replacement Plan Session Host Template" - } - } - ], - "outputs": { - "TemplateSpecResourceId": { - "type": "string", - "value": "[resourceId('Microsoft.Resources/templateSpecs', parameters('Name'))]" - } - } - } - } - }, - { - "condition": "[not(parameters('UseUserAssignedManagedIdentity'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('RBAC-vdiVMContributor-{0}', parameters('TimeStamp'))]", - "subscriptionId": "[subscription().subscriptionId]", - "location": "[resourceGroup().location]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "PrinicpalId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployFunctionApp'), '2022-09-01').outputs.functionAppPrincipalId.value]" - }, - "RoleDefinitionId": { - "value": "a959dbd1-f747-45e3-8ba6-dd80f235f97c" - }, - "Scope": { - "value": "[subscription().id]" - } - }, - "template": { - "$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": "852792330190262115" - } - }, - "parameters": { - "PrinicpalId": { - "type": "string" - }, - "RoleDefinitionId": { - "type": "string" - }, - "Scope": { - "type": "string" - } - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(parameters('PrinicpalId'), parameters('RoleDefinitionId'), parameters('Scope'))]", - "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('RoleDefinitionId'))]", - "principalId": "[parameters('PrinicpalId')]" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', 'deployFunctionApp')]" - ] - }, - { - "condition": "[not(parameters('UseUserAssignedManagedIdentity'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('RBAC-TemplateSpecReader-{0}', parameters('TimeStamp'))]", - "subscriptionId": "[subscription().subscriptionId]", - "location": "[resourceGroup().location]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "PrinicpalId": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployFunctionApp'), '2022-09-01').outputs.functionAppPrincipalId.value]" - }, - "RoleDefinitionId": { - "value": "392ae280-861d-42bd-9ea5-08ee6d83b80e" - }, - "Scope": { - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate'), '2022-09-01').outputs.TemplateSpecResourceId.value]" - } - }, - "template": { - "$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": "852792330190262115" - } - }, - "parameters": { - "PrinicpalId": { - "type": "string" - }, - "RoleDefinitionId": { - "type": "string" - }, - "Scope": { - "type": "string" - } - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(parameters('PrinicpalId'), parameters('RoleDefinitionId'), parameters('Scope'))]", - "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('RoleDefinitionId'))]", - "principalId": "[parameters('PrinicpalId')]" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Resources/deployments', 'deployFunctionApp')]", - "[resourceId('Microsoft.Resources/deployments', 'deployStandardSessionHostTemplate')]" - ] - } - ] -} \ No newline at end of file diff --git a/deploy/zip/FunctionAp..zip b/deploy/zip/FunctionAp..zip new file mode 100644 index 0000000000000000000000000000000000000000..4d4ea3ee3e9d8528a2fad503dc568aa4250ae96b GIT binary patch literal 43383 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 literal 0 HcmV?d00001 From cc67e9d88a577843de479cf8fff0a52446a1713d Mon Sep 17 00:00:00 2001 From: Stefano Zeglio Date: Mon, 23 Mar 2026 17:17:07 +0100 Subject: [PATCH 82/83] Replace deployment zip artifact --- deploy/zip/{FunctionAp..zip => FunctionApp.zip} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename deploy/zip/{FunctionAp..zip => FunctionApp.zip} (100%) diff --git a/deploy/zip/FunctionAp..zip b/deploy/zip/FunctionApp.zip similarity index 100% rename from deploy/zip/FunctionAp..zip rename to deploy/zip/FunctionApp.zip From 7ecef42cafaaca4e4d46a0d6744296f12500a1e0 Mon Sep 17 00:00:00 2001 From: Stefano Zeglio Date: Mon, 23 Mar 2026 17:45:44 +0100 Subject: [PATCH 83/83] Switch FunctionApp zip references to main branch --- deploy/arm/DeployAVDSessionHostReplacer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/arm/DeployAVDSessionHostReplacer.json b/deploy/arm/DeployAVDSessionHostReplacer.json index 6c09232..a726024 100644 --- a/deploy/arm/DeployAVDSessionHostReplacer.json +++ b/deploy/arm/DeployAVDSessionHostReplacer.json @@ -335,7 +335,7 @@ }, "FunctionAppUrl": { "type": "string", - "defaultValue": "[if(and(contains(deployment().properties, 'templateLink'), not(empty(deployment().properties.templateLink.uri))), uri(replace(split(deployment().properties.templateLink.uri, '?')[0], 'DeployAVDSessionHostReplacer.json', ''), '../../FunctionApp/FunctionApp.zip'), 'https://raw.githubusercontent.com/stefze/AVDSessionHostReplacer/migration/deploy/zip/FunctionApp.zip')]", + "defaultValue": "[if(and(contains(deployment().properties, 'templateLink'), not(empty(deployment().properties.templateLink.uri))), uri(replace(split(deployment().properties.templateLink.uri, '?')[0], 'DeployAVDSessionHostReplacer.json', ''), '../../FunctionApp/FunctionApp.zip'), 'https://raw.githubusercontent.com/stefze/AVDSessionHostReplacer/main/deploy/zip/FunctionApp.zip')]", "metadata": { "description": "Required: No | URL of the FunctionApp.zip package. By default, this is derived from the current template URL so it follows the deployed repo branch automatically." } @@ -632,7 +632,7 @@ }, "FunctionAppUrl": { "type": "string", - "defaultValue": "https://raw.githubusercontent.com/stefze/AVDSessionHostReplacer/migration/deploy/zip/FunctionApp.zip", + "defaultValue": "https://raw.githubusercontent.com/stefze/AVDSessionHostReplacer/main/deploy/zip/FunctionApp.zip", "metadata": { "description": "Required: No | URL of the FunctionApp.zip file. This is the zip file containing the Function App code. | Default: The latest release of the Function App code." }