Friday, October 18, 2019

WVD and VDI automation: Change in language pack installation on Windows 10 1809 and higher

Currently I’m working on a Windows 10 Enterprise 1903 CAD-VDI project now. One goal of this project is to deploy the golden master image with Automation.

My customer has Spanish, French, English, Dutch, Romanian and Russian speaking employees and needs the Windows User Interface in this language. As of Windows 1809 and higher, Microsoft changed things with the language pack installation. They call this a Local Experience Pack (LXP in short).

Prior to Windows 10 1809 an installation of a Language Interface Pack (LIP in short) was easy. Just download the lp.cab of the language pack and install it with DISM or lpksetup.exe. Example old commands:

  • dism.exe /online /add-package /packagepath:D:\langpacks\de-de\lp.cab
  • lpksetup /i es-ES /r /s /p D:\langpacks\es-es\lp.cab
Installation of LXP are different then good old LIPs. So, this is what I want to share with you. More information here: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/add-language-packs-to-windows

The User Experience changed when using LXP in Windows 10 >=1809. With LXP, things are more user focused from the modern settings panel. And more user input is needed, which is difficult for automation. If an end-user wants to select their “Windows Display Language”. The user needs to add a preferred language first (even if the LXP is already installed on the machine level). After that, he’s able to select the Windows Display Language of choice (which is a subset of the user defined preferred language).


If you want to automate this process in a pooled VDI or Windows Virtual Desktop environment all the workers need to have the LXP installed upfront or your users need admin rights. If you want to automate the installation of LXP to a golden master or persistent images, the process is as follows:
  1. Download the “Windows 10 Language Pack” ISO for your Windows build from the Windows Volume Licensing or MSDN website. (i.e. “Windows 10 Language Pack, version 1903”) 
  2. Install the required Language Pack cab language files to the system. (i.e. “Microsoft-Windows-Client-Language-Pack_x64_es-es.cab”)
  3. Install the corresponding LXP APPX file to the system. (i.e. “LanguageExperiencePack.es-ES.Neutral.appx”)
  4. Add the language to the “preferred language” per user!

I’ve created a powershell script, which automates the steps 2 to 4. The script can be downloaded here: https://vandenbornit-my.sharepoint.com/:u:/g/personal/patrick_vandenborn_it/EdzXPl6eDB5Fj-uDxgRZsf4BkyMyGjRDZ050r62PoVs3eA?e=7yMJIK or copy paste the code at the bottom of this blog.


Use the powershell script as follows:
1.    Create a folder of choice and create two subfolders (LangPacks and LXP)


2.    Copy the cab files from the ISO (x86 or x64 folder) to the LangPacks directory


3.    Copy the corresponding LXP folders form the ISO (LocalExperiencePacks) to the LXP directory


4.    Edit the Install_Win10_LXP.ps1 script and change the global settings:

 

5.    Add the Install_Win10_LXP.ps1 to your automation software or run as admin from PowerShell

6.    The scripts detect missing files


7.    When the script finishes, a powershell command is generated. Use this as a RunOnce login script for each user in your WVD or VDI environment:


8.    Run the powershell script in user context

###########################################
# PowerShell Script for users or login script
###########################################
$prefered_list = Get-WinUserLanguageList
$prefered_list.Add("es-es")
$prefered_list.Add("fr-fr")
$prefered_list.Add("nl-nl")
Set-WinUserLanguageList($prefered_list) -Force


9.    This adds the installed languages to the “preferred language list” automatically:

 The powershell script:
#############################
# Install Language pack Win10 1809 and higher
#
# Created by Patrick van den Born (vandenborn.it)
#
# MS Switched from lp.cab to APPX also called Local Experience Packs
# More info: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/add-language-packs-to-windows
#
# This tool installs the needed CAB and APPX packages
#
# You can download the Language Pack from your volume licensing channel website
#
# In my case: "Windows 10 Language Pack, version 1903"
#
# Create a folder structure like this
#
# \LangPacks         i.e. D:\Win10-1903\LangPacks --> Root folder for LangPacks
# \LXP               i.e. D:\Win10-1903\LXP --> Root folder for Local Experience Pack
#
# Then copy required cab from ISO x64 or x86 folder, for example
# \LangPacks\Microsoft-Windows-Client-Language-Pack_x64_es-es.cab
# \LangPacks\Microsoft-Windows-Client-Language-Pack_x64_fr-fr.cab
# \LangPacks\Microsoft-Windows-Client-Language-Pack_x64_nl-nl.cab
#
# Then copy corresponding Local Experience Pack folders from ISO LocalExperiencePack folder, for example
# \LXP\es-es\LanguageExperiencePack.es-ES.Neutral.appx
# \LXP\es-es\License.xml
# \LXP\fr-fr\LanguageExperiencePack.fr-FR.Neutral.appx
# \LXP\fr-fr\License.xml
# \LXP\nl-nl\LanguageExperiencePack.nl-NL.Neutral.appx
# \LXP\nl-nl\License.xml
#
# The cab and appx files needs to be installed as Admin
#
# To activate the languages, this should be started as user and is marked at the end of this script
#
###############################################

#####################
# Global script vars
#####################
$lp_root_folder = "D:\Win10-1903" #Root folder where the copied sourcefiles are
$architecture = "x64" #Architecture of cab files
$systemlocale = "nl-NL" #System local when script finishes

#####################
# Start installation of language pack on Win10 1809 and higher
#####################
$installed_lp = New-Object System.Collections.ArrayList
foreach ($language in Get-ChildItem -Path "$lp_root_folder\LXP") {
    #check if files exist

    $appxfile = $lp_root_folder + "\LXP\" + $language.Name + "\LanguageExperiencePack." + $language.Name + ".Neutral.appx"
    $licensefile = $lp_root_folder + "\LXP\" + $language.Name + "\License.xml"
    $cabfile = $lp_root_folder + "\LangPacks\Microsoft-Windows-Client-Language-Pack_" + $architecture + "_" + $language.Name + ".cab"
   
    if (!(Test-Path $appxfile)) {
        Write-Host $language.Name " - File missing: $appxfile" -ForegroundColor Red
        Write-Host "Skipping installation of "  + $language.Name
    } elseif (!(Test-Path $licensefile)) {
        Write-Host $language.Name " - File missing: $licensefile" -ForegroundColor Red
        Write-Host "Skipping installation of "  + $language.Name
    } elseif (!(Test-Path $cabfile)) {
        Write-Host $language.Name " - File missing: $cabfile" -ForegroundColor Red
        Write-Host "Skipping installation of "  + $language.Name
    } else {
        Write-Host $language.Name " - Installing $cabfile" -ForegroundColor Green
        Start-Process -FilePath "dism.exe" -WorkingDirectory "C:\Windows\System32" -ArgumentList "/online /Add-Package /PackagePath=$cabfile /NoRestart" -Wait

        Write-Host $language.Name " - Installing $appxfile" -ForegroundColor Green
        Start-Process -FilePath "dism.exe" -WorkingDirectory "C:\Windows\System32" -ArgumentList "/online /Add-ProvisionedAppxPackage /PackagePath=$appxfile /LicensePath=$licensefile /NoRestart" -Wait

        Write-Host $language.Name " - CURRENT USER - Add language to preffered languages (User level)" -ForegroundColor Green
        $prefered_list = Get-WinUserLanguageList
        $prefered_list.Add($language.Name)
        Set-WinUserLanguageList($prefered_list) -Force

        $installed_lp.Add($language.Name)
    }
}

Write-Host "$systemlocale - Setting the system locale" -ForegroundColor Green
Set-WinSystemLocale -SystemLocale $systemlocale

#Generating output for script

Write-Host " "
Write-Host "###########################################" -ForegroundColor Blue
Write-Host "PowerShell Script for users or login script" -ForegroundColor Blue
Write-Host "###########################################" -ForegroundColor Blue
Write-Host '$prefered_list = Get-WinUserLanguageList' -ForegroundColor Blue

For ($i = 0; $i -ne $installed_lp.Count; $i++) {
    Write-Host '$prefered_list.Add(' $installed_lp[$i] ')' -ForegroundColor Blue
}

Write-Host 'Set-WinUserLanguageList($prefered_list) -Force' -ForegroundColor Blue