In this series I am going to show you how build a Windows 10 Image via Azure Pipelines and DevOps without 3rd party tooling, welcome to part 4!
We built the image in the past parts. It’s now time to actually built hostpools from our image. So, part 4 is about building hostpools!
Prerequisites
Part 1, 2 & 3 of this series needs to be completed. These are the links:
1. Windows 10 Image Series – Part 1 (Creating the Windows VM Pipeline)
2. Windows 10 Image Series – Part 2 (Artifacts & Application Installation)
3. Windows 10 Image Series – Part 3 (Shared Image Gallery)
Therefore, the prerequisites from part 1 are also required for this part. So:
Firstly, I am assuming that you have knowledge of Azure DevOps. These are the parts that already need be setup:
- Service Connection via Service Principal to ARM (more information)
- Azure Key Vault with connection to Azure DevOps (more information)
- GIT Repo in Azure DevOps (more information)
In addition, if you don’t have knowledge about Azure DevOps and still want to follow this series please let me know. I might write a blog about the preparing Azure DevOps.
Checkout/Skip to other parts:
0. Windows 10 Image Series – Part 0 (Preparing Azure/Azure DevOps)
1. Windows 10 Image Series – Part 1 (Creating the Windows VM Pipeline)
2. Windows 10 Image Series – Part 2 (Artifacts & Application Installation)
3. Windows 10 Image Series – Part 3 (Shared Image Gallery)
3.1 Windows 10 Image Series – Part 3.1 (Create test VM from Shared Image Gallery)
5. Windows 10 Image Series – Part 5 (Convert the Image Build pipeline to YAML)
6. Windows 10 Image Series – Part 6 (Deploy Sessionhosts with Bicep and YAML)
Creating the Azure Virtual Desktop Hostpool
Firstly, we need te create an Azure Virtual Desktop Hostpool to deploy the virtual machine to. We are using a Powershell function I created in an earlier blogpost.
Please copy this Powershell function:
Function CreateWVDHostPools { Param ( [Parameter(Mandatory = $True, Position = 1, ValueFromPipeline = $False)] [String]$ResourceGroupName, [Parameter(Mandatory = $True, Position = 2, ValueFromPipeline = $False)] [string[]]$HostPools ) $Location = "WestEurope" $ExistingResourceGroups = Get-AzResourceGroup if ($ExistingResourceGroups.ResourceGroupName -notcontains $ResourceGroupName) { Write-Host "ResourceGroup $($ResourceGroupName) does not exist. Creating new ResourceGroup" -ForegroundColor Green New-AzResourceGroup -Name $ResourceGroupName -Location $Location } else { Write-Host "ResourceGroup $($ResourceGroupName) already exists" -ForegroundColor Yellow } foreach ($HostPoolName in $HostPools){ New-AzWvdWorkspace -ResourceGroupName $ResourceGroupName ` -Name "$($HostPoolName)-Workspace" ` -Location $Location ` -FriendlyName "$($HostPoolName)-Workspace" ` -ApplicationGroupReference $null ` -Description "$($HostPoolName)-Workspace" New-AzWvdHostPool -Name $HostPoolName ` -ResourceGroupName $ResourceGroupName ` -Location $Location ` -HostPoolType Pooled ` -PreferredAppGroupType 'Desktop' ` -LoadBalancerType DepthFirst ` -MaxSessionLimit '12' ` $HostPool = Get-AzWvdHostPool -Name $HostPoolName -ResourceGroupName $ResourceGroupName New-AzWvdApplicationGroup -Name "$($HostPoolName)-DAG" ` -ResourceGroupName $ResourceGroupName ` -ApplicationGroupType 'Desktop' ` -HostPoolArmPath $HostPool.id ` -Location $Location $DAG = Get-AzWvdApplicationGroup -Name "$($HostPoolName)-DAG" -ResourceGroupName $ResourceGroupName Register-AzWvdApplicationGroup -ResourceGroupName $ResourceGroupName ` -WorkspaceName "$($HostPoolName)-Workspace" ` -ApplicationGroupPath $DAG.id } }
After that run the following code:
CreateWVDHostPools -ResourceGroupName DemoPart4 -HostPools NielskokdemoPart4
As a result, a new resourcegroup is created:
And the AVD Hostpool, Workspace and Applicationgroup are created:
Creating ResourceGroup for Virtual Machines
Secondly, we need a ResourceGroup for our Virtual Machines to reside in. I have created a Powershell Function to create ResourceGroups.
Please copy the following code:
function CreateResourceGroups { param ( [Parameter(Mandatory = $True, Position = 1, ValueFromPipeline = $False)] [string[]]$ResourceGroups, [Parameter(Mandatory = $False, Position = 2, ValueFromPipeline = $False)] [string]$Location = 'WestEurope' ) $ExistingResourceGroups = Get-AzResourceGroup foreach ($ResourceGroup in $ResourceGroups){ if ($ExistingResourceGroups.ResourceGroupName -notcontains $ResourceGroup) { Write-Host "ResourceGroup $($ResourceGroup) does not exist. Creating new ResourceGroup" -ForegroundColor Green New-AzResourceGroup -Name $ResourceGroup -Location $Location } else { Write-Host "ResourceGroup $($ResourceGroup) already Exists" -ForegroundColor Yellow } } }
After that, run this code: (If you want another region please add the -location parameter)
CreateResourceGroups -ResourceGroups DemoPart4VirtualMachines
As a result, the resource group is created:
Creating the hostpool pipeline
We are using build pipelines for our hostpool creation stage. We are not going to use release pipelines. In addition, YAML can’t be used in release pipelines and since YAML holds all the features in Azure Pipelines, we need to use build pipelines.
Firstly, log to Azure DevOps.
After that, go to Pipelines:
And click on “New pipeline”:
For now, we create a classic pipeline. In Part 5 we will convert everything to YAML:
After that, select your project, repository and branch:
Lastly, start with an empty job:
Next, name your pipeline and select the agent pool:
After that, add a Azure CLI job:
Fill in the name, select the service connection and select Inline Powershell:
I have used Sander Rozemuller’s method to deploy via Powershell. Please check out his blog as well.
Furthermore, put this script in the Inline Script section: (Please note that you need to fill in the variables)
#### Set Variables #ShareImageGallery $SharedImageGalleryName = 'WVDImageGallery' $SharedImageGalleryRG = 'RG_WE_SharedImageGallery' $SharedImageGalleryDefinitionName = 'Windows10WVDImage' #Hostpool - SessionhostProperties $HostpoolRG = 'DemoPart4' $HostPoolName = 'NielskokdemoPart4' $VMResourceGroupName = 'DemoPart4VirtualMachines' $SessionHostCount = 1 $InitialNumber = 1 $AVDPrefix = 'DEMO-' $VMSize = 'Standard_D2s_v3' $DiskSizeGB = 128 $Domain = 'DOMAINNAME' $ouPath = 'OUTPATH' $moduleLocation = 'https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration.zip' $VMLocalAdminUser = 'VMLocalAdminUser' $virtualNetworkname = 'VIRTUALNETWORKNAME' $SubnetName = 'SUBNETNAME' $Location = 'WestEurope' ## Get Image Version from Shared Image Gallery $imageVersion = Get-AzGalleryImageVersion -ResourceGroupName $SharedImageGalleryRG ` -GalleryName $SharedImageGalleryName ` -GalleryImageDefinitionName $SharedImageGalleryDefinitionName | Select-Object -Last 1 ## Get HostPool Properties $HostpoolProperties = Get-AzWvdHostPool -ResourceGroupName $HostpoolRG ` -Name $HostPoolName ## Create Hostpool RegistrationKey $HostpoolRegKey = New-AzWvdRegistrationInfo -ResourceGroupName $HostpoolRG ` -HostPoolName $HostPoolName ` -ExpirationTime (Get-Date).AddDays(14) ## Get Virtual Network Properties $virtualNetwork = Get-AzVirtualNetwork -Name $virtualNetworkname ## Get KeyVault Secrets $DomainJoinPassword = Get-AzKeyVaultSecret -VaultName 'KEYVAULTNAME' -Name DomainJoinPassword -AsPlainText $DomainJoinUsername = Get-AzKeyVaultSecret -VaultName 'KEYVAULTNAME' -Name DomainJoinUsername -AsPlainText $LocalAdminPassword = ConvertTo-SecureString (Get-AzKeyVaultSecret -VaultName 'WEDevOps' -Name LocalAdminPassword) -AsPlainText -Force ## Create Virtual Machines Do { Write-Host '' $VMName = $avdPrefix+"$initialNumber" $ComputerName = $VMName $nicName = "nic-$vmName" $NIC = New-AzNetworkInterface -Name $NICName -ResourceGroupName $VMResourceGroupName -Location $location -SubnetId ($virtualNetwork.Subnets | Where { $_.Name -eq $SubnetName }).Id $Credential = New-Object System.Management.Automation.PSCredential ($VMLocalAdminUser, $LocalAdminPassword); $VirtualMachine = New-AzVMConfig -VMName $VMName -VMSize $VMSize $VirtualMachine = Set-AzVMOperatingSystem -VM $VirtualMachine -Windows -ComputerName $ComputerName -Credential $Credential -ProvisionVMAgent -EnableAutoUpdate $VirtualMachine = Add-AzVMNetworkInterface -VM $VirtualMachine -Id $NIC.Id $VirtualMachine = Set-AzVMOSDisk -Windows -VM $VirtualMachine -CreateOption FromImage -DiskSizeInGB $DiskSizeGB $VirtualMachine = Set-AzVMSourceImage -VM $VirtualMachine -Id $imageVersion.id $sessionHost = New-AzVM -ResourceGroupName $VMResourceGroupName -Location $Location -VM $VirtualMachine $initialNumber++ $sessionHostCount-- Write-Output "$VMName deployed" } while ($sessionHostCount -ne 0) { Write-Verbose "Session hosts are created" } ## Install modules for Domain Join and Join Sessionhost to Hostpool $domainJoinSettings = @{ Name = "joindomain" Type = "JsonADDomainExtension" Publisher = "Microsoft.Compute" typeHandlerVersion = "1.3" SettingString = '{ "name": "'+ $($domain) + '", "ouPath": "'+ $($ouPath) + '", "user": "'+ $($DomainJoinUsername) + '", "restart": "'+ $true + '", "options": 3 }' ProtectedSettingString = '{ "password":"' + $($DomainJoinPassword) + '"}' VMName = $VMName ResourceGroupName = $VMResourceGroupName location = $Location } Set-AzVMExtension @domainJoinSettings $avdDscSettings = @{ Name = "Microsoft.PowerShell.DSC" Type = "DSC" Publisher = "Microsoft.Powershell" typeHandlerVersion = "2.73" SettingString = "{ ""modulesUrl"":'$moduleLocation', ""ConfigurationFunction"":""Configuration.ps1\\AddSessionHost"", ""Properties"": { ""hostPoolName"": ""$($HostpoolProperties.Name)"", ""registrationInfoToken"": ""$($HostpoolRegKey.token)"" } }" VMName = $VMName ResourceGroupName = $VMResourceGroupName location = $Location } Set-AzVMExtension @avdDscSettings
After that. run the pipeline:
The VMs created:
And added to the Hostpool:
This was Windows 10 Image Series – Part 4, check out the other parts:
0. Windows 10 Image Series – Part 0 (Preparing Azure/Azure DevOps)
1. Windows 10 Image Series – Part 1 (Creating the Windows VM Pipeline)
2. Windows 10 Image Series – Part 2 (Artifacts & Application Installation)
3. Windows 10 Image Series – Part 3 (Shared Image Gallery)
5. Windows 10 Image Series – Part 5 (Convert the Image Build pipeline to YAML)
6. Windows 10 Image Series – Part 6 (Deploy Sessionhosts with Bicep and YAML)
hi Niels
when i wan to run the first function I get
New-AzWvdHostPool : A parameter cannot be found that matches parameter name ‘PreferredAppGroupType’.
At C:\Users\rafal\Downloads\Untitled1.ps1:37 char:25
+ -PreferredAppGroupType ‘Desktop’ `
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-AzWvdHostPool], ParameterBindingException
+ FullyQualifiedErrorId : NamedParameterNotFound,New-AzWvdHostPool
hi Niels,
How could use this pipeline to make a vm in a host but but if should get Intune enrolld ?
Hi David,
The simplest method is to sync them using AD Connect and use a group policy to join them to Intune.
https://docs.microsoft.com/en-us/windows/client-management/mdm/enroll-a-windows-10-device-automatically-using-group-policy
Thanks,
Niels