├── Azure DevOps and Bicep Pipelines ├── bastion │ └── bastionhost.bicep ├── main.bicep ├── main.parameters.json └── vnet │ └── vnet.bicep ├── Azure DevOps and Terraform Pipelines ├── gitignore ├── main.tf ├── outputs.tf ├── terraform.tfvars └── variables.tf ├── Image Builder Pipeline ├── Install-Applications.ps1 ├── Win10MultiTemplate.json ├── Win10MultiTemplate.parameters.json └── azure-pipelines.yml ├── README.md ├── RestartandUpdateCustomization.txt └── Session Host Pipeline ├── AddSessionHost.ps1 ├── AddSessionHostTemplate.json ├── AddSessionHostTemplate.parameters.json └── azure-pipelines.yml /Azure DevOps and Bicep Pipelines/bastion/bastionhost.bicep: -------------------------------------------------------------------------------- 1 | // File to create a Bastion Host 2 | 3 | param bastionPubIpName string 4 | 5 | param bastionName string 6 | 7 | param location string 8 | 9 | param tags object 10 | 11 | param vnetId string 12 | 13 | var subnetId = '${vnetId}/subnets/AzureBastionSubnet' 14 | 15 | resource pubIp 'Microsoft.Network/publicIPAddresses@2022-01-01' = { 16 | name: bastionPubIpName 17 | location: location 18 | sku: { 19 | name: 'Standard' 20 | } 21 | properties: { 22 | publicIPAllocationMethod: 'Static' 23 | } 24 | } 25 | 26 | resource bastion 'Microsoft.Network/bastionHosts@2022-01-01' = { 27 | name: bastionName 28 | location: location 29 | tags: tags 30 | sku: { 31 | name: 'Basic' 32 | } 33 | properties: { 34 | ipConfigurations: [ 35 | { 36 | name: 'ipConfig' 37 | properties: { 38 | privateIPAllocationMethod: 'dynamic' 39 | publicIPAddress: { 40 | id: pubIp.id 41 | } 42 | subnet: { 43 | id: subnetId 44 | } 45 | } 46 | } 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Azure DevOps and Bicep Pipelines/main.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | 3 | // All 4 | param location string = resourceGroup().location 5 | 6 | param tags object 7 | 8 | // Subnet 9 | param vnetName string 10 | 11 | param addressPrefixes array 12 | 13 | param ipSubnets array 14 | 15 | // Bastion 16 | param bastionPubIpName string 17 | 18 | param bastionName string 19 | 20 | 21 | // Run 22 | module vnet './vnet/vnet.bicep' = { 23 | name: 'vnetDeploy' 24 | params: { 25 | location: location 26 | tags: tags 27 | ipSubnets: ipSubnets 28 | vnetName: vnetName 29 | addressPrefixes: addressPrefixes 30 | } 31 | } 32 | 33 | module bastion './bastion/bastionhost.bicep' = { 34 | name: 'bastionDeploy' 35 | params: { 36 | bastionPubIpName: bastionPubIpName 37 | bastionName: bastionName 38 | location: location 39 | tags: tags 40 | vnetId: vnet.outputs.vnetId 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /Azure DevOps and Bicep Pipelines/main.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "tags": { 6 | "value": { 7 | "Environment": "Lab", 8 | "Department": "IT" 9 | } 10 | }, 11 | "vnetName": { 12 | "value": "TestVNet7" 13 | }, 14 | "addressPrefixes": { 15 | "value": [ 16 | "10.10.0.0/16", 17 | "10.11.0.0/16" 18 | ] 19 | }, 20 | "ipSubnets": { 21 | "value": [ 22 | { 23 | "subnetPrefix": "10.10.0.0/24", 24 | "name": "Subnet1" 25 | }, 26 | { 27 | "subnetPrefix": "10.10.1.0/24", 28 | "name": "Subnet2" 29 | }, 30 | { 31 | "subnetPrefix": "10.10.250.0/24", 32 | "name": "AzureBastionSubnet" 33 | } 34 | ] 35 | }, 36 | "bastionPubIpName": { 37 | "value": "BastionIp" 38 | }, 39 | "bastionName": { 40 | "value": "BastionHost" 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /Azure DevOps and Bicep Pipelines/vnet/vnet.bicep: -------------------------------------------------------------------------------- 1 | // File to create a virtual network 2 | 3 | param location string 4 | 5 | param tags object 6 | 7 | param vnetName string 8 | 9 | param addressPrefixes array 10 | 11 | param ipSubnets array 12 | 13 | resource vnet 'Microsoft.Network/virtualNetworks@2022-01-01' = { 14 | name: vnetName 15 | location: location 16 | tags: tags 17 | properties: { 18 | addressSpace: { 19 | addressPrefixes: addressPrefixes 20 | } 21 | subnets: [for ipSubnet in ipSubnets: { 22 | name: ipSubnet.name 23 | properties: { 24 | addressPrefix: ipSubnet.subnetPrefix 25 | } 26 | }] 27 | } 28 | } 29 | 30 | output vnetId string = vnet.id 31 | -------------------------------------------------------------------------------- /Azure DevOps and Terraform Pipelines/gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | crash.*.log 11 | 12 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 13 | # password, private keys, and other secrets. These should not be part of version 14 | # control as they are data points which are potentially sensitive and subject 15 | # to change depending on the environment. 16 | *.tfvars 17 | *.tfvars.json 18 | 19 | # Ignore override files as they are usually used to override resources locally and so 20 | # are not checked in 21 | override.tf 22 | override.tf.json 23 | *_override.tf 24 | *_override.tf.json 25 | 26 | # Include override files you do wish to add to version control using negated pattern 27 | # !example_override.tf 28 | 29 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 30 | # example: *tfplan* 31 | 32 | # Ignore CLI configuration files 33 | .terraformrc 34 | terraform.rc -------------------------------------------------------------------------------- /Azure DevOps and Terraform Pipelines/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | azurerm = { 4 | source = "hashicorp/azurerm" 5 | version = "3.15.00" 6 | } 7 | } 8 | backend "azurerm" { 9 | resource_group_name = var.bkstrgrg 10 | storage_account_name = var.bkstrg 11 | container_name = var.bkcontainer 12 | key = var.bkstrgkey 13 | } 14 | } 15 | 16 | provider "azurerm" { 17 | features {} 18 | } 19 | 20 | resource "azurerm_resource_group" "vnet_rg" { 21 | name = var.resourcegroup_name 22 | location = var.location 23 | tags = var.tags 24 | } 25 | 26 | resource "azurerm_virtual_network" "vnet" { 27 | name = var.vnet_name 28 | address_space = var.vnet_address_space 29 | location = azurerm_resource_group.vnet_rg.location 30 | resource_group_name = azurerm_resource_group.vnet_rg.name 31 | tags = var.tags 32 | } 33 | 34 | resource "azurerm_subnet" "subnet" { 35 | for_each = var.subnets 36 | resource_group_name = var.resourcegroup_name 37 | virtual_network_name = azurerm_virtual_network.vnet.name 38 | name = each.value["name"] 39 | address_prefixes = each.value["address_prefixes"] 40 | } 41 | 42 | resource "azurerm_public_ip" "bastion_pubip" { 43 | name = "${var.bastionhost_name}PubIP" 44 | location = azurerm_resource_group.vnet_rg.location 45 | resource_group_name = azurerm_resource_group.vnet_rg.name 46 | allocation_method = "Static" 47 | sku = "Standard" 48 | tags = var.tags 49 | } 50 | 51 | resource "azurerm_bastion_host" "bastion" { 52 | name = var.bastionhost_name 53 | location = azurerm_resource_group.vnet_rg.location 54 | resource_group_name = azurerm_resource_group.vnet_rg.name 55 | tags = var.tags 56 | 57 | ip_configuration { 58 | name = "bastion_config" 59 | subnet_id = azurerm_subnet.subnet["bastion_subnet"].id 60 | public_ip_address_id = azurerm_public_ip.bastion_pubip.id 61 | } 62 | } -------------------------------------------------------------------------------- /Azure DevOps and Terraform Pipelines/outputs.tf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsrob50/PublicDevOps/619d011ec74593002941ec3fc6ed6559620aac62/Azure DevOps and Terraform Pipelines/outputs.tf -------------------------------------------------------------------------------- /Azure DevOps and Terraform Pipelines/terraform.tfvars: -------------------------------------------------------------------------------- 1 | 2 | # Deployment Config 3 | 4 | resourcegroup_name = "" 5 | 6 | location = "" 7 | 8 | tags = { 9 | "Environment" = "Lab" 10 | "Owner" = "" 11 | } 12 | 13 | vnet_name = "" 14 | 15 | vnet_address_space = ["10.211.0.0/16"] 16 | 17 | subnets = { 18 | Subnet_1 = { 19 | name = "subnet_1" 20 | address_prefixes = ["10.211.1.0/24"] 21 | } 22 | Subnet_2 = { 23 | name = "subnet_2" 24 | address_prefixes = ["10.211.2.0/24"] 25 | } 26 | bastion_subnet = { 27 | name = "AzureBastionSubnet" 28 | address_prefixes = ["10.211.250.0/24"] 29 | } 30 | } 31 | 32 | bastionhost_name = "BastionHost" -------------------------------------------------------------------------------- /Azure DevOps and Terraform Pipelines/variables.tf: -------------------------------------------------------------------------------- 1 | variable "bkstrgrg" { 2 | type = string 3 | description = "The name of the backend storage account resource group" 4 | default = "" 5 | } 6 | 7 | variable "bkstrg" { 8 | type = string 9 | description = "The name of the backend storage account" 10 | default = "" 11 | } 12 | 13 | variable "bkcontainer" { 14 | type = string 15 | description = "The container name for the backend config" 16 | default = "" 17 | } 18 | 19 | variable "bkstrgkey" { 20 | type = string 21 | description = "The access key for the storage account" 22 | default = "" 23 | } 24 | 25 | variable "resourcegroup_name" { 26 | type = string 27 | description = "The name of the resource group" 28 | default = "" 29 | } 30 | 31 | variable "location" { 32 | type = string 33 | description = "The region for the deployment" 34 | default = "" 35 | } 36 | 37 | variable "tags" { 38 | type = map(string) 39 | description = "Tags used for the deployment" 40 | default = { 41 | "Environment" = "Lab" 42 | "Owner" = "" 43 | } 44 | } 45 | 46 | variable "vnet_name" { 47 | type = string 48 | description = "The name of the vnet" 49 | default = "" 50 | } 51 | 52 | variable "vnet_address_space" { 53 | type = list(any) 54 | description = "the address space of the VNet" 55 | default = ["10.13.0.0/16"] 56 | } 57 | 58 | variable "subnets" { 59 | type = map(any) 60 | default = { 61 | subnet_1 = { 62 | name = "subnet_1" 63 | address_prefixes = ["10.13.1.0/24"] 64 | } 65 | subnet_2 = { 66 | name = "subnet_2" 67 | address_prefixes = ["10.13.2.0/24"] 68 | } 69 | subnet_3 = { 70 | name = "subnet_3" 71 | address_prefixes = ["10.13.3.0/24"] 72 | } 73 | # The name must be AzureBastionSubnet 74 | bastion_subnet = { 75 | name = "AzureBastionSubnet" 76 | address_prefixes = ["10.13.250.0/24"] 77 | } 78 | } 79 | } 80 | 81 | variable "bastionhost_name" { 82 | type = string 83 | description = "The name of the basion host" 84 | default = "" 85 | } 86 | -------------------------------------------------------------------------------- /Image Builder Pipeline/Install-Applications.ps1: -------------------------------------------------------------------------------- 1 | # Software install Script 2 | # 3 | # Applications to install: 4 | # 5 | # Foxit Reader Enterprise Packaging (requires registration) 6 | # https://kb.foxitsoftware.com/hc/en-us/articles/360040658811-Where-to-download-Foxit-Reader-with-Enterprise-Packaging-MSI- 7 | # 8 | # Notepad++ 9 | # https://notepad-plus-plus.org/downloads/v7.8.8/ 10 | # See comments on creating a custom setting to disable auto update message 11 | # https://community.notepad-plus-plus.org/post/38160 12 | 13 | 14 | #region Set logging 15 | $logFile = "c:\temp\" + (get-date -format 'yyyyMMdd') + '_softwareinstall.log' 16 | function Write-Log { 17 | Param($message) 18 | Write-Output "$(get-date -format 'yyyyMMdd HH:mm:ss') $message" | Out-File -Encoding utf8 $logFile -Append 19 | } 20 | #endregion 21 | 22 | #region Foxit Reader 23 | try { 24 | Start-Process -filepath msiexec.exe -Wait -ErrorAction Stop -ArgumentList '/i', 'c:\temp\FoxitReader101_enu_Setup.msi', '/quiet', 'ADDLOCAL="FX_PDFVIEWER"' 25 | if (Test-Path "C:\Program Files (x86)\Foxit Software\Foxit Reader\FoxitReader.exe") { 26 | Write-Log "Foxit Reader has been installed" 27 | } 28 | else { 29 | write-log "Error locating the Foxit Reader executable" 30 | } 31 | } 32 | catch { 33 | $ErrorMessage = $_.Exception.message 34 | write-log "Error installing Foxit Reader: $ErrorMessage" 35 | } 36 | #endregion 37 | 38 | #region Notepad++ 39 | try { 40 | Start-Process -filepath 'c:\temp\npp.7.8.8.Installer.x64.exe' -Wait -ErrorAction Stop -ArgumentList '/S' 41 | Copy-Item 'C:\temp\config.model.xml' 'C:\Program Files\Notepad++' 42 | Rename-Item 'C:\Program Files\Notepad++\updater' 'C:\Program Files\Notepad++\updaterOld' 43 | if (Test-Path "C:\Program Files\Notepad++\notepad++.exe") { 44 | Write-Log "Notepad++ has been installed" 45 | } 46 | else { 47 | write-log "Error locating the Notepad++ executable" 48 | } 49 | } 50 | catch { 51 | $ErrorMessage = $_.Exception.message 52 | write-log "Error installing Notepad++: $ErrorMessage" 53 | } 54 | #endregion 55 | 56 | #region Sysprep Fix 57 | # Fix for first login delays due to Windows Module Installer 58 | try { 59 | ((Get-Content -path C:\DeprovisioningScript.ps1 -Raw) -replace 'Sysprep.exe /oobe /generalize /quiet /quit', 'Sysprep.exe /oobe /generalize /quit /mode:vm' ) | Set-Content -Path C:\DeprovisioningScript.ps1 60 | write-log "Sysprep Mode:VM fix applied" 61 | } 62 | catch { 63 | $ErrorMessage = $_.Exception.message 64 | write-log "Error updating script: $ErrorMessage" 65 | } 66 | #endregion 67 | 68 | #region Time Zone Redirection 69 | $Name = "fEnableTimeZoneRedirection" 70 | $value = "1" 71 | # Add Registry value 72 | try { 73 | New-ItemProperty -ErrorAction Stop -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" -Name $name -Value $value -PropertyType DWORD -Force 74 | if ((Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services").PSObject.Properties.Name -contains $name) { 75 | Write-log "Added time zone redirection registry key" 76 | } 77 | else { 78 | write-log "Error locating the Teams registry key" 79 | } 80 | } 81 | catch { 82 | $ErrorMessage = $_.Exception.message 83 | write-log "Error adding teams registry KEY: $ErrorMessage" 84 | } 85 | #endregion -------------------------------------------------------------------------------- /Image Builder Pipeline/Win10MultiTemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "imageTemplateName": { 6 | "type": "string" 7 | }, 8 | "api-version": { 9 | "type": "string" 10 | }, 11 | "svclocation": { 12 | "type": "string" 13 | } 14 | }, 15 | "variables": {}, 16 | "resources": [ 17 | { 18 | "name": "[parameters('imageTemplateName')]", 19 | "type": "Microsoft.VirtualMachineImages/imageTemplates", 20 | "apiVersion": "[parameters('api-version')]", 21 | "location": "[parameters('svclocation')]", 22 | "dependsOn": [], 23 | "tags": { 24 | "imagebuilderTemplate": "win10multi", 25 | "userIdentity": "enabled" 26 | }, 27 | "identity": { 28 | "type": "UserAssigned", 29 | "userAssignedIdentities": { 30 | "": {} 31 | } 32 | }, 33 | "properties": { 34 | "buildTimeoutInMinutes": 90, 35 | "vmProfile": { 36 | "vmSize": "Standard_D2_v2", 37 | "osDiskSizeGB": 127 38 | }, 39 | "source": { 40 | "type": "PlatformImage", 41 | "publisher": "MicrosoftWindowsDesktop", 42 | "offer": "Windows-10", 43 | "sku": "20h2-evd", 44 | "version": "latest" 45 | }, 46 | "customize": [ 47 | { 48 | "type": "PowerShell", 49 | "name": "GetAzCopy", 50 | "inline": [ 51 | "New-Item -Type Directory -Path 'c:\\' -Name temp", 52 | "invoke-webrequest -uri 'https://aka.ms/downloadazcopy-v10-windows' -OutFile 'c:\\temp\\azcopy.zip'", 53 | "Expand-Archive 'c:\\temp\\azcopy.zip' 'c:\\temp'", 54 | "copy-item 'C:\\temp\\azcopy_windows_amd64_*\\azcopy.exe\\' -Destination 'c:\\temp'" 55 | ] 56 | }, 57 | { 58 | "type": "PowerShell", 59 | "name": "GetArchive", 60 | "inline": [ 61 | "c:\\temp\\azcopy.exe copy '' c:\\temp\\software.zip", 62 | "Expand-Archive 'c:\\temp\\software.zip' c:\\temp" 63 | ] 64 | }, 65 | { 66 | "type": "PowerShell", 67 | "runElevated": true, 68 | "name": "RunPoShInstall", 69 | "scriptUri": "" 70 | } 71 | ], 72 | "distribute": [ 73 | { 74 | "type": "SharedImage", 75 | "galleryImageId": "", 76 | "runOutputName": "win10Client", 77 | "artifactTags": { 78 | "environment": "Lab", 79 | "Owner": "IT" 80 | }, 81 | "replicationRegions": [ 82 | "CentralUS" 83 | ] 84 | } 85 | ] 86 | } 87 | } 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /Image Builder Pipeline/Win10MultiTemplate.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "imageTemplateName": { 6 | "value": "" 7 | }, 8 | "api-version": { 9 | "value": "2019-05-01-preview" 10 | }, 11 | "svclocation": { 12 | "value": "" 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Image Builder Pipeline/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | # trigger: 7 | # - master 8 | trigger: none 9 | pr: none 10 | 11 | pool: 12 | vmImage: ubuntu-latest 13 | 14 | variables: 15 | - name: Location 16 | value: '' 17 | - name: ResourceGroup 18 | value: ' 21 | 22 | steps: 23 | - task: AzureCLI@2 24 | displayName: "Copy Install Script" 25 | inputs: 26 | azureSubscription: 'ImageBuilder' 27 | scriptType: 'bash' 28 | scriptLocation: 'inlineScript' 29 | inlineScript: | 30 | az storage blob upload \ 31 | --account-name \ 32 | --container-name \ 33 | --name Install-Applications.ps1 \ 34 | --file Install-Applications.ps1 \ 35 | - task: AzureResourceManagerTemplateDeployment@3 36 | displayName: "Deploy Template" 37 | continueOnError: true 38 | inputs: 39 | deploymentScope: 'Resource Group' 40 | azureResourceManagerConnection: 'ImageBuilder' 41 | subscriptionId: '' 42 | action: 'Create Or Update Resource Group' 43 | resourceGroupName: '$(ResourceGroup)' 44 | location: '$(Location)' 45 | templateLocation: 'Linked artifact' 46 | csmFile: 'Win10MultiTemplate.json' 47 | csmParametersFile: 'Win10MultiTemplate.parameters.json' 48 | deploymentMode: 'Incremental' 49 | - task: AzurePowerShell@5 50 | displayName: "Install Az.ImageBuilder" 51 | inputs: 52 | azureSubscription: 'ImageBuilder' 53 | ScriptType: 'InlineScript' 54 | Inline: 'Install-Module -name Az.ImageBuilder -AllowPrerelease -Force -Scope CurrentUser' 55 | azurePowerShellVersion: 'LatestVersion' 56 | - task: AzurePowerShell@5 57 | displayName: "Build Image" 58 | inputs: 59 | azureSubscription: 'ImageBuilder' 60 | ScriptType: 'InlineScript' 61 | Inline: 'Start-AzImageBuilderTemplate -ResourceGroupName $(ResourceGroup) -Name $(ImageTemplateName)' 62 | azurePowerShellVersion: 'LatestVersion' 63 | - task: AzurePowerShell@5 64 | displayName: "Remove Template" 65 | inputs: 66 | azureSubscription: 'ImageBuilder' 67 | ScriptType: 'InlineScript' 68 | Inline: 'Remove-AzImageBuilderTemplate -ImageTemplateName $(ImageTemplateName) -ResourceGroupName $(ResourceGroup)' 69 | azurePowerShellVersion: 'LatestVersion' -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PublicDevOps 2 | Public repo for files related to Azure DevOps Videos 3 | 4 | # Azure DevOps and Bicep Pipelines 5 | Files used for Azure DevOps and Bicep Pipelines Video 6 | https://youtu.be/Q2HZdwTAWG0 7 | 8 | # Azure DevOps and Terraform Pipelines 9 | Files used for Azure DevOps and Terraform Pipelines Video 10 | https://youtu.be/d85-KD9stqc 11 | 12 | # Image Builder Pipeline 13 | Files used for Azure DevOps and Image Builder 14 | https://youtu.be/zL0eLEl2BxI 15 | 16 | # Session Host Pipeline 17 | Files used to deploy Session Hosts with Azure Devops 18 | https://youtu.be/bZy6Yay0ybM 19 | 20 | # RestartandUpdateCustomization.txt 21 | Resources for the Image Builder deployment template to add the restart and update customization. 22 | 23 | Files in this repo are for meant as examples and for demonstration purposes only. 24 | -------------------------------------------------------------------------------- /RestartandUpdateCustomization.txt: -------------------------------------------------------------------------------- 1 | { 2 | "type": "WindowsRestart", 3 | "restartCommand": "shutdown /r /f /t 0", 4 | "restartCheckCommand": "echo Azure-Image-Builder-Restarted-the-VM > c:\\buildArtifacts\\azureImageBuilderRestart.txt", 5 | "restartTimeout": "5m" 6 | }, 7 | { 8 | "type": "WindowsUpdate", 9 | "searchCriteria": "IsInstalled=0", 10 | "filters": ["exclude:$_.Title -like '*Preview*'", "include:$true"], 11 | "updateLimit": 20 12 | } -------------------------------------------------------------------------------- /Session Host Pipeline/AddSessionHost.ps1: -------------------------------------------------------------------------------- 1 | # Pipeline Variables 2 | variables: 3 | - name: hostPoolRg 4 | value: "" 5 | - name: hostPoolName 6 | value: "" 7 | - name: laWorkspaceName 8 | value: "" 9 | - name: laWorkspaceRg 10 | value: "LogAnalyticsWorkspaceRG>" 11 | 12 | # Get the Registration Key 13 | # if no key, create one for 24 hours 14 | $hostPoolRegKey = (Get-AzWvdRegistrationInfo -ResourceGroupName $(hostPoolRg) -HostPoolName $(hostPoolName)).token 15 | if ($hostPoolRegKey -eq $null) { 16 | $hostPoolRegKey = (New-AzWvdRegistrationInfo -ResourceGroupName $(hostPoolRg) -HostPoolName $(hostPoolName) -ExpirationTime $((get-date).ToUniversalTime().AddDays(1).ToString('yyyy-MM-ddTHH:mm:ss.fffffffZ'))).Token 17 | } 18 | "##vso[task.setvariable variable=hostPoolRegKey;]$hostPoolRegKey" 19 | 20 | 21 | # Get the Log Analytics Workspace Key 22 | $laWorkspaceKey = (Get-AzOperationalInsightsWorkspaceSharedKey -ResourceGroupName $(laWorkspaceRg) -Name $(laWorkspaceName)).PrimarySharedKey 23 | "##vso[task.setvariable variable=laWorkspaceKey;]$laWorkspaceKey" 24 | 25 | 26 | # Get the VM Prefix 27 | $vmPrefix = "SH" + (Get-Date -Format "MMddyyHHmm") 28 | "##vso[task.setvariable variable=vmPrefix;]$vmPrefix" 29 | 30 | 31 | # Override template parameters. 32 | -hostpoolToken $(hostPoolRegKey) -administratorAccountPassword $(domainadd) -vmAdministratorAccountPassword $(LocalAdmin) -vmNamePrefix $(vmPrefix) -workspaceKey $(laWorkspaceKey) 33 | 34 | 35 | # Enable Drain Mode 36 | $sessionHosts = Get-AzWvdSessionHost -ResourceGroupName $(hostPoolRg) -HostPoolName $(hostPoolName) | Where-Object {$_.Name -like "*$(vmPrefix)*"} 37 | foreach ($sessionHost in $sessionHosts) { 38 | $sessionHost = (($sessionHost.name).Split("/"))[1] 39 | Update-AzWvdSessionHost -ResourceGroupName $(hostPoolRg) -HostPoolName $(hostPoolName) -Name $sessionHost -AllowNewSession:$false 40 | } 41 | -------------------------------------------------------------------------------- /Session Host Pipeline/AddSessionHostTemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "nestedTemplatesLocation": { 6 | "type": "string", 7 | "metadata": { 8 | "description": "The base URI where artifacts required by this template are located." 9 | }, 10 | "defaultValue": "https://raw.githubusercontent.com/Azure/RDS-Templates/master/ARM-wvd-templates/nestedtemplates/" 11 | }, 12 | "artifactsLocation": { 13 | "type": "string", 14 | "metadata": { 15 | "description": "The base URI where artifacts required by this template are located." 16 | }, 17 | "defaultValue": "https://raw.githubusercontent.com/Azure/RDS-Templates/master/ARM-wvd-templates/DSC/Configuration.zip" 18 | }, 19 | "tags": { 20 | "type": "object", 21 | "metadata": { 22 | "description": "The tags to be assigned to the deployment" 23 | }, 24 | "defaultValue": {} 25 | }, 26 | "hostpoolName": { 27 | "type": "string", 28 | "metadata": { 29 | "description": "The name of the Hostpool to be created." 30 | } 31 | }, 32 | "hostpoolToken": { 33 | "type": "securestring", 34 | "metadata": { 35 | "description": "The registration key for the host pool where the session hosts will be added." 36 | } 37 | }, 38 | "administratorAccountUsername": { 39 | "type": "string", 40 | "metadata": { 41 | "description": "A username in the domain that has privileges to join the session hosts to the domain. For example, 'vmjoiner@contoso.com'." 42 | }, 43 | "defaultValue": "" 44 | }, 45 | "administratorAccountPassword": { 46 | "type": "securestring", 47 | "metadata": { 48 | "description": "The password that corresponds to the existing domain username." 49 | }, 50 | "defaultValue": "" 51 | }, 52 | "vmAdministratorAccountUsername": { 53 | "type": "string", 54 | "metadata": { 55 | "description": "A username to be used as the virtual machine administrator account. The vmAdministratorAccountUsername and vmAdministratorAccountPassword parameters must both be provided. Otherwise, domain administrator credentials provided by administratorAccountUsername and administratorAccountPassword will be used." 56 | }, 57 | "defaultValue": "" 58 | }, 59 | "vmAdministratorAccountPassword": { 60 | "type": "securestring", 61 | "metadata": { 62 | "description": "The password associated with the virtual machine administrator account. The vmAdministratorAccountUsername and vmAdministratorAccountPassword parameters must both be provided. Otherwise, domain administrator credentials provided by administratorAccountUsername and administratorAccountPassword will be used." 63 | }, 64 | "defaultValue": "" 65 | }, 66 | "availabilityOption": { 67 | "type": "string", 68 | "metadata": { 69 | "description": "Select the availability options for the VMs." 70 | }, 71 | "defaultValue": "AvailabilitySet", 72 | "allowedValues": [ 73 | "None", 74 | "AvailabilitySet", 75 | "AvailabilityZone" 76 | ] 77 | }, 78 | "createAvailabilitySet": { 79 | "type": "bool", 80 | "metadata": { 81 | "description": "Whether to create a new availability set for the VMs." 82 | }, 83 | "defaultValue": true 84 | }, 85 | "availabilitySetUpdateDomainCount": { 86 | "type": "int", 87 | "metadata": { 88 | "description": "The platform update domain count of availability set to be created." 89 | }, 90 | "defaultValue": 5, 91 | "allowedValues": [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 ] 92 | }, 93 | "availabilitySetFaultDomainCount": { 94 | "type": "int", 95 | "metadata": { 96 | "description": "The platform fault domain count of availability set to be created." 97 | }, 98 | "defaultValue": 2, 99 | "allowedValues": [ 1, 2, 3 ] 100 | }, 101 | "availabilityZones": { 102 | "type": "array", 103 | "metadata": { 104 | "description": "The availability zones to equally distribute VMs amongst" 105 | }, 106 | "defaultValue": [] 107 | }, 108 | "vmResourceGroup": { 109 | "type": "string", 110 | "metadata": { 111 | "description": "The resource group of the session host VMs." 112 | }, 113 | "defaultValue": "[resourceGroup().name]" 114 | }, 115 | "vmLocation": { 116 | "type": "string", 117 | "metadata": { 118 | "description": "The location of the session host VMs." 119 | }, 120 | "defaultValue": "[resourceGroup().location]" 121 | }, 122 | "vmSize": { 123 | "type": "string", 124 | "metadata": { 125 | "description": "The size of the session host VMs." 126 | } 127 | }, 128 | "vmInitialNumber": { 129 | "type": "int", 130 | "metadata": { 131 | "description": "VM name prefix initial number." 132 | }, 133 | "defaultValue": 1 134 | }, 135 | "vmNumberOfInstances": { 136 | "type": "int", 137 | "metadata": { 138 | "description": "Number of session hosts that will be created and added to the hostpool." 139 | } 140 | }, 141 | "vmNamePrefix": { 142 | "type": "string", 143 | "metadata": { 144 | "description": "This prefix will be used in combination with the VM number to create the VM name. If using 'rdsh' as the prefix, VMs would be named 'rdsh-0', 'rdsh-1', etc. You should use a unique prefix to reduce name collisions in Active Directory." 145 | } 146 | }, 147 | "vmImageType": { 148 | "type": "string", 149 | "metadata": { 150 | "description": "Select the image source for the session host vms. VMs from a Gallery image will be created with Managed Disks." 151 | }, 152 | "defaultValue": "CustomImage", 153 | "allowedValues": [ 154 | "CustomVHD", 155 | "CustomImage", 156 | "Gallery", 157 | "Disk" 158 | ] 159 | }, 160 | "vmGalleryImageOffer": { 161 | "type": "string", 162 | "metadata": { 163 | "description": "(Required when vmImageType = Gallery) Gallery image Offer." 164 | }, 165 | "defaultValue": "" 166 | }, 167 | "vmGalleryImagePublisher": { 168 | "type": "string", 169 | "metadata": { 170 | "description": "(Required when vmImageType = Gallery) Gallery image Publisher." 171 | }, 172 | "defaultValue": "" 173 | }, 174 | "vmGalleryImageSKU": { 175 | "type": "string", 176 | "metadata": { 177 | "description": "(Required when vmImageType = Gallery) Gallery image SKU." 178 | }, 179 | "defaultValue": "" 180 | }, 181 | "vmImageVhdUri": { 182 | "type": "string", 183 | "metadata": { 184 | "description": "(Required when vmImageType = CustomVHD) URI of the sysprepped image vhd file to be used to create the session host VMs. For example, https://rdsstorage.blob.core.windows.net/vhds/sessionhostimage.vhd" 185 | }, 186 | "defaultValue": "" 187 | }, 188 | "vmCustomImageSourceId": { 189 | "type": "string", 190 | "metadata": { 191 | "description": "(Required when vmImageType = CustomImage) Resource ID of the image" 192 | }, 193 | "defaultValue": "" 194 | }, 195 | "vmDiskType": { 196 | "type": "string", 197 | "allowedValues": [ 198 | "UltraSSD_LRS", 199 | "Premium_LRS", 200 | "StandardSSD_LRS", 201 | "Standard_LRS" 202 | ], 203 | "metadata": { 204 | "description": "The VM disk type for the VM: HDD or SSD." 205 | }, 206 | "defaultValue": "Premium_LRS" 207 | }, 208 | "vmUseManagedDisks": { 209 | "type": "bool", 210 | "metadata": { 211 | "description": "True indicating you would like to use managed disks or false indicating you would like to use unmanaged disks." 212 | }, 213 | "defaultValue": true 214 | }, 215 | "storageAccountResourceGroupName": { 216 | "type": "string", 217 | "metadata": { 218 | "description": "(Required when vmUseManagedDisks = False) The resource group containing the storage account of the image vhd file." 219 | }, 220 | "defaultValue": "" 221 | }, 222 | "existingVnetName": { 223 | "type": "string", 224 | "metadata": { 225 | "description": "The name of the virtual network the VMs will be connected to." 226 | } 227 | }, 228 | "existingSubnetName": { 229 | "type": "string", 230 | "metadata": { 231 | "description": "The subnet the VMs will be placed in." 232 | } 233 | }, 234 | "virtualNetworkResourceGroupName": { 235 | "type": "string", 236 | "metadata": { 237 | "description": "The resource group containing the existing virtual network." 238 | } 239 | }, 240 | "createNetworkSecurityGroup": { 241 | "type": "bool", 242 | "metadata": { 243 | "description": "Whether to create a new network security group or use an existing one" 244 | }, 245 | "defaultValue": false 246 | }, 247 | "networkSecurityGroupId": { 248 | "type": "string", 249 | "metadata": { 250 | "description": "The resource id of an existing network security group" 251 | }, 252 | "defaultValue": "" 253 | }, 254 | "networkSecurityGroupRules": { 255 | "type": "array", 256 | "metadata": { 257 | "description": "The rules to be given to the new network security group" 258 | }, 259 | "defaultValue": [] 260 | }, 261 | "deploymentId": { 262 | "type": "string", 263 | "metadata": { 264 | "description": "GUID for the deployment" 265 | }, 266 | "defaultValue": "[guid(resourceGroup().id)]" 267 | }, 268 | "ouPath": { 269 | "type": "string", 270 | "metadata": { 271 | "description": "OUPath for the domain join" 272 | }, 273 | "defaultValue": "" 274 | }, 275 | "domain": { 276 | "type": "string", 277 | "metadata": { 278 | "description": "Domain to join" 279 | }, 280 | "defaultValue": "" 281 | }, 282 | "aadJoin": { 283 | "type": "bool", 284 | "metadata": { 285 | "description": "IMPORTANT: Please don't use this parameter as AAD Join is not supported yet. True if AAD Join, false if AD join" 286 | }, 287 | "defaultValue": false 288 | }, 289 | "intune": { 290 | "type": "bool", 291 | "metadata": { 292 | "description": "IMPORTANT: Please don't use this parameter as intune enrollment is not supported yet. True if intune enrollment is selected. False otherwise" 293 | }, 294 | "defaultValue": false 295 | }, 296 | "workspaceId": { 297 | "type": "string", 298 | "metadata": { 299 | "description": "The ID of the Log Analytics workspace used for WVD Monitoring Insights" 300 | } 301 | }, 302 | "workspaceKey": { 303 | "type": "string", 304 | "metadata": { 305 | "description": "The Log Analytics workspace key used to authenticate the agent" 306 | } 307 | } 308 | }, 309 | "variables": { 310 | "availabilitySetName": "[concat('AVSet-', parameters('vmNamePrefix'))]", 311 | "rdshManagedDisks": "[if(equals(parameters('vmImageType'), 'CustomVHD'), parameters('vmUseManagedDisks'), bool('true'))]", 312 | "rdshPrefix": "[concat(parameters('vmNamePrefix'),'-')]", 313 | "avSetSKU": "[if(variables('rdshManagedDisks'), 'Aligned', 'Classic')]", 314 | "vhds": "[concat('vhds','/', variables('rdshPrefix'))]", 315 | "subnet-id": "[resourceId(parameters('virtualNetworkResourceGroupName'),'Microsoft.Network/virtualNetworks/subnets',parameters('existingVnetName'), parameters('existingSubnetName'))]", 316 | "vmTemplateName": "[concat( if(variables('rdshManagedDisks'), 'managedDisks', 'unmanagedDisks'), '-', toLower(replace(parameters('vmImageType'),' ', '')), 'vm')]", 317 | "vmTemplateUri": "[concat(parameters('nestedTemplatesLocation'), variables('vmTemplateName'),'.json')]", 318 | "rdshVmNamesOutput": { 319 | "copy": [ 320 | { 321 | "name": "rdshVmNamesCopy", 322 | "count": "[parameters('vmNumberOfInstances')]", 323 | "input": { 324 | "name": "[concat(variables('rdshPrefix'), add(parameters('vmInitialNumber'), copyIndex('rdshVmNamesCopy')))]" 325 | } 326 | } 327 | ] 328 | } 329 | }, 330 | "resources": [ 331 | { 332 | "apiVersion": "2018-05-01", 333 | "name": "[concat('AVSet-linkedTemplate-', parameters('deploymentId'))]", 334 | "type": "Microsoft.Resources/deployments", 335 | "resourceGroup": "[parameters('vmResourceGroup')]", 336 | "condition": "[and(equals(parameters('availabilityOption'), 'AvailabilitySet'), parameters('createAvailabilitySet'))]", 337 | "properties": { 338 | "mode": "Incremental", 339 | "template": { 340 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 341 | "contentVersion": "1.0.0.0", 342 | "resources": [ 343 | { 344 | "apiVersion": "2018-10-01", 345 | "type": "Microsoft.Compute/availabilitySets", 346 | "name": "[variables('availabilitySetName')]", 347 | "location": "[parameters('vmLocation')]", 348 | "tags": "[parameters('tags')]", 349 | "properties": { 350 | "platformUpdateDomainCount": "[parameters('availabilitySetUpdateDomainCount')]", 351 | "platformFaultDomainCount": "[parameters('availabilitySetFaultDomainCount')]" 352 | }, 353 | "sku": { 354 | "name": "[variables('avSetSKU')]" 355 | } 356 | } 357 | ] 358 | } 359 | } 360 | }, 361 | { 362 | "apiVersion": "2018-05-01", 363 | "name": "[concat('vmCreation-linkedTemplate-', parameters('deploymentId'))]", 364 | "resourceGroup": "[parameters('vmResourceGroup')]", 365 | "dependsOn": [ 366 | "[concat('AVSet-linkedTemplate-', parameters('deploymentId'))]" 367 | ], 368 | "type": "Microsoft.Resources/deployments", 369 | "properties": { 370 | "mode": "Incremental", 371 | "templateLink": { 372 | "uri": "[variables('vmTemplateUri')]", 373 | "contentVersion": "1.0.0.0" 374 | }, 375 | "parameters": { 376 | "artifactsLocation": { 377 | "value": "[parameters('artifactsLocation')]" 378 | }, 379 | "availabilityOption": { 380 | "value": "[parameters('availabilityOption')]" 381 | }, 382 | "availabilitySetName": { 383 | "value": "[variables('availabilitySetName')]" 384 | }, 385 | "availabilityZones": { 386 | "value": "[parameters('availabilityZones')]" 387 | }, 388 | "vmGalleryImageOffer": { 389 | "value": "[parameters('vmGalleryImageOffer')]" 390 | }, 391 | "vmGalleryImagePublisher": { 392 | "value": "[parameters('vmGalleryImagePublisher')]" 393 | }, 394 | "vmGalleryImageSKU": { 395 | "value": "[parameters('vmGalleryImageSKU')]" 396 | }, 397 | "rdshPrefix": { 398 | "value": "[variables('rdshPrefix')]" 399 | }, 400 | "rdshNumberOfInstances": { 401 | "value": "[parameters('vmNumberOfInstances')]" 402 | }, 403 | "rdshVMDiskType": { 404 | "value": "[parameters('vmDiskType')]" 405 | }, 406 | "rdshVmSize": { 407 | "value": "[parameters('vmSize')]" 408 | }, 409 | "enableAcceleratedNetworking": { 410 | "value": false 411 | }, 412 | "vmAdministratorAccountUsername": { 413 | "value": "[parameters('vmAdministratorAccountUsername')]" 414 | }, 415 | "vmAdministratorAccountPassword": { 416 | "value": "[parameters('vmAdministratorAccountPassword')]" 417 | }, 418 | "administratorAccountUsername": { 419 | "value": "[parameters('administratorAccountUsername')]" 420 | }, 421 | "administratorAccountPassword": { 422 | "value": "[parameters('administratorAccountPassword')]" 423 | }, 424 | "subnet-id": { 425 | "value": "[variables('subnet-id')]" 426 | }, 427 | "vhds": { 428 | "value": "[variables('vhds')]" 429 | }, 430 | "rdshImageSourceId": { 431 | "value": "[parameters('vmCustomImageSourceId')]" 432 | }, 433 | "location": { 434 | "value": "[parameters('vmLocation')]" 435 | }, 436 | "createNetworkSecurityGroup": { 437 | "value": "[parameters('createNetworkSecurityGroup')]" 438 | }, 439 | "networkSecurityGroupId": { 440 | "value": "[parameters('networkSecurityGroupId')]" 441 | }, 442 | "networkSecurityGroupRules": { 443 | "value": "[parameters('networkSecurityGroupRules')]" 444 | }, 445 | "networkInterfaceTags": { 446 | "value": "[parameters('tags')]" 447 | }, 448 | "networkSecurityGroupTags": { 449 | "value": "[parameters('tags')]" 450 | }, 451 | "virtualMachineTags": { 452 | "value": "[parameters('tags')]" 453 | }, 454 | "imageTags": { 455 | "value": "[parameters('tags')]" 456 | }, 457 | "vmInitialNumber": { 458 | "value": "[parameters('vmInitialNumber')]" 459 | }, 460 | "hostpoolName": { 461 | "value": "[parameters('hostpoolName')]" 462 | }, 463 | "hostpoolToken": { 464 | "value": "[parameters('hostpoolToken')]" 465 | }, 466 | "domain": { 467 | "value": "[parameters('domain')]" 468 | }, 469 | "ouPath": { 470 | "value": "[parameters('ouPath')]" 471 | }, 472 | "aadJoin": { 473 | "value": "[parameters('aadJoin')]" 474 | }, 475 | "intune": { 476 | "value": "[parameters('intune')]" 477 | }, 478 | "_guidValue": { 479 | "value": "[parameters('deploymentId')]" 480 | } 481 | } 482 | } 483 | }, 484 | { 485 | "type": "Microsoft.Compute/virtualMachines/extensions", 486 | "name": "[concat(variables('rdshPrefix'), add(copyindex(), parameters('vmInitialNumber')),'/OMSExtension')]", 487 | "apiVersion": "2015-06-15", 488 | "location": "[parameters('vmLocation')]", 489 | "dependsOn": [ 490 | "[concat('vmCreation-linkedTemplate-', parameters('deploymentId'))]" 491 | ], 492 | "copy": { 493 | "name": "rdsh-la-loop", 494 | "count": "[parameters('vmNumberOfInstances')]" 495 | }, 496 | "properties": { 497 | "publisher": "Microsoft.EnterpriseCloud.Monitoring", 498 | "type": "MicrosoftMonitoringAgent", 499 | "typeHandlerVersion": "1.0", 500 | "autoUpgradeMinorVersion": true, 501 | "settings": { 502 | "workspaceId": "[parameters('workspaceId')]" 503 | }, 504 | "protectedSettings": { 505 | "workspaceKey": "[parameters('workspaceKey')]" 506 | } 507 | } 508 | } 509 | ], 510 | "outputs": { 511 | "rdshVmNamesObject": { 512 | "value": "[variables('rdshVmNamesOutput')]", 513 | "type": "object" 514 | } 515 | } 516 | } 517 | -------------------------------------------------------------------------------- /Session Host Pipeline/AddSessionHostTemplate.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "nestedTemplatesLocation": { 6 | "value": "https://raw.githubusercontent.com/Azure/RDS-Templates/master/ARM-wvd-templates/nestedtemplates/" 7 | }, 8 | "artifactsLocation": { 9 | "value": "https://raw.githubusercontent.com/Azure/RDS-Templates/master/ARM-wvd-templates/DSC/Configuration.zip" 10 | }, 11 | "tags": { 12 | "value": { 13 | "Environment": "Lab", 14 | "Owner": "IT" 15 | } 16 | }, 17 | "hostpoolName": { 18 | "value": "" 19 | }, 20 | "hostpoolToken": { 21 | "value": "" 22 | }, 23 | "administratorAccountUsername": { 24 | "value": "" 25 | }, 26 | "administratorAccountPassword": { 27 | "reference": { 28 | "keyVault": { 29 | "id": "" 30 | }, 31 | "secretName": "" 32 | } 33 | }, 34 | "vmAdministratorAccountUsername": { 35 | "value": "" 36 | }, 37 | "vmAdministratorAccountPassword": { 38 | "reference": { 39 | "keyVault": { 40 | "id": "" 41 | }, 42 | "secretName": "" 43 | } 44 | }, 45 | "vmSize": { 46 | "value": "" 47 | }, 48 | "vmInitialNumber": { 49 | "value": 1 50 | }, 51 | "vmNumberOfInstances": { 52 | "value": 3 53 | }, 54 | "vmNamePrefix": { 55 | "value": "" 56 | }, 57 | "vmImageType": { 58 | "value": "CustomImage" 59 | }, 60 | "vmCustomImageSourceId": { 61 | "value": "" 62 | }, 63 | "vmDiskType": { 64 | "value": "" 65 | }, 66 | "existingVnetName": { 67 | "value": "" 68 | }, 69 | "existingSubnetName": { 70 | "value": "" 71 | }, 72 | "virtualNetworkResourceGroupName": { 73 | "value": "" 74 | }, 75 | "ouPath": { 76 | "value": "" 77 | }, 78 | "domain": { 79 | "value": "" 80 | }, 81 | "workspaceId": { 82 | "value": "" 83 | }, 84 | "workspaceKey": { 85 | "value": "" 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /Session Host Pipeline/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | # trigger: 7 | # - main 8 | trigger: none 9 | pr: none 10 | 11 | variables: 12 | - name: hostPoolRg 13 | value: "" 14 | - name: hostPoolName 15 | value: "" 16 | - name: laWorkspaceName 17 | value: "" 18 | - name: laWorkspaceRg 19 | value: "LogAnalyticsWorkspaceRG>" 20 | 21 | pool: 22 | vmImage: ubuntu-latest 23 | 24 | steps: 25 | - task: AzureKeyVault@1 26 | displayName: "Get Key Vault Vars" 27 | inputs: 28 | azureSubscription: 'SessionHostBuild2' 29 | KeyVaultName: 'CentralKey02' 30 | SecretsFilter: '*' 31 | RunAsPreJob: false 32 | - task: AzurePowerShell@5 33 | displayName: "Get Host Pool Reg Key" 34 | inputs: 35 | azureSubscription: 'SessionHostBuild2' 36 | ScriptType: 'InlineScript' 37 | Inline: | 38 | $hostPoolRegKey = (Get-AzWvdRegistrationInfo -ResourceGroupName $(hostPoolRg) -HostPoolName $(hostPoolName)).token 39 | if ($hostPoolRegKey -eq "") { 40 | $hostPoolRegKey = (New-AzWvdRegistrationInfo -ResourceGroupName $(hostPoolRg) -HostPoolName $(hostPoolName) -ExpirationTime $((get-date).ToUniversalTime().AddDays(1).ToString('yyyy-MM-ddTHH:mm:ss.fffffffZ'))).Token 41 | } 42 | "##vso[task.setvariable variable=hostPoolRegKey;]$hostPoolRegKey" 43 | azurePowerShellVersion: 'LatestVersion' 44 | - task: AzurePowerShell@5 45 | displayName: "Get Log Analytics Key" 46 | inputs: 47 | azureSubscription: 'SessionHostBuild2' 48 | ScriptType: 'InlineScript' 49 | Inline: | 50 | $laWorkspaceKey = (Get-AzOperationalInsightsWorkspaceSharedKey -ResourceGroupName $(laWorkspaceRg) -Name $(laWorkspaceName)).PrimarySharedKey 51 | "##vso[task.setvariable variable=laWorkspaceKey;]$laWorkspaceKey" 52 | azurePowerShellVersion: 'LatestVersion' 53 | - task: AzurePowerShell@5 54 | displayName: "Set SH Prefix" 55 | inputs: 56 | azureSubscription: 'SessionHostBuild2' 57 | ScriptType: 'InlineScript' 58 | Inline: | 59 | $vmPrefix = "SH" + (Get-Date -Format "MMddyyHHmm") 60 | "##vso[task.setvariable variable=vmPrefix;]$vmPrefix" 61 | azurePowerShellVersion: 'LatestVersion' 62 | - task: AzureResourceManagerTemplateDeployment@3 63 | displayName: "Build Session Hosts " 64 | inputs: 65 | deploymentScope: 'Resource Group' 66 | azureResourceManagerConnection: 'SessionHostBuild2' 67 | subscriptionId: '' 68 | action: 'Create Or Update Resource Group' 69 | resourceGroupName: 'ARMTestSHRG01' 70 | location: 'Central US' 71 | templateLocation: 'Linked artifact' 72 | csmFile: 'AddVirtualMachinesTemplate.json' 73 | csmParametersFile: 'AddVirtualMachinesTemplate.parameters.json' 74 | overrideParameters: '-hostpoolToken $(hostPoolRegKey) -administratorAccountPassword $(domainadd) -vmAdministratorAccountPassword $(LocalAdmin) -vmNamePrefix $(vmPrefix) -workspaceKey $(laWorkspaceKey)' 75 | deploymentMode: 'Incremental' 76 | - task: AzurePowerShell@5 77 | displayName: "SH Drain Mode" 78 | inputs: 79 | azureSubscription: 'SessionHostBuild2' 80 | ScriptType: 'InlineScript' 81 | Inline: | 82 | $sessionHosts = Get-AzWvdSessionHost -ResourceGroupName $(hostPoolRg) -HostPoolName $(hostPoolName) | Where-Object {$_.Name -like "*$(vmPrefix)*"} 83 | foreach ($sessionHost in $sessionHosts) { 84 | $sessionHost = (($sessionHost.name).Split("/"))[1] 85 | Update-AzWvdSessionHost -ResourceGroupName $(hostPoolRg) -HostPoolName $(hostPoolName) -Name $sessionHost -AllowNewSession:$false 86 | } 87 | azurePowerShellVersion: 'LatestVersion' --------------------------------------------------------------------------------