You have numerous options to tackle language packs when deploying Windows 10. These include (but not limited to);
- 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.
- 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.
- Create one image per language
This is a terrible idea and you really shouldn’t do it.
- 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 wants 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.
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.
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.
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.
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.
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.
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.
I will be writing an in-depth review of UI++ in time, stay tuned.
If you already use UI++ , the selection is simply;
The Variable we are defining here is ‘UILanguage‘. 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:
The method in this post uses a separate answer file which is called by the utilising the command ‘control.exe intl.cpl,,/f:”c:\Unattend.xml” – this command sets the values defined in the Multilingual User Interface control panel applet. See here for more information on the MUI.
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 pack 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 1 script to control all language packs, regardless or version.
The script is performing the following functions:
- Loading in TS Variables using MDT
- Defining a language file which will eventually be saved
- Installing any .cab files (language packs) in the path defined in %LPDownloadPath01%
- Modifying the XML template with values stored in the variables of %UILanguage%, %KeyboardLocale% and %GeoID%
- Outputting a formatted XML to c:\windows\temp
The final piece of the puzzle is to call the generated XML file, this is called separately:
cmd.exe /C control.exe 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 UILanguage 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 our two remaining variables for our script, %KeyboardLocale% 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 UILanguage )
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.
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%”
On the Language Packs root step , add a conditon to not process if the UILanguage is en-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.
Next, create groups per language you want to install, on these groups add a condition of UILanguage equals %LangID%. For example on my ‘Install French Language Packs‘ I would have:
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.
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.
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‘
Nearly done, now we need to set a ‘Restart Computer’ step, this is required for the next step (the application of the XML) to succeed.
If you omit this Restart step, the next step (setting the xml) will fail with exit code 1
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:
cmd.exe /C control.exe intl.cpl,,/f:”C:\Windows\temp\UI-Settings.xml”
Note. One user reported issues with chinese keyboard ID’s (error code 1) this was fixed changing the command line to:
rundll32.exe shell32,Control_RunDLL intl.cpl,,/f:”C:\Windows\temp\UI-Settings.xml”
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.
The Script has read in the defined variables and created the XML file.
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.
If everything was defined correctly, you should have the correct language set defined.
I hope this is useful for everyone, please leave any questions or comments below.