Configure AVD Languages with Azure Image Builder

Last Updated on 26/08/2025 by Alex

If you’ve ever had to deal with users logging into an Azure Virtual Desktop session that greets them in the wrong language, you’ll know how frustrating it can be for both them and you. Sorting out language and regional settings across a fleet of session hosts isn’t exactly thrilling, but it’s one of those things that makes a huge difference to the user experience.

In this post I’ll walk you through how to install additional languages and set the default language and regional settings for all users, baked directly into your AVD image using Azure Image Builder. It’s clean, repeatable, and avoids that dreaded “first login weirdness” users love to report. The below examples are for the English (United Kingdom) language and code is in Bicep format (feel free to convert if needed).

Prerequisites

I am going to assume that you have some knowledge of Azure Image Builder and how it works, so I am not going to run through how to step this up, but… if you want a blog post on how to configure AIB, just drop me a message or comment below.

Installing Language Packs

The first step to installing languages into your AIB image, is obviously to install the language packs, for this, I use the custom image template scripts provided by Microsoft here, obviously update the parameter to what language you wish to install, here’s my example for installing English (United Kingdom) language, I’ve added some additional write-outputs and basic error handling too:

{
        name: 'FILE_InstallLanguagePacks'
        type: 'File'
        destination: 'C:\\AIBTemp\\Production\\Configurations\\installLanguagePacks.ps1'
        sourceUri: 'https://raw.githubusercontent.com/Azure/RDS-Templates/master/CustomImageTemplateScripts/CustomImageTemplateScripts_2024-03-27/InstallLanguagePacks.ps1'
      }
      {
        name: 'CONF_InstallUKLanguagePack'
        type: 'PowerShell'        
        runAsSystem: true
        runElevated: true
        inline: [
          '$ErrorActionPreference = "Stop"'
          '$stepStart = Get-Date'
          'Write-Output "STEP STARTED: Installing UK Language Pack at $stepStart (UTC)"'
          '& "C:\\AIBTemp\\Production\\Configurations\\installLanguagePacks.ps1" -LanguageList "English (United Kingdom)"'
          '$stepEnd = Get-Date'
          '$duration = $stepEnd - $stepStart'
          'Write-Output "STEP COMPLETED: UK Language Pack installed at $stepEnd (UTC) (Duration: $($duration.ToString()))"'
        ]
      }
BICEP

Set Default Language Settings for System

Once the language pack has been installed successfully, we need to set the device to use that language as the default. For this, again, I’ll be using an additional step within Azure Image Builder:

      {
        name: 'CONF_SetSystemLanguage_en-GB'
        type: 'PowerShell'
        runAsSystem: true
        runElevated: true
        inline: [
          '$ErrorActionPreference = "Stop"'
          '$stepStart = Get-Date'
          'Write-Output "STEP STARTED: Setting system language to en-GB at $stepStart (UTC)"'
          '$LanguageTag = "en-gb"'
          'Write-Output "Setting Win User Language List to prioritise: $LanguageTag"'
          '$OldList = Get-WinUserLanguageList'
          '$UserLanguageList = New-WinUserLanguageList -Language $LanguageTag'
          '$UserLanguageList += $OldList | Where-Object { $_.LanguageTag -ne $LanguageTag }'
          'Set-WinUserLanguageList -LanguageList $UserLanguageList -Force'
          'Write-Output "Setting system preferred UI language to: $LanguageTag"'
          'Set-SystemPreferredUILanguage -Language $LanguageTag'
          'Write-Output "Setting system locale to: $LanguageTag"'
          'Set-WinSystemLocale -SystemLocale $LanguageTag'
          'Write-Output "A restart is required to set language configurations"'
          '$stepEnd = Get-Date'
          '$duration = $stepEnd - $stepStart'
          'Write-Output "STEP COMPLETED: System language set to en-GB at $stepEnd (UTC) (Duration: $($duration.ToString()))"'
        ]
      }
BICEP

Set UK Regional Settings and Default Language for Users

Now that we have our English (United Kingdom) language installed and system set to en-GB, we need a way of setting this for all users on the platform, if you’re familiar with these settings, you’ll know that these are user settings, thus installing in the users registry hive, if you’re using AD or Entra Hybrid Join you can of course using group policy to set these regional settings, but what if you’re cloud-native or just wishing to install these settings in the image itself? Introducing the Virtual Desktop Optimisation Tool or VDOT.

VDOT as the name suggests is a script provided by the RDS Team at Microsoft, it optimises your image for use with VDI, disabling particular scheduled tasks, services, removing AppX packages etc. But the item that we’re particularly interested in is that it can apply user-based registry settings, it does this by reading the DefaultUserSettings.Json during the script execution, loads the default registry hive and applies those settings.

So now that we have a way of doing this, let’s use Azure Image Builder, to download VDOT, add the additional UK based regional registry settings, here’s the code to do this:

      {
        name: 'CONF_RunVDOTOptimisations'
        type: 'PowerShell'
        runAsSystem: true
        runElevated: true
        inline: [
          '$ErrorActionPreference = "Stop"'
          '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8'
          '$PSDefaultParameterValues["Out-File:Encoding"] = "UTF8"'
          '$stepStart = Get-Date'
          'Write-Output "STEP STARTED: Running VDOT Optimization Tool Preparation at $stepStart (UTC)"'
          '$vdotZipUrl = "https://github.com/The-Virtual-Desktop-Team/Virtual-Desktop-Optimization-Tool/archive/refs/heads/main.zip"'
          '$vdotTempDir = "C:\\AIBTemp\\Production\\Configurations"'
          '$vdotZipPath = Join-Path $vdotTempDir "VDOT.zip"'
          '$vdotExtractedPath = Join-Path $vdotTempDir "Virtual-Desktop-Optimization-Tool-main"'
          '$defaultUserSettingsPath = Join-Path $vdotExtractedPath "2009\\ConfigurationFiles\\DefaultUserSettings.JSON"'
          'New-Item -ItemType Directory -Path $vdotTempDir -Force | Out-Null'
          'Write-Output "Downloading VDOT zip..."'
          'Invoke-WebRequest -Uri $vdotZipUrl -OutFile $vdotZipPath'
          'Write-Output "Extracting zip..."'
          'Expand-Archive -Path $vdotZipPath -DestinationPath $vdotTempDir -Force'
          'Write-Output "Unblocking files..."'
          'Get-ChildItem -Path $vdotExtractedPath -Recurse | Unblock-File'
          'if (Test-Path $defaultUserSettingsPath) {'
          '  Write-Output "Appending UK locale settings to DefaultUserSettings.JSON..."'
          '  $json = Get-Content $defaultUserSettingsPath -Encoding UTF8 | ConvertFrom-Json'
          '  $appendItems = @('
          '    [ordered]@{ HivePath = "HKLM:\\VDOT_TEMP\\Control Panel\\International"; KeyName = "Locale"; PropertyType = "STRING"; PropertyValue = "00000809"; SetProperty = "True" }'
          '    [ordered]@{ HivePath = "HKLM:\\VDOT_TEMP\\Control Panel\\International"; KeyName = "LocaleName"; PropertyType = "STRING"; PropertyValue = "en-GB"; SetProperty = "True" }'
          '    [ordered]@{ HivePath = "HKLM:\\VDOT_TEMP\\Control Panel\\International"; KeyName = "sCurrency"; PropertyType = "STRING"; PropertyValue = "\\u00A3"; SetProperty = "True" }'
          '    [ordered]@{ HivePath = "HKLM:\\VDOT_TEMP\\Control Panel\\International"; KeyName = "sShortDate"; PropertyType = "STRING"; PropertyValue = "dd/MM/yyyy"; SetProperty = "True" }'
          '    [ordered]@{ HivePath = "HKLM:\\VDOT_TEMP\\Control Panel\\International\\Geo"; KeyName = "Name"; PropertyType = "STRING"; PropertyValue = "GB"; SetProperty = "True" }'
          '    [ordered]@{ HivePath = "HKLM:\\VDOT_TEMP\\Control Panel\\International\\Geo"; KeyName = "Nation"; PropertyType = "STRING"; PropertyValue = "242"; SetProperty = "True" }'
          '  )'
          '  $json += $appendItems'
          '  $json | ConvertTo-Json -Depth 5 | Out-File -FilePath $defaultUserSettingsPath -Encoding UTF8 -Force'
          '  Write-Output "Locale entries appended to DefaultUserSettings.JSON."'
          '} else {'
          '  Write-Output "WARNING: DefaultUserSettings.JSON not found at $defaultUserSettingsPath"'
          '}'
          '$vdotScript = Join-Path $vdotExtractedPath "Windows_VDOT.ps1"'
          'if (Test-Path $vdotScript) {'
          '  Write-Output "Executing Windows_VDOT.ps1 with selected optimizations..."'
          '  & $vdotScript -Verbose -AcceptEula -Optimizations @('
          '    "Autologgers"'
          '    "DefaultUserSettings"'
          '    "LocalPolicy"'
          '    "NetworkOptimizations"'
          '    "ScheduledTasks"'
          '    "Services"'
          '    "WindowsMediaPlayer"'
          '  ) -AdvancedOptimizations @("Edge")'
          '  Write-Output "VDOT script completed."'
          '} else {'
          '  Write-Output "ERROR: VDOT.ps1 not found at $vdotScript"'
          '}'
          '$stepEnd = Get-Date'
          '$duration = $stepEnd - $stepStart'
          'Write-Output "STEP COMPLETED: VDOT optimization finished at $stepEnd (UTC) (Duration: $($duration.ToString()))"'
        ]
      }
BICEP

Again, this is in Bicep format, but pay special attention to lines 25 to 31, these are the registry settings needed to apply the regional settings for users. This is currently for the UK, but you can find your desired language settings references here and here.

Putting it all together

Now you have the above individual steps, let’s put it all together, as there are a couple of extra reboots as well, here are the full steps:

      {
        name: 'CONF_LanguagePackPreInstall_ApplyWindowsUpdates'
        type: 'WindowsUpdate'
      } 
      {
        name: 'CONF_LanguagePackPreInstall_RestartPreInstall'
        type: 'WindowsRestart'
        restartTimeout: '20m'
      }                                 
      {
        name: 'FILE_InstallLanguagePacks'
        type: 'File'
        destination: 'C:\\AIBTemp\\Production\\Configurations\\installLanguagePacks.ps1'
        sourceUri: 'https://raw.githubusercontent.com/Azure/RDS-Templates/master/CustomImageTemplateScripts/CustomImageTemplateScripts_2024-03-27/InstallLanguagePacks.ps1'
      }
      {
        name: 'CONF_InstallUKLanguagePack'
        type: 'PowerShell'        
        runAsSystem: true
        runElevated: true
        inline: [
          '$ErrorActionPreference = "Stop"'
          '$stepStart = Get-Date'
          'Write-Output "STEP STARTED: Installing UK Language Pack at $stepStart (UTC)"'
          '& "C:\\AIBTemp\\Production\\Configurations\\installLanguagePacks.ps1" -LanguageList "English (United Kingdom)"'
          '$stepEnd = Get-Date'
          '$duration = $stepEnd - $stepStart'
          'Write-Output "STEP COMPLETED: UK Language Pack installed at $stepEnd (UTC) (Duration: $($duration.ToString()))"'
        ]
      }
      {
        name: 'CONF_LanguagePackPostInstall_ApplyWindowsUpdates'
        type: 'WindowsUpdate'
      }
      {
        name: 'CONF_LanguagePackPostInstall_RestartPostInstall'
        type: 'WindowsRestart'
        restartTimeout: '20m'
      }
      {
        name: 'CONF_SetSystemLanguage_en-GB'
        type: 'PowerShell'
        runAsSystem: true
        runElevated: true
        inline: [
          '$ErrorActionPreference = "Stop"'
          '$stepStart = Get-Date'
          'Write-Output "STEP STARTED: Setting system language to en-GB at $stepStart (UTC)"'
          '$LanguageTag = "en-gb"'
          'Write-Output "Setting Win User Language List to prioritise: $LanguageTag"'
          '$OldList = Get-WinUserLanguageList'
          '$UserLanguageList = New-WinUserLanguageList -Language $LanguageTag'
          '$UserLanguageList += $OldList | Where-Object { $_.LanguageTag -ne $LanguageTag }'
          'Set-WinUserLanguageList -LanguageList $UserLanguageList -Force'
          'Write-Output "Setting system preferred UI language to: $LanguageTag"'
          'Set-SystemPreferredUILanguage -Language $LanguageTag'
          'Write-Output "Setting system locale to: $LanguageTag"'
          'Set-WinSystemLocale -SystemLocale $LanguageTag'
          'Write-Output "A restart is required to set language configurations"'
          '$stepEnd = Get-Date'
          '$duration = $stepEnd - $stepStart'
          'Write-Output "STEP COMPLETED: System language set to en-GB at $stepEnd (UTC) (Duration: $($duration.ToString()))"'
        ]
      }
      {
        name: 'REBOOT_SetSystemLanguageUK'
        type: 'WindowsRestart'
        restartTimeout: '20m'
      }
      {
        name: 'CONF_RunVDOTOptimisations'
        type: 'PowerShell'
        runAsSystem: true
        runElevated: true
        inline: [
          '$ErrorActionPreference = "Stop"'
          '[Console]::OutputEncoding = [System.Text.Encoding]::UTF8'
          '$PSDefaultParameterValues["Out-File:Encoding"] = "UTF8"'
          '$stepStart = Get-Date'
          'Write-Output "STEP STARTED: Running VDOT Optimization Tool Preparation at $stepStart (UTC)"'
          '$vdotZipUrl = "https://github.com/The-Virtual-Desktop-Team/Virtual-Desktop-Optimization-Tool/archive/refs/heads/main.zip"'
          '$vdotTempDir = "C:\\AIBTemp\\Production\\Configurations"'
          '$vdotZipPath = Join-Path $vdotTempDir "VDOT.zip"'
          '$vdotExtractedPath = Join-Path $vdotTempDir "Virtual-Desktop-Optimization-Tool-main"'
          '$defaultUserSettingsPath = Join-Path $vdotExtractedPath "2009\\ConfigurationFiles\\DefaultUserSettings.JSON"'
          'New-Item -ItemType Directory -Path $vdotTempDir -Force | Out-Null'
          'Write-Output "Downloading VDOT zip..."'
          'Invoke-WebRequest -Uri $vdotZipUrl -OutFile $vdotZipPath'
          'Write-Output "Extracting zip..."'
          'Expand-Archive -Path $vdotZipPath -DestinationPath $vdotTempDir -Force'
          'Write-Output "Unblocking files..."'
          'Get-ChildItem -Path $vdotExtractedPath -Recurse | Unblock-File'
          'if (Test-Path $defaultUserSettingsPath) {'
          '  Write-Output "Appending UK locale settings to DefaultUserSettings.JSON..."'
          '  $json = Get-Content $defaultUserSettingsPath -Encoding UTF8 | ConvertFrom-Json'
          '  $appendItems = @('
          '    [ordered]@{ HivePath = "HKLM:\\VDOT_TEMP\\Control Panel\\International"; KeyName = "Locale"; PropertyType = "STRING"; PropertyValue = "00000809"; SetProperty = "True" }'
          '    [ordered]@{ HivePath = "HKLM:\\VDOT_TEMP\\Control Panel\\International"; KeyName = "LocaleName"; PropertyType = "STRING"; PropertyValue = "en-GB"; SetProperty = "True" }'
          '    [ordered]@{ HivePath = "HKLM:\\VDOT_TEMP\\Control Panel\\International"; KeyName = "sCurrency"; PropertyType = "STRING"; PropertyValue = "\\u00A3"; SetProperty = "True" }'
          '    [ordered]@{ HivePath = "HKLM:\\VDOT_TEMP\\Control Panel\\International"; KeyName = "sShortDate"; PropertyType = "STRING"; PropertyValue = "dd/MM/yyyy"; SetProperty = "True" }'
          '    [ordered]@{ HivePath = "HKLM:\\VDOT_TEMP\\Control Panel\\International\\Geo"; KeyName = "Name"; PropertyType = "STRING"; PropertyValue = "GB"; SetProperty = "True" }'
          '    [ordered]@{ HivePath = "HKLM:\\VDOT_TEMP\\Control Panel\\International\\Geo"; KeyName = "Nation"; PropertyType = "STRING"; PropertyValue = "242"; SetProperty = "True" }'
          '  )'
          '  $json += $appendItems'
          '  $json | ConvertTo-Json -Depth 5 | Out-File -FilePath $defaultUserSettingsPath -Encoding UTF8 -Force'
          '  Write-Output "Locale entries appended to DefaultUserSettings.JSON."'
          '} else {'
          '  Write-Output "WARNING: DefaultUserSettings.JSON not found at $defaultUserSettingsPath"'
          '}'
          '$vdotScript = Join-Path $vdotExtractedPath "Windows_VDOT.ps1"'
          'if (Test-Path $vdotScript) {'
          '  Write-Output "Executing Windows_VDOT.ps1 with selected optimizations..."'
          '  & $vdotScript -Verbose -AcceptEula -Optimizations @('
          '    "Autologgers"'
          '    "DefaultUserSettings"'
          '    "LocalPolicy"'
          '    "NetworkOptimizations"'
          '    "ScheduledTasks"'
          '    "Services"'
          '    "WindowsMediaPlayer"'
          '  ) -AdvancedOptimizations @("Edge")'
          '  Write-Output "VDOT script completed."'
          '} else {'
          '  Write-Output "ERROR: VDOT.ps1 not found at $vdotScript"'
          '}'
          '$stepEnd = Get-Date'
          '$duration = $stepEnd - $stepStart'
          'Write-Output "STEP COMPLETED: VDOT optimization finished at $stepEnd (UTC) (Duration: $($duration.ToString()))"'
        ]
      }
      {
        name: 'CONF_VDOTOptimisations_RestartPostConfig'
        type: 'WindowsRestart'
        restartTimeout: '20m'
      }
BICEP

The Results

Once deployed, users logging in for the first time will see English (United Kingdom) set as their language and regional format — no extra steps needed. Like so:

Confirm Language and Regional settings via PowerShell

Bonus Content – GPO Method

But, we want to set the regional settings via group policy…. okay, I hear you, here’s the group policy settings to apply the regional settings (it won’t install language packs or set them as the default), here you go:

Any tips? Do you have an easier method? Would love to hear from you!

1 thought on “Configure AVD Languages with Azure Image Builder”

  1. Pingback: AVD Community Newsletter – 25th July 2025 – AVD Community

Leave a Reply...

Scroll to Top