Create a multi-lingual / multi-language MSI using WiX and custom build scripts

Some people might find this useful, so I thought I’d write it up. If you want to create a multi-language MSI (Windows Installer package) that works without a bootstrapper executable, this is one possible way to do it. It relies on an undocumented feature of Windows Installer, so proceed with caution/at your own risk. Testing your final output is a must.

Anyway, according to this article at installsite.org, you can embed transforms into an MSI in a way so that Windows Installer will automatically apply them according to the system language when your MSI is loaded. This is the undocumented feature. If your MSI is simple enough that you don’t need dialog boxes/UI, but you just blaze through a default install, yet you want the few messages that show up by default to be localized, what I’m about to describe might be enough for you.

Here’s what you need: WiX (Windows Installer XML toolset), MsiTran.exe and some sample MSI scripts from the Windows SDK, and finally some custom-written scripts to glue the whole thing together.

I’ll assume you already have WiX installed (possibly in conjunction with Visual Studio), and that you have a simple, single-language WiX project handy.

You can get the Windows SDK from Microsoft as an ISO image (this one is for Windows 7). You can then mount this image using Magic Disc, Daemon Tools Lite or some other ISO-peeking utility. If you don’t want to install the entire SDK just to get the MsiTran.exe and MSI script samples, open the ISO up using your tool of choice and find the folder Setup.

To install just the tools package that will give you the MsiTran.exe, run WinSDKTools\WinSDKTools_x86.msi. MsiTran.exe will be found in %ProgramFiles%\Microsoft SDKs\Windows\v7.0\Bin after this. I suggest copying this file to your WiX installer project folder.

To get the script samples, run the MSI WinSDKSamples\WinSDKSamples_x86.msi. Once you’ve done this, you’ll find the sample scripts in %ProgramFiles%\Microsoft SDKs\Windows\v7.0\Samples\SysMgmt\Msi\scripts. Out of all these scripts you only need WiSubStg.vbs and WiLangId.vbs. I suggest copying them to your WiX installer project folder.

You should know ahead of building the original WiX project which languages and how many languages you’ll end up needing. Your original installer needs to list all of these in a comma separated list in the Summary Stream (and the list can’t contain 0). In WiX that looks like this:

<Package 
  Compressed="yes" 
  Description="My Installer"
  InstallerVersion="200"
  Comments="My Software Installer"
  Languages="1033,1031,1028,2052,1030,1043,1035,1036,1040,1041,1042,1044,1046,1034,1053,1049,1055,1045,2070"
/>

Once you’ve built the single language MSI using WiX, you run a script for each language you need your MSI localized in that does the following:

  • Copy the original MSI to a new file
  • Modify the newly copied MSI so it contains a different ProductLanguage using WiLangId.vbs
  • Create a transform that captures the difference between the two MSIs using MSITran.exe
  • Embed the transform in the final master installer using WiSubStg.vbs

Here’s the script that does it (I call this CreateEmbedLangTransform.cmd):

set MsiName=%1
set lang=%2
set langcode=%3
copy %MsiName%.msi %MsiName%_%lang%.msi
cscript WiLangId.vbs %MsiName%_%lang%.msi Product %langcode% > CreateLangTransform_%lang%.txt
MsiTran.exe -g %MsiName%.msi %MsiName%_%lang%.msi %lang%.mst >> CreateLangTransform_%lang%.txt
cscript wisubstg.vbs FinalMasterInstaller\%MsiName%.msi %lang%.mst %langcode% >> CreateLangTransform_%lang%.txt
cscript wisubstg.vbs FinalMasterInstaller\%MsiName%.msi >> CreateLangTransform_%lang%.txt

All you have to do as a post-build step (either in Visual Studio or as part of your build engine of choice) is call this script once for each language you’re interested in (I call this CreateLocalizedInstallerAllLanguages.cmd):

if not exist FinalMasterInstaller md FinalMasterInstaller
copy MyInstaller.msi FinalMasterInstaller
call CreateEmbedLangTransform.cmd MyInstaller da 1030
call CreateEmbedLangTransform.cmd MyInstaller de 1031
call CreateEmbedLangTransform.cmd MyInstaller es 1034
call CreateEmbedLangTransform.cmd MyInstaller fi 1035
call CreateEmbedLangTransform.cmd MyInstaller fr 1036
call CreateEmbedLangTransform.cmd MyInstaller it 1040
call CreateEmbedLangTransform.cmd MyInstaller jp 1041
call CreateEmbedLangTransform.cmd MyInstaller ko 1042
call CreateEmbedLangTransform.cmd MyInstaller nl 1043
call CreateEmbedLangTransform.cmd MyInstaller no 1044
call CreateEmbedLangTransform.cmd MyInstaller pt_br 1046
call CreateEmbedLangTransform.cmd MyInstaller sv 1053
call CreateEmbedLangTransform.cmd MyInstaller zh_cn 2052
call CreateEmbedLangTransform.cmd MyInstaller zh_tw 1028
call CreateEmbedLangTransform.cmd MyInstaller ru 1049
call CreateEmbedLangTransform.cmd MyInstaller tr 1055
call CreateEmbedLangTransform.cmd MyInstaller pl 1045
call CreateEmbedLangTransform.cmd MyInstaller pt_pt 2070

You’ll need to look up additional language codes in Microsoft’s documentation if you need more than the ones listed above.

Note that I’ve modified the scripts above from the ones I’m actually using to make them a little more generic, and I haven’t tested every aspect of them, but they give you the gist of it.

Here are the script files zipped up.

5 Comments

  1. Andrew

    I know this is a pretty old post, but is there any chance you could translate your VB code to C#?

    I’m pretty new at C#, and I’m crazy bad at VB.net, but I’m supposed to be done with a project for the ‘ole day-job on Monday.

    I’m done with the project, now all I have to do is make the .msi(s). The way they used to do it was a manual deployment of one .msi per language, but this looks like the perfect solution to all of my remaining problems. (assuming I can figure out how to dynamically generate this xml appropriately, and incorporate your scripts into our build server)

    Greatly appreciated,
    Andrew

  2. Andrew

    Wow, now I feel retarded.

    On my way back to the top of the page I actually looked at your scripts. 😛 Never mind about that whole translating to C# idea, lol.

    This is exactly what I’ve been looking for all day. Thanks a lot!

  3. Guido

    Works perfectly during install. One issue I noticed is that during uninstall, the original install time language is used instead of the current system language. Is there any way to get the uninstaller to use the system language instead?

  4. I don’t know about that. It’s been a while sine I posted this, and I’ve moved on to other things now. Google it on Bing?

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.