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' ) DO FOR /F "tokens=5 delims= " %%Z IN ( 'REG QUERY HKCU\Software\Microsoft\Installer\Products\%%A /v ProductName' ) DO 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!:
"Scott,Thanks for the help with REG.exe and FOR /F. My requirement was to beable to uninstall any installed version of a product in an automatedfashion via a batch file. Because we don't know the exact product codesthat might be installed, we start our search with the upgrade code.Initially, I had some trouble determining the values to use for:<yourUpgradeCodeGUIDHere>.What I found is that MSIs record 'compressed' GUIDs in the registry forupgrade and product codes. So if you want to search the registry forUpgradeCodes, you will need to search for the compressed code.If <yourUpgradeCodeGUIDHere> is: {abcdefgh-ijkl-mnop-qrst-uvwxyz123456}Then the compressed version you should search for is: hgfedcbalkjiponmrqtsvuxwzy214365http://www.appdeploy.com/messageboards/tm.asp?m=11996&mpage=1⼅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 Installerregistry locations from: http://msdn2.microsoft.com/en-US/library/aa367758.aspx because weinstall per-machine.The value(s) stored under an UpgradeCode path in the registry will becompressed product codes. In order to run an msiexec uninstall you needan uncompressed code in the {8-4-4-4-12} format. I ended up writing asmall console app to do the translations between compressed anduncompressed codes. (Why does MSI compress the codes?) Here is the meatof 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. Console.WriteLine(newGuid.ToString(outputFormat).ToUpper()); } }Combining the GuidCompressor, UpgradeCodeGuid, and HKCR registrylocation 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.txtSET binpath=C:\toolsFOR /F %%A IN ('%binpath%\GuidCompressor.exe%UncompressedUpgradeCodeGUID% N') DO ( FOR /F "tokens=1 skip=4 delims= " %%B IN ('REG QUERYHKCR\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."
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. I am a failed stand-up comic, a cornrower, and a book author.
Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.