Product Code GUID Resolution

 Written by  Uncategorized  Add comments
Oct 102008
 

Incorrect handling of the creation or maintenance of one of your company’s product installers can lead to very messy situations down the road. A variable product code attributable to a product where the code should be constant is an unfortunate reality that some may find themselves affected by, given previous ignorant usage of packaging tools. It doesn’t need much explaining: your customers’ machines will have multiple entries of the same product in both their installer cache and elsewhere, with no apparent way to remove them.

The solution devised in order to fix this must involve being able to find these hidden pieces of software and get MSIEXEC to remove them peacefully and correctly. What if the reason for your troubles is indeed a non-constant product code? Do you have all the codes written down somewhere, or memorized? Most likely, you do not; or perhaps you thought you did, but there exist various elusive versions out there that snuck by the record keeping aspect of the whole deal.

I have made a simple program I call the GUIDResolver that will resolve installed product’s product code GUID’s based on the product name and spit them or the associated uninstall string on the screen. Thus, it is an essential component in a larger scripting solution for cleaning up the mess you or some previous worker at your company has made.

Before trying to resolve any past installation issues, it is hopeful that the current situation is one such that the installation is now being done properly. Before getting the new package out in the wild, however, you’ll need to clean the machines that are sporting your mess. This process needs to be able to be automated, and be thorough.

In the event that there are multiple product codes out there, we must be able to produce a list of the GUID’s that are present on each machine, and then uninstall the product via MSIEXEC. The program I’ve written addresses this aspect of the cleanup, and by searching the registry for some values unique to your product, you can produce a list of GUID’s.

The “key” to the problem lies with the various keys found in the registry at HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall. Most installations will result in a key named after the GUID being generated there. In order to match up a product with a GUID, you can use the DisplayName string value found in each key. If your product name is something extremely common that, perhaps, shares part of its name with a Microsoft product…well, you’re screwed, try to come up with a better name next time hot shot.

What the program achieves is a one-liner (give or take) in .NET, but unless your product was developed using .NET, you’ll need to stray away from the framework since we can’t guarantee it is on every machine. It is most likely possible to script this sort of thing using VBScript, but VBScript is an abomination and I wouldn’t soil my fingertips writing it.

Thus, I wrote it in C++. It works basically by enumerating through the keys found in the Uninstall key and will either return GUID’s or uninstall strings that match the product name.

To enumerate through the keys, we use RegEnumKey as shown below:

while (RegEnumKey(hKey, registry_enum_index, sub_key_name, registry_value_sz)
== ERROR_SUCCESS)
{
.
.
.

…where hKey is an opened registry key (HKEY) pointing to the Uninstall key, registry_enum_index is the index of the subkey we’re opening under hKey, sub_key_name will hold the name of the subkey we are opening, and registry_value_sz points to the size of sub_key_name.

We grab the DisplayName and, optionally, the UninstallString via RegQueryValue.

if (RegQueryValueEx(sKey, _T(“DisplayName”), NULL, &buf_type, (LPBYTE)buf, &buf_sz) != ERROR_SUCCESS)
{
//The current key most likely did not contain “DisplayName”
continue;
}

if (_tcsncmp(argv[product_name_idx], buf, cmp_sz) == 0)
{
if (uninstall_string)
{
TCHAR *uninstall_buf = (TCHAR*)malloc(sizeof(TCHAR)*512);
DWORD uBuf_sz = 512;

if (RegQueryValueEx(sKey, _T(“UninstallString”), NULL, &buf_type, (LPBYTE)uninstall_buf, &uBuf_sz) != ERROR_SUCCESS)
{
std::cerr << "Error in reading the detected match's UninstallString." << std::endl;
}
else
{
_tprintf(_T("%s\n"), uninstall_buf);
free(uninstall_buf);
}
}
else
_tprintf(_T("%s\n"), sub_key_name);
}
.
.

To use the program, simply run it from the command line supplying the product name (in quotes if more than one word of course) as an argument, and all GUIDs associated with that name will appear. Use the /uninstallString switch to have it return an uninstall string instead (i.e. msiexec.exe /x{GUID}). A scripting component of the cleanup solution could utilize this output to perform the actual uninstallation, or whatever it is you wish to accomplish with the GUIDs.

It could be improved, like filtering out GUIDs from the sometimes present non-GUID keys under Uninstall (most likely, your product won’t be under one of those anyway), but it’s good enough as it is. I may or may not improve upon it.

To download the source or binary, please visit the projects page on the static side of omniscientist.com.

Matt Weber

I'm the founder of Bad Echo LLC, which offers consulting services to clients who need an expert in C#, WPF, Outlook, and other advanced .NET related areas. I enjoy well-designed code, independent thought, and the application of rationality in general. You can reach me at matt@badecho.com.

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

 
© 2012-2013 Matt Weber. All Rights Reserved. Terms of Use.