Installing Language Packs – Windows 10 (WaaS)

Overview

You have numerous options to tackle language packs when deploying Windows 10. These include:

  1. Install language packs offline/online during reference image creation. 
    When dealing with a large number of languages, this will dramatically increase your install.wim size.
  2. Install languages packs offline with DISM manually
    The same goes as above, imagine supporting 20 languages, that’s a lot of bloat in your image.
  3. Create one image per language
    This is a terrible idea and you really shouldn’t do it.
  4. Install languages packs online during OSD
    This is the most dynamic way to achieve this, why install languages you do not need?

In any case you have two primary functions here. Installation and configuration of the language(s) per region.

Installing only the languages you actually want makes a lot of sense. Furthermore, its important to note that Windows 10 is an agile OS, you will be supporting at the very least two releases per year, so you really want to be able to remove as much manual work as possible.

Remember, language packs are specific to each Windows release. A 1607 LP will not work on a 1703 release.

Note: Windows 10 in-place upgrades only support the same base system language, you cannot upgrade a Windows 10 en-GB base system language with  Windows 10 en-US media.

System Language:

wrhn8qj

Note: This post was revised a number of times with updates (including screenshots) to cover bug fixes, therefore references to languages in each step may be mixed.

None of the steps i will outline below are particularly ground-breaking (or new) but I feel it’s useful to hash over the particulars. Additionally, this method of language application is very useful when you have a number of languages to support. We want to be able to easily add language packs to support the next OS release without reinventing the wheel each time.


Preparation

As stated previously , each OS release has a particular language pack. In the below examples, you will see references for Windows 10 1703 and Windows 10 LTSB 2016 (this is the 1607 build). Language packs are downloadable from the Volume Licensing page, see here for more information on downloading the correct LP ISO.

https://blogs.technet.microsoft.com/mniehaus/2017/04/26/finding-windows-10-language-packs/

The link above describes downloading media for just the base languages, if you want language features (OCR, handwriting, Text-To-Speech) these are located in the ‘Features on Demand’ downloads, also found on the VLSC.

Mount the downloaded ISO, create a language pack folder in your SCCM source folder, and then create a folder based from the release name (1703, 1607) and architecture (x86, x64) . Create subsequent subfolders with the language ID (en-GB, fr-FR, de-DE etc). This folder structure isn’t critically important, but it makes sense to be neat.

lp1

Language packs are no longer simply called ‘lp.cab‘ as they used to be, the naming format is now ‘Microsoft-Windows-Client-Language-Pack_x64_fr-fr.cab‘. In each language ID, you should place the base language file, and any of the ‘Features on Demand’ if this is applicable.

lp2

Create a basic Package in ConfigMgr for each language pack, your source path should be the root directory for each language ID. Do not create a program, name the packages accordingly.

lp3

We will be installing language packs based off a user selection during OSD. This definition can be made in many ways, you could have a UI for choosing a language (as i do), you could set collection variables on your OSD collections, or you could get be really clever and automate language selection based off a region (network range). It doesn’t really matter, the goal here is to simply define what language we want to actually end up with.

As mentioned, I use an excellent UI solution called UI++ . The built in MDT driven UDI will also work fine as well, all we want to do here is prompt for selection of a language.

You can choose to define all variables (UILanguage, Keyboard, TimeZone etc) within UI++ (or any other UI) or you can use the dynamic variables step used later in this post.

If you already use UI++ , the selection is simply;


<ChoiceInput Variable="Location" Question="Select Location" Required="true">
<Choice Option="Ireland" Value="IRE"/>
<Choice Option="UK" Value="UK"/>
<Choice Option="USA" Value="US"/>
<Choice Option="Spain" Value="ES"/>
<Choice Option="Germany" Value="DE"/>
<Choice Option="China" Value="CH"/>
</ChoiceInput>
</Action>

view raw

UIExample1.xml

hosted with ❤ by GitHub

The Variable we are defining here is ‘Location‘. This selection will be referenced later in the Task Sequence to dynamically assign the specific language pack we require.

We need a way to install and set language preferences per build, a very popular method is to dynamically populate the unattended xml with language values, see this post for information on this approach:

https://www.scconfigmgr.com/2014/01/30/create-an-answer-file-for-language-settings-during-osd-with-configmgr/

The method in this post uses a separate answer file which is called by the utilising the command rundll32.exe shell32,Control_RunDLL intl.cpl,,/f:”C:\Windows\temp\UI-Settings.xml”– this command sets the values defined in the Multilingual User Interface control panel applet.  See here for more information on the MUI.

https://support.microsoft.com/en-us/kb/2764405
https://technet.microsoft.com/en-us/library/cc721887(ws.10).aspx

I found this really helpful script originally written by Nicolas Lacour, which has now been modified. The script itself will install the language pack and then associate that language as default based off the values retrieved from the task sequence. This script should be saved into a generic OSD scripts folder, it does not need to be stored with the language pack reference files. The benefit of this is there is one script to control all language packs, regardless or version.


#########################
#
# Version 1.1 By Diagg | http://www.osd-couture.com
# Release Date 11/11/2016
# Version 1.3 by dpadgett | https://execmgr.net/
# Release Date 19/9/2017
# Thanks to Aida@Microsoft
#
##== Refs
# XMl File
# https://support.microsoft.com/en-us/kb/2764405
#
##== Keyboard ID
# https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-input-locales-for-windows-language-packs
#
##== Geo ID
# https://msdn.microsoft.com/en-us/library/dd374073.aspx
#########################
Write-Host '****************************************************'
Write-Host '**********Online Language Pack Injector*************'
##== Get MDT variables ($env:PSModulePath is requiered for SCCM CB and above… yet another regression from 2012R2…)
$env:PSModulePath = $env:PSModulePath + ";C:\_SMSTaskSequence\WDPackage\Tools\Modules"
Import-Module ZTIutility
##== Init
$Global:CurrentScriptName = $MyInvocation.MyCommand.Name
$Global:CurrentScriptFullName = $MyInvocation.MyCommand.Path
$Global:CurrentScriptPath = split-path $MyInvocation.MyCommand.Path
$srcXML = @"
<gs:GlobalizationServices xmlns:gs="urn:longhornGlobalizationUnattend">
<!– user list –>
<gs:UserList>
<gs:User UserID="Current" CopySettingsToDefaultUserAcct="true" CopySettingsToSystemAcct="true"/>
</gs:UserList>
<!– MUI preferences –>
<gs:MUILanguagePreferences>
<gs:MUILanguage Value="%UIID%"/>
<gs:MUIFallback Value="en-US"/>
</gs:MUILanguagePreferences>
<!– system locale –>
<gs:SystemLocale Name="%UIID%"/>
<!–User Locale–>
<gs:UserLocale>
<gs:Locale Name="%UIID%" SetAsCurrent="true" ResetAllSettings="false"/>
</gs:UserLocale>
<!–location–>
<gs:LocationPreferences>
<gs:GeoID Value="%GEOID%"/>
</gs:LocationPreferences>
<!– input preferences –>
<gs:InputPreferences>
<gs:InputLanguageID Action="add" ID="%KBID%" Default="true"/>
<gs:InputLanguageID Action="remove" ID="0409:00000409"/>
</gs:InputPreferences>
</gs:GlobalizationServices>
"@
#Call ENBASE XML Formatting when language is En-*, we dont need to set a fallback language here.
$srcXMLENBase = @"
<gs:GlobalizationServices xmlns:gs="urn:longhornGlobalizationUnattend">
<!– user list –>
<gs:UserList>
<gs:User UserID="Current" CopySettingsToDefaultUserAcct="true" CopySettingsToSystemAcct="true"/>
</gs:UserList>
<!– system locale –>
<gs:SystemLocale Name="%UIID%"/>
<!–User Locale–>
<gs:UserLocale>
<gs:Locale Name="%UIID%" SetAsCurrent="true" ResetAllSettings="false"/>
</gs:UserLocale>
<!–location–>
<gs:LocationPreferences>
<gs:GeoID Value="%GEOID%"/>
</gs:LocationPreferences>
<!– input preferences –>
<gs:InputPreferences>
<gs:InputLanguageID Action="add" ID="%KBID%" Default="true"/>
<gs:InputLanguageID Action="remove" ID="0409:00000409"/>
</gs:InputPreferences>
</gs:GlobalizationServices>
"@
#Fixes a bug in Windows 10 creators update where default keyboard on the welcome screen is not set.
$AddUSKBD = @"
<gs:GlobalizationServices xmlns:gs="urn:longhornGlobalizationUnattend">
<!– user list –>
<gs:UserList>
<gs:User UserID="Current" CopySettingsToDefaultUserAcct="true" CopySettingsToSystemAcct="true"/>
</gs:UserList>
<!– input preferences –>
<gs:InputPreferences>
<gs:InputLanguageID Action="add" ID="0409:00000409"/>
</gs:InputPreferences>
</gs:GlobalizationServices>
"@
##== Inject Languages Packs
## option to add /Quiet switch to dism to reduce smsts.log spam
$Files = Get-ChildItem $tsenv:LPDownloadPath01 -Recurse
write-host "Custom path var is: $tsenv:LPDownloadPath01"
write-host "Files found are: $tsenv:LPDownloadPath01"
#Note 'Download Package Content Step 'Save as var' will append numerical code to declared var (01)
Foreach ($file in $Files)
{
If ($File.extension.ToUpper() -eq ".CAB")
{
Write-Host ('intjecting package ' + $file.fullname)
$command = "dism /online /add-package /PackagePath:" + $file.fullname
Invoke-Expression $command
}
}
##== Edit XML
$destXML = "C:\Windows\Temp\UI-Settings.xml"
$destXML2 = "C:\Windows\Temp\UI-Settings-USKBD.xml"
$VarNameLng = '%UIID%'
Write-Host ('value read from tsenv:UILanguage : ' + $tsenv:UILanguage)
$lng = $tsenv:UILanguage
Write-Host ('Promoted UI:' + $lng)
$VarNameKB = '%KBID%'
Write-Host ('value read from tsenv:KeyboardLocale : ' + $tsenv:KeyboardLocale)
$KB = $tsenv:KeyboardLocale
$VarNameGEO = '%GEOID%'
Write-Host ('value read from tsenv:GeoID : ' + $tsenv:GeoID)
$GEO = $tsenv:GeoID
##== Quick Fix for an UDI bug that cast out truncated Keyboard ID
$KBdigit=$KB.substring(9,4)
$KB = ($KBdigit + ":0000" + $KBdigit)
Write-Host ('Promoted Keyboard:' + $KB)
##== Set Selected Language as the default UI
if ($lng -like 'en-*') {
$content = $srcXMLENBase -replace $VarNamelng, $lng
}
else
{
$content = $srcXML -replace $VarNamelng, $lng
}
$content = $content -replace $VarNameKB, $KB
$content = $content -replace $VarNameGEO, $GEO
Write-Host ("Creating XML language file in " + $destXML)
Out-File $destXML -InputObject $content
Write-Host ("Creating XML language file for USKBD " + $destXML2)
Out-File $destXML2 -InputObject $AddUSKBD
Write-Host 'Online Language Pack injection finished !!'
Write-Host '****************************************************'

The script is performing the following functions:

  1. Loading in TS Variables using MDT (MDT Toolkit step must be added at some point before this)
  2. Defining a language file which will eventually be saved
  3. Installing any .cab files (language packs) in the path defined in %LPDownloadPath01%
  4. Modifying the XML template with values stored in the variables of %UILanguage%, %KeyboardLocale% and %GeoID%
  5. Outputting formatted XML files to c:\windows\temp

The final piece of the puzzle is to call the generated XML file, this is called separately:

rundll32.exe shell32,Control_RunDLL intl.cpl,,/f:”C:\Windows\temp\UI-Settings.xml”

Note: Double check copy/paste doesn’t  mess the quotes up! ” “


Create TS Steps

Now its time to put all of this together. Open your Windows 10 Task Sequence and ensure you have a mechanism in place to define which language you want to use. Remember, we are defining the custom Location variable. At any point after your Setup Windows & ConfigMgr step and build following structure.

Add ‘Localisation‘ group and within it set a ‘Set Dynamic Variables‘ step. Within this step we want to define the variables for our script, %KeyboardLocale%, %UILanguage%, %TimeZone% and %GeoID% (I suggest you review this page for an overview of what these values actually do). These variables should switch depending on the language we have chosen to define (via the Language variable )

GEO ID
https://msdn.microsoft.com/en-us/library/dd374073.aspx

Keyboard ID
https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/language-packs-and-windows-deployment

You can also add a step to define the local Timezone based off the language selection. Given languages may have multiple time zones, you may want to specify the %TimeZone% variable seen here within its own UI menu.

https://msdn.microsoft.com/en-us/library/ms912391(v=winembedded.11).aspx

2018-06-11 12_19_39-irlsccm01 - Remote Desktop Connection

To apply the defined timezone, add a ‘Run Command Line’ step called ‘Set Time Zone’, add the command;

c:\Windows\System32\tzutil.exe /s “%TimeZone%”
Note: Double check copy/paste doesn’t  mess the quotes up! ” “

On the Language Packs root step , add a condition to not process if the Location is US. I am using en-US base media, and there is no need for me to install any language packs if this is the selected language.

2018-06-11 12_30_32-irlsccm01 - Remote Desktop Connection

Next, create groups per language you want to install, on these groups add a condition of the Location. For example on my ‘German LP’ step I would have:

nva7du8

Next, add a ‘Download Package Content‘ step. Browse to the relevant language package we created earlier. Tick ‘Save Path as Variable’ and set the value to LPDownloadPath.

cd5wfwp

This step is defined so that we can split out our Install script and package content so they are independent from each other. We always know that this package will download and store in the variable LPDownloadPath so we can utilise that variable in the script. This step saves us from storing a install script in the package source directory of every language pack, this allows for easy modification of the script as there is only one copy of it.

Note: You will notice the definition of LPDownloadPath is referred to as LPDownloadPath01 in the PS script , this is due to the TS engine appending 01 to the defined variable. Do not set the ‘Save path as a variable’ as LPDownloadPath01 as this would become LPDownloadPath0101.

Repeat this process for each language pack you wish to install. Next, call our PowerShell script in a standard ‘Run PowerShell Script‘ TS step, this should be stored in your generic scripts package or on its own. Be sure to set the execution policy to ‘Bypass

5kxyoxe

English speaking countries will use ‘en-US’ as a default display language but obviously have their own Keyboard, Region, and GeoID.  To cope with this, the PowerShell script (Set-LanguageOnline.ps1) has two separate formats for the UI-Settings.xml. The script will use the format of ‘srcXMLENBase‘ if the UILanguage variable begins with en-.

(e.g . en-IE, en-GB, en-AU)

This format omits the  section as it is not required. This logic can be seen on like 70 of the PS script. Typically you wont install a language pack for English base languages therefore the download content step can be omitted, however if you would like language features such as OCR and handwriting you could of course leave the download step in. 

Now we need to set a ‘Restart Computer (Pre-language Preferences)’ step, this is required for the next step (the application of the XML) to succeed.

Do not omit this Restart step

Place the restart step outside of each language packs logic group so we only need to define it once. Immediately after restart computer, set a ‘Run Command Line‘ step and use the following command:

rundll32.exe shell32,Control_RunDLL intl.cpl,,/f:”C:\Windows\temp\UI-Settings.xml”
Note: Double check copy/paste doesn’t  mess the quotes up! ” “

a01ui4z

This command will apply the language preference file that was generated by the PS script.

Check SMSTS.log for entries for the steps we have defined.

The CAB file is located and installed, you can suppress the DISM % output by defining /Quiet within the DISM command.

mstsc_2017-09-20_23-09-02

The Script has read in the defined variables and created the XML file.

mstsc_2017-09-20_23-07-58

mstsc_2017-09-20_23-03-12

Add an additional reboot in the ”Restart Computer (Post-language Preferences)’ step.

You probably want to include the US keyboard layout as an option for each build regardless of the default language you deploy. There is a bug in Windows 10 1709 onward that will cause the ‘default’ parameter in the ‘InputLanguageID’ XML element to be ignored on the Welcome screen, this leads to the US keyboard layout always being default. This will cause an issue with foreign keyboard layouts for users logging in the first time, as their usernames and passwords will likely fail.

(Note – this has been tested on 1809 and the bug is still present)

To overcome this, the PowerShell script will;

  1. Remove the US keyboard layout, set the desired layout as default.
  2. Add the US keyboard layout as a separate task sequence step, a separate XML is exported to C:\Windows\temp\UI-Settings-USKBD.xml” and imported.

The task sequence step ‘Apply Language Preferences (USKBD)’ is called.

deeygc8

This step exports the UI-Settings-USKBD.xml file to C:\Windows\Temp

57eofop

The XML layout simply adds the US keyboard, but does not set it as default.

kkvcr68

The end result is the desired keyboard layout becoming default at the welcome screen.

tnoabt1

 

To finish the process, you will need an additional reboot step. I define the SMSTS variable of SMSTSPostAction to ‘shutdown /r /t 15‘ at the beginning of all my Task Sequences, this covers the final reboot.

mstsc_2017-09-20_23-13-34

If everything was defined correctly, you should have the correct language set defined.

All changes to the Windows language settings are captured in the ‘Microsoft\Windows\International’ event log.

 

 

 

I hope this is useful for everyone, please leave any questions or comments below.

Regards,

Dan

 

15 thoughts on “Installing Language Packs – Windows 10 (WaaS)

  1. Hi Its really good but unable to follow aobut en-GB as i am in the process of installing language pack for en-GB

    Like

  2. Dear Dan,

    First, thanks a lot this very interesting post.
    Regarding the first restart step, how is it supposed to be configured ?
    Because when I restart my OS, I’m on the D: Drive which generates an error when I want to proceed to the “Apply Language prefrences” step

    Thanks

    Like

    1. Hi Frederic,

      The restart step is a simple the Restart into : ‘currently installed default operating system’.

      This should reboot the machine back into windows and resume the task sequence.

      Like

  3. Thanks !
    it seems to work now.
    The only Strange behavior concerns the fact that I’m prompted to select English or French during the deployment process.
    The UI-settings is present and filled with the correct values.
    “Dism /online /get-intl” gives me the expected resuls.
    Any idea?

    Like

  4. Thank you for this process, what if I’m needing to install the following languages during the imaging process, de-de, es-es,fr-fr,ja-ja,zh-cn, and en-Us?

    Like

      1. More info, so we have to image a lab of workstations and they much contain all of the languages I listed, your’s seems more tasked for users to select one specific language. How would I go about setting the task sequence to install all of those language packs?

        Like

    1. You should be fine to add multiple language pack steps to the download package offline step , that will allow dism to then install recursively each language pack it finds. The script will then set a primary language but at least they will all be installed

      Like

  5. Hi. Have you tried this in 1803 version of windows 10?. I can install the LPs correctly but after installation there is no second language selectable.

    Like

  6. Hi Dan,

    I have another concern regarding Full media ISOs.
    As the “Download Package Content” step is not supported in that scenario, what would be the best workaround to work with according to you ?

    Like

Leave a comment