Scott Hanselman

BATCH FILE VOODOO: Determine if multiple (and which) versions of an MSI-installed Product are installed using UpgradeCode

June 23, 2004 Comment on this post [1] Posted in Tools | XML
Sponsored By

We have a product that supports side-by-side installs, and I wanted to enumerate all the versions of that product and display it's version/product name.  Sure, it could be done in yet-another-C# program, but why not use Batch?

Every MSI installer has an UpgradeCode GUID that can't change for the life of the product.  The ProductCode and PackageCode may change, but the UpgradeCode is what tells us if the product is already installed.  At that point the installer can decide if it wants to upgrade or just install along side.  Those UpgradeCodes (for all apps) are in HKCU\Software\Microsoft\Installer\UpgradeCodes\ with SubKeys for each Product while the Products are in HKCU\Software\Microsoft\Installer\Products\ with details for each like Version, ProductName, etc. In this example I'm getting the ProductName which includes the version for me, but of course you can get anything you want.

NOTE: We're using REG.EXE which is included in the PATH on 2003 and XP but is an add-on to Windows 2000.

Here's the voodoo (all one line).  Note - The delims= includes a TAB, then a space:

FOR /F "tokens=1 skip=4 delims=        " %%A IN ('REG QUERY HKCU\Software\Microsoft\Installer\UpgradeCodes\<yourUpgradeCodeGUIDHere> /s') DO FOR /F "tokens=5 delims=            " %%Z IN ('REG QUERY HKCU\Software\Microsoft\Installer\Products\%%A /v ProductName') DO ECHO %%Z

Here it is broken down:

FOR /F "tokens=1 skip=4 delims=           " %%A
IN (
       'REG QUERY HKCU\Software\Microsoft\Installer\UpgradeCodes\<yourUpgradeCodeGUIDHere> /s'
     FOR /F "tokens=5 delims=            " %%Z 
         IN (
             'REG QUERY HKCU\Software\Microsoft\Installer\Products\%%A /v ProductName'
            ECHO %%Z

Thanks to Rob van der Woude's great (and oft-updated) Batch File site.

UPDATE: Some interesting details on compressed GUIDs from John Walker. Thanks John!:


Thanks for the help with REG.exe and FOR /F. My requirement was to be
able to uninstall any installed version of a product in an automated
fashion via a batch file. Because we don't know the exact product codes
that might be installed, we start our search with the upgrade code.

Initially, I had some trouble determining the values to use for:

What I found is that MSIs record 'compressed' GUIDs in the registry for
upgrade and product codes. So if you want to search the registry for
UpgradeCodes, you will need to search for the compressed code.
If <yourUpgradeCodeGUIDHere> is:
Then the compressed version you should search for is:
explains the process for converting an upgrade code.

I also searched a different location to determine product codes at:
HKCR\Installer\UpgradeCodes\ based on information on Windows Installer
registry locations from: because we
install per-machine.

The value(s) stored under an UpgradeCode path in the registry will be
compressed product codes. In order to run an msiexec uninstall you need
an uncompressed code in the {8-4-4-4-12} format. I ended up writing a
small console app to do the translations between compressed and
uncompressed codes. (Why does MSI compress the codes?) Here is the meat
of the translator (errorhandling and usage message code removed):

   class GuidCompressor
       /// <example>GuidCompressor.exe {abcdefgh-ijkl-mnop-qrst-uvwxyz123456} N
       /// returns: hgfedcbalkjiponmrqtsvuxwzy214365</example>
       /// <example>GuidCompressor.exe hgfedcbalkjiponmrqtsvuxwzy214365 B
       /// returns: {abcdefgh-ijkl-mnop-qrst-uvwxyz123456}</example>
       static void Main(string[] args)
           Guid origGuid = new Guid(args[0]);
           //outputFormat should be N, D, B, P
           string outputFormat = args[1];

           string raw = origGuid.ToString("N");
           char[] aRaw = raw.ToCharArray();
           //compressed format reverses 11 byte sequences of the original guid
           int[] revs
               = new int[]{8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2};
           int pos = 0;
           for (int i = 0; i < revs.Length; i++)
               Array.Reverse(aRaw, pos, revs[i]);
               pos += revs[i];
           string n = new string(aRaw);
           Guid newGuid = new Guid(n);
           //GUID in registry are all caps.

Combining the GuidCompressor, UpgradeCodeGuid, and HKCR registry
location here is the batch file that I use to uninstall:
Delims are <tab><space>

SET UncompressedUpgradeCodeGUID={abcdefgh-ijkl-mnop-qrstuvwxyz123456}
SET logfile=C:\temp\mylogfile.txt
SET binpath=C:\tools
FOR /F %%A IN ('%binpath%\GuidCompressor.exe
%UncompressedUpgradeCodeGUID%   N') DO (
       FOR /F "tokens=1 skip=4 delims=  " %%B IN ('REG QUERY
HKCR\Installer\UpgradeCodes\%%A /s') DO (
               FOR /F %%C IN ('%binpath%\GuidCompressor.exe %%B   B')
DO (
                       %windir%\System32\msiexec.exe /x %%C /lvx*
%logfile% /qb

This research was tested on 32bit versions of WinXP and Win2k3."

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
January 27, 2006 0:43
I have a general question on your products that support multiple versions of product to be installed at the same time. How do you handle if the user selects the same destination directory for the new version that an older version is already installed. You should somehow force it into a major upgrade situation correct?
I also have a product that want to support multiple versions on the machine but most of the time the users would choose the same destination as the older version and at this point want it to replace the existing version. This is the the major upgrade process but only for the version that is installed in the same location.

Any insight on how you handle this situation would be greatly appreciated.

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.