I need to deploy a language pack for one of our offices via ConfigMgr. I have no idea how to do this!
What they want is for the language to appear in this section of Office:
I don’t know much of Office so I didn’t even know where to start with. I found this official doc on deploying languages and that talked about modifying the config file in ProPlus.WW\Config.xml. I spent a lot of time trying to understand how to proceed with that and even downloaded the huge ISOs from VLSC but had no idea how to deploy them via ConfigMgr. That is, until I spoke to a colleague with more experience in this and realized that what I am really after is the Office 2016 Proofing Toolkit. You see, language packs are for the UI – the menus and all that – whereas if you are only interested in spell check and all that stuff what you need is the proofing tools. (In retrospect, the screenshot above says so – “dictionaries, grammar checking, and sorting” – but I didn’t notice that initially).
So first step, download the last ISO in the list below (Proofing Tools; 64-bit if that’s your case).
Extract it somewhere. It will have a bunch of files like this:
The proofkit.ww
folder is your friend. Within that you will find folders for various languages. You can see this doc for a list of language identifiers and languages. In the root of that folder is a config.xml file with the following –
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<Configuration Product="Proofkit"> <!-- <Display Level="full" CompletionNotice="yes" SuppressModal="no" AcceptEula="no" /> --> <!-- <Logging Type="standard" Path="%temp%" Template="Microsoft Office Proofkit Setup(*).txt" /> --> <!-- <USERNAME Value="Customer" /> --> <!-- <COMPANYNAME Value="MyCompany" /> --> <!-- <INSTALLLOCATION Value="%programfiles%\Microsoft Office" /> --> <!-- <LIS CACHEACTION="CacheOnly" /> --> <!-- <LIS SOURCELIST="\\server1\share\Office;\\server2\share\Office" /> --> <!-- <DistributionPoint Location="\\server\share\Office" /> --> <!-- <OptionState Id="OptionID" State="absent" Children="force" /> --> <!-- <Setting Id="SETUP_REBOOT" Value="IfNeeded" /> --> <!-- <Command Path="%windir%\system32\msiexec.exe" Args="/i \\server\share\my.msi" QuietArg="/q" ChainPosition="after" Execute="install" /> --> </Configuration> |
By default this file does nothing. Everything’s commented out as you can see. If you want to additional languages, you modify the config.xml first and then pass it to setup.exe
via a command like setup /config \path\to\this\config.xml
. The setup
command is the setup.exe
in the folder itself.
Here’s my config.xml
file which enables two languages and disables everything else.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
<Configuration Product="Proofkit"> <!-- <Display Level="full" CompletionNotice="yes" SuppressModal="no" AcceptEula="no" /> --> <Display Level="none" CompletionNotice="no" SuppressModal="yes" AcceptEula="yes" /> <!-- <Logging Type="standard" Path="%temp%" Template="Microsoft Office Proofkit Setup(*).txt" /> --> <!-- <USERNAME Value="Customer" /> --> <!-- <COMPANYNAME Value="MyCompany" /> --> <!-- <INSTALLLOCATION Value="%programfiles%\Microsoft Office" /> --> <!-- <LIS CACHEACTION="CacheOnly" /> --> <!-- <LIS SOURCELIST="\\server1\share\Office;\\server2\share\Office" /> --> <!-- <DistributionPoint Location="\\server\share\Office" /> --> <!-- <OptionState Id="OptionID" State="absent" Children="force" /> --> <OptionState Id="ProofingTools_1086" State="local" Children="force" /> <OptionState Id="ProofingTools_1033" State="local" Children="force"/> <!-- explicitly disable the ones I don't need; probably not necessary but I came across a forum post where the author had to do this --> <OptionState Id="IMEMain_1028" State="absent" Children="force"/> <OptionState Id="IMEMain_1041" State="absent" Children="force"/> <OptionState Id="IMEMain_1042" State="absent" Children="force"/> <OptionState Id="IMEMain_2052" State="absent" Children="force"/> <OptionState Id="ProofingTools_1025" State="absent" Children="force"/> <OptionState Id="ProofingTools_1026" State="absent" Children="force"/> <OptionState Id="ProofingTools_1027" State="absent" Children="force"/> <OptionState Id="ProofingTools_1028" State="absent" Children="force"/> <OptionState Id="ProofingTools_1029" State="absent" Children="force"/> <OptionState Id="ProofingTools_1030" State="absent" Children="force"/> <OptionState Id="ProofingTools_1031" State="absent" Children="force"/> <OptionState Id="ProofingTools_1032" State="absent" Children="force"/> <OptionState Id="ProofingTools_1035" State="absent" Children="force"/> <OptionState Id="ProofingTools_1036" State="absent" Children="force"/> <OptionState Id="ProofingTools_1037" State="absent" Children="force"/> <OptionState Id="ProofingTools_1038" State="absent" Children="force"/> <OptionState Id="ProofingTools_1039" State="absent" Children="force"/> <OptionState Id="ProofingTools_1040" State="absent" Children="force"/> <OptionState Id="ProofingTools_1041" State="absent" Children="force"/> <OptionState Id="ProofingTools_1042" State="absent" Children="force"/> <OptionState Id="ProofingTools_1043" State="absent" Children="force"/> <OptionState Id="ProofingTools_1044" State="absent" Children="force"/> <OptionState Id="ProofingTools_1045" State="absent" Children="force"/> <OptionState Id="ProofingTools_1046" State="absent" Children="force"/> <OptionState Id="ProofingTools_1047" State="absent" Children="force"/> <OptionState Id="ProofingTools_1048" State="absent" Children="force"/> <OptionState Id="ProofingTools_1049" State="absent" Children="force"/> <OptionState Id="ProofingTools_1050" State="absent" Children="force"/> <OptionState Id="ProofingTools_1051" State="absent" Children="force"/> <OptionState Id="ProofingTools_1052" State="absent" Children="force"/> <OptionState Id="ProofingTools_1053" State="absent" Children="force"/> <OptionState Id="ProofingTools_1054" State="absent" Children="force"/> <OptionState Id="ProofingTools_1055" State="absent" Children="force"/> <OptionState Id="ProofingTools_1056" State="absent" Children="force"/> <OptionState Id="ProofingTools_1057" State="absent" Children="force" /> <OptionState Id="ProofingTools_1058" State="absent" Children="force"/> <OptionState Id="ProofingTools_1060" State="absent" Children="force"/> <OptionState Id="ProofingTools_1061" State="absent" Children="force"/> <OptionState Id="ProofingTools_1062" State="absent" Children="force"/> <OptionState Id="ProofingTools_1063" State="absent" Children="force"/> <OptionState Id="ProofingTools_1065" State="absent" Children="force"/> <OptionState Id="ProofingTools_1066" State="absent" Children="force"/> <OptionState Id="ProofingTools_1067" State="absent" Children="force"/> <OptionState Id="ProofingTools_1068" State="absent" Children="force"/> <OptionState Id="ProofingTools_1069" State="absent" Children="force"/> <OptionState Id="ProofingTools_1071" State="absent" Children="force"/> <OptionState Id="ProofingTools_1074" State="absent" Children="force"/> <OptionState Id="ProofingTools_1076" State="absent" Children="force"/> <OptionState Id="ProofingTools_1077" State="absent" Children="force"/> <OptionState Id="ProofingTools_1078" State="absent" Children="force"/> <OptionState Id="ProofingTools_1079" State="absent" Children="force"/> <OptionState Id="ProofingTools_1081" State="absent" Children="force"/> <OptionState Id="ProofingTools_1082" State="absent" Children="force"/> <OptionState Id="ProofingTools_1087" State="absent" Children="force"/> <OptionState Id="ProofingTools_1088" State="absent" Children="force"/> <OptionState Id="ProofingTools_1089" State="absent" Children="force"/> <OptionState Id="ProofingTools_1091" State="absent" Children="force"/> <OptionState Id="ProofingTools_1092" State="absent" Children="force"/> <OptionState Id="ProofingTools_1093" State="absent" Children="force"/> <OptionState Id="ProofingTools_1094" State="absent" Children="force"/> <OptionState Id="ProofingTools_1095" State="absent" Children="force"/> <OptionState Id="ProofingTools_1096" State="absent" Children="force"/> <OptionState Id="ProofingTools_1097" State="absent" Children="force"/> <OptionState Id="ProofingTools_1098" State="absent" Children="force"/> <OptionState Id="ProofingTools_1099" State="absent" Children="force"/> <OptionState Id="ProofingTools_1100" State="absent" Children="force"/> <OptionState Id="ProofingTools_1101" State="absent" Children="force"/> <OptionState Id="ProofingTools_1102" State="absent" Children="force"/> <OptionState Id="ProofingTools_1106" State="absent" Children="force"/> <OptionState Id="ProofingTools_1110" State="absent" Children="force"/> <OptionState Id="ProofingTools_1111" State="absent" Children="force"/> <OptionState Id="ProofingTools_1115" State="absent" Children="force"/> <OptionState Id="ProofingTools_1121" State="absent" Children="force"/> <OptionState Id="ProofingTools_1123" State="absent" Children="force"/> <OptionState Id="ProofingTools_1128" State="absent" Children="force"/> <OptionState Id="ProofingTools_1130" State="absent" Children="force"/> <OptionState Id="ProofingTools_1132" State="absent" Children="force"/> <OptionState Id="ProofingTools_1134" State="absent" Children="force"/> <OptionState Id="ProofingTools_1136" State="absent" Children="force"/> <OptionState Id="ProofingTools_1153" State="absent" Children="force"/> <OptionState Id="ProofingTools_1159" State="absent" Children="force"/> <OptionState Id="ProofingTools_1160" State="absent" Children="force"/> <OptionState Id="ProofingTools_1169" State="absent" Children="force"/> <OptionState Id="ProofingTools_2052" State="absent" Children="force"/> <OptionState Id="ProofingTools_2068" State="absent" Children="force"/> <OptionState Id="ProofingTools_2070" State="absent" Children="force"/> <OptionState Id="ProofingTools_2074" State="absent" Children="force"/> <OptionState Id="ProofingTools_2108" State="absent" Children="force"/> <OptionState Id="ProofingTools_2117" State="absent" Children="force"/> <OptionState Id="ProofingTools_3076" State="absent" Children="force"/> <OptionState Id="ProofingTools_3082" State="absent" Children="force"/> <OptionState Id="ProofingTools_3098" State="absent" Children="force"/> <OptionState Id="ProofingTools_5146" State="absent" Children="force"/> <!-- <Setting Id="SETUP_REBOOT" Value="IfNeeded" /> --> <Setting Id="SETUP_REBOOT" Value="Never" /> <!-- <Command Path="%windir%\system32\msiexec.exe" Args="/i \\server\share\my.msi" QuietArg="/q" ChainPosition="after" Execute="install" /> --> </Configuration> |
Step 2 would be to copy the setup.exe, setup.dll, proofkit.ww, and proofmui.en-us to your ConfigMgr content store to a folder of its own. It’s important to copy proofmui-en.us too. I had missed that initially and was getting “The language of this installation package is not supported by your system” errors when deploying. After that you’d make a new application which will run a command like setup.exe /config \path\to\this\config.xml
. I am not going into the details of that. These two blog posts are excellent references: this & this.
At this point I was confused again, though. Everything I read about the proofing kit made it sound like a one time deal – as in you install all the languages you want, and you are done. What I couldn’t understand was how would I go about adding/ removing languages incrementally? What I mean is say I modified this file to add Spanish and Portugese as languages, and I deploy the application again … since all machines already have the proofing kit package installed, and it’s product code is already present in the detection methods, wouldn’t the deployment silently ignore?
To see why this doesn’t make sense to me, here are the typical instructions (based on above blog posts):
- Copy to content store
- Modify
config.xml
with the languages you are interested in - Create a new ConfigMgr application. While creating you go for the MSI method and point it to the
proofkit.ww\proofkitww.msi
file. This will fill the MSI detection code etc. in ConfigMgr. - After that edit the application you created, modify the content location to remove the
proofkit.ww
part (because we are now going to runsetup.exe
from the folder above it), and modify the installation program in the Programs tab to besetup.exe /config proofkit.ww\config.xml
.
Notice how the uninstall program and detection method both have the MSI code of the MSI we targeted initially. So what do I do if I modify the config.xml
file later and want to re-deploy the application? Since it will detect the MSI code of the previous deployment it won’t run at all; all I can do is uninstall the previous installation first and then re-install – but that’s going to interrupt users, right?
Speaking to my colleagues it seems the general approach is to include all languages you want upfront itself, then add some custom detection methods so you don’t depend on the MSI code above, and push out new languages if needed by creating new applications. I couldn’t find mention of something like this when I Googled (probably coz I wasn’t asking the right questions), so here goes what I did based on what I understood from others.
As before, create the application so we are at the screenshot stage above. As it stands the application will install and will detect that it has installed correctly if it finds the MSI product code. What I need to do is add something extra to this so I can re-deploy the application and it will notice that inspite of the MSI being installed it needs to re-install. First I played around with adding a batch file as a second deployment type after the MSI deployment type, having it add a registry registry. Something like this:
1 2 3 4 5 |
@echo off SET KEY=OfficeProofingKit2016 SET VER=1 reg add HKLM\Software\MyFirm /v %KEY% /t REG_SZ /d %VER% |
This adds a key called OfficeProofingKit2016 with value 1. Whenever I change my languages I can update the version to kick a new install. I added this as a detection to the batch file detection type, and made the MSI deployment type a dependency of it. The idea being that when I change languages and update the batch file and detection method with a new version, it will trigger a re-run of the batch file which will in turn cause the MSI deployment type to be re-run.
That turned out to be a dead end coz 1) I am not entirely clear how multiple deployment types work and 2) I don’t think whatever logic I had in my head was correct anyways. When the MSI deployment type re-runs wouldn’t it see the product is already installed and just silently continue?! I dunno.
Fast forward. I took a bath, cleared my head, and started looking for ways in which I could just do both installation and tattooing in the same batch file. I didn’t want to go with batch files as they are outdated (plus there’s the thing with UNC paths etc). I didn’t want to do VBScript as that’s even more outdated :p and what I really should be doing is some PowerShell scripting to be really cool and do this like a pro. Which led me to the PowerShell App Deployment Toolkit (PSADT). Oh. My. God. Wow! What a thing.
The website’s a bit sparse on documentation but that’s coz you got to do download the toolkit and look at the Word doc in there and examples. Plus a bit of Googling to get you started with what others are doing. But boy, is PSADT something! Once you download the PSADT zip file and extract its contents there’s a toolkit folder with the following:
This folder is what you would copy over to the content store of whatever application you want to install. And into the “files” folder of this is where you’d copy all the application deployment stuff – the things you’d previously have copied into the content store. You can install/ uninstall by invoking the Deploy-Application.ps1
file or you can simple run the Deploy-Application.exe
file.
Notice I changed the deployment type to a script instead of MSI, as it previously was. The only program I have in that is the Deploy-Application.exe
.
And I changed the detection method to be the registry key I am interested in with the value I want.
That’s all. Now for the fun stuff, which is in the Deploy-Application.ps1
file.
At first glance that file looks complicated. That’s because there’s a lot of stuff in it, including comments and variables etc., but what we really need to concerting ourselves with is certain sections. That’s where you set some variables plus do things like install applications (via MSI or directly running an exe like I am doing here), do some post install stuff (which is what I wanted to do, the point for this whole exercise!), uninstall stuff etc. In fact, this is all I had to add to the file for my stuff:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
[string]$appVendor = 'Microsoft' [string]$appName = 'Office Proofing Kit 2016' [string]$appVersion = '2016' [string]$appArch = '' [string]$appLang = 'EN' [string]$appRevision = '01' [string]$appScriptVersion = '1.0.1' [string]$appScriptDate = '02/06/2019' # mm/dd/yyyy [string]$appScriptAuthor = 'Rakhesh Sasidharan' [string]$appRegKey = 'HKLM\SOFTWARE\MyFirm\Software' [string]$appRegKeyName = 'OfficeProofingKit2016' [string]$appRegKeyValue = '2' # !!when you change this version be sure to update the detection method!! ## <Perform Installation tasks here> Execute-Process -Path "$dirFiles\setup.exe" -Parameters "/config proofkit.ww\config.xml" ## <Perform Post-Installation tasks here> Set-RegistryKey -Key "$appRegKey" -Name "$appRegKeyName" -Value "$appRegKeyValue" -Type String -ContinueOnError:$True Update-GroupPolicy ## Display a message at the end of the install If (-not $useDefaultMsi) { Show-InstallationPrompt -Message 'New languages were successfully added to your Office 2016 installation. Please close and open Word, Outlook, etc. for the new languages to be enabled.' -ButtonRightText 'OK' -Icon Information -NoWait } # <Perform Uninstallation tasks here> Execute-MSI -Action Uninstall -Path '{90160000-00CC-0000-1000-0000000FF1CE}' ## <Perform Post-Uninstallation tasks here> Remove-RegistryKey -Key "$appRegKey" -Name $appRegKeyName |
That’s it! :) That takes care of running setup.exe
with the config.xml
file as an argument. Tattooing the registry. Informing users. And even undoing these changes when I want to uninstall.
I found the Word document that came with PSADT and this cheatsheet very handy to get me started.
Update: Forgot to mention. All the above steps only install the languages on user machines. To actually enable it you have to use GPOs. Additionally, if you want to change keyboard layouts post-install that’s done via registry key. You can add it to PSADT deployment itself. The registry key is HKEY_CURRENT_USER\Keyboard Layout\Preload
. Here’s a list of values.