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 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:
<yourUpgradeCodeGUIDHere>.
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:
{abcdefgh-ijkl-mnop-qrst-uvwxyz123456}
Then the compressed version you should search for is:
hgfedcbalkjiponmrqtsvuxwzy214365
http://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 Installer
registry locations from:
http://msdn2.microsoft.com/en-US/library/aa367758.aspx 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.
Console.WriteLine(newGuid.ToString(outputFormat).ToUpper());
}
}
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."
Hosting By