Skip to main content

Building an Azure VM with WSL2 via Azure Resource Manager (ARM) templates

·450 words·3 mins

A recent post on an Microsoft mailing list asked: “Is there an Azure VM template that has WSL2 preinstalled? Or is there a way to reboot during Azure Custom Script Extension’s run?”

Unfortunately there is no such template hosted on the Azure Gallery and from my experience any VM extension-based way of adding system prerequisites runs the risk of automatic updates, agent upgrades and/or one of the extensions or any other scheduled process causing a reboot in the middle of any installer that is running.

For a “100% as-code” approach (with apologies to DSC, the Custom Script Extension, Chef, Puppet et. al) I have not found a more reliable way than inserting an imperative custom PowerShell script in the OS customization phase before any user can logon to the desktop, rebooting and then executing the remainder using a PowerShell job “at startup” using the task scheduler for the “post-reboot” phase.

To demonstrate I cobbled together an example ARM template here on GitHub: https://github.com/stuartpreston/arm-vm-windows-wsl2 and you should have a read through the template and properties before using it.

A few notes.

  1. The Windows 10 images are published under the publisher MicrosoftWindowsDesktop and you can use the following Azure CLI command to retrieve the latest list: az vm image list --publisher MicrosoftWindowsDesktop --all -o table
Offer       Publisher                Sku                          Urn                                                                                   Version
----------  -----------------------  ---------------------------  ------------------------------------------------------------------------------------  ---------------------
office-365  MicrosoftWindowsDesktop  1903-evd-o365pp              MicrosoftWindowsDesktop:office-365:1903-evd-o365pp:18362.1198.2011031735              18362.1198.2011031735
office-365  MicrosoftWindowsDesktop  1903-evd-o365pp              MicrosoftWindowsDesktop:office-365:1903-evd-o365pp:18362.1256.2012032308              18362.1256.2012032308
office-365  MicrosoftWindowsDesktop  19h2-evd-o365pp              MicrosoftWindowsDesktop:office-365:19h2-evd-o365pp:18363.1198.2011031735              18363.1198.2011031735
office-365  MicrosoftWindowsDesktop  19h2-evd-o365pp              MicrosoftWindowsDesktop:office-365:19h2-evd-o365pp:18363.1256.2012032308              18363.1256.2012032308
office-365  MicrosoftWindowsDesktop  20h1-evd-o365pp              MicrosoftWindowsDesktop:office-365:20h1-evd-o365pp:19041.630.2011061636               19041.630.2011061636
office-365  MicrosoftWindowsDesktop  20h1-evd-o365pp              MicrosoftWindowsDesktop:office-365:20h1-evd-o365pp:19041.685.2012032305               19041.685.2012032305
office-365  MicrosoftWindowsDesktop  20h2-evd-o365pp              MicrosoftWindowsDesktop:office-365:20h2-evd-o365pp:19042.630.2011061636               19042.630.2011061636
office-365  MicrosoftWindowsDesktop  20h2-evd-o365pp              MicrosoftWindowsDesktop:office-365:20h2-evd-o365pp:19042.685.2012032244               19042.685.2012032244
office-365  MicrosoftWindowsDesktop  rs5-evd-o365pp               MicrosoftWindowsDesktop:office-365:rs5-evd-o365pp:17763.1577.2011031610               17763.1577.2011031610
office-365  MicrosoftWindowsDesktop  rs5-evd-o365pp               MicrosoftWindowsDesktop:office-365:rs5-evd-o365pp:17763.1637.2012040632               17763.1637.2012040632
  1. The interesting bits of the ARM template are here: https://github.com/stuartpreston/arm-vm-windows-wsl2/blob/main/azuredeploy.json#L73 and https://github.com/stuartpreston/arm-vm-windows-wsl2/blob/main/azuredeploy.json#L194-L217 where I insert a script and then execute it in the user first logon (before anyone can reach a desktop window over RDP).

  2. The script used is split into two sections:

Phase 1: Install Features, download the latest Ubuntu 20.04 LTS release:

$ProgressPreference='SilentlyContinue'
Enable-WindowsOptionalFeature -Online -NoRestart -FeatureName Microsoft-Windows-Subsystem-Linux
Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -NoRestart
Invoke-WebRequest https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi -OutFile c:\wsl_update_x64.msi -UseBasicParsing
Invoke-WebRequest -Uri https://aka.ms/wslubuntu2004 -OutFile c:\\ubuntu2004.appx -UseBasicParsing 
Add-AppxPackage c:\ubuntu2004.appx

Phase 2: Plant a script on the system and set up a scheduled task to launch it on the next startup:

Set-Content -Path c:\config2.ps1 -Value "msiexec /i c:\wsl_update_x64.msi /qn`r`nsleep 5`r`nwsl --set-default-version 2`r`nubuntu2004.exe install --root"
$action = New-ScheduledTaskAction -Execute powershell.exe -Argument "-File c:\config2.ps1"
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -RunLevel Highest -LogonType S4U
$trigger = New-JobTrigger -AtStartup -RandomDelay 00:00:01
$definition = New-ScheduledTask -Action $action -Principal $principal -Trigger $trigger
Register-ScheduledTask -TaskName \"install-ubuntu\" -InputObject $definition
Set-ExecutionPolicy RemoteSigned -Force
Restart-Computer -Force",

Hopefully someone out there will find these snippets and approach useful. It certainly isn’t perfect but it does keep everything as Infra code.