Dwarfsoft [GPA]

MSI Package Code Fun

by on Jun.22, 2010, under Uncategorized


Print This Post Print This Post

I have recently been building quite a few MSI packages, and as of the end of last week I was required to go back through and rebuild a whole batch of them. Due to the way a lot of deployments work from Novell Zenworks, Product Codes are used quite frequently to stop the MSI from installing more than once (thereby disabling multiple installs). I believe that this is due to the MSI Template that is used corporately having a few Dialogs missing for Repair/Modify. Therefore when the second install occurs the MSI dies a painful death and the user is presented with a number of errors.

So, rather than fix the source of the problem I work within the structure I am given. After my forays in the past couple of days I found it immensely frustrating trying to get the Product Code key to update our documentation for each MSI which had been rebuilt. This is because, up until now, I had been installing the MSI and searching for the MSI name under HKEY_CLASSES_ROOT\Installer\Product\. The code displayed under this key had confused me for quite some time until I found some information regarding a “Packed GUID“.

As it turns out, the code I linked to doesn’t work. It did, however, lead me to figure out HOW to make it work by making me assess each of the “chunks” in the original GUID.

Effectively there are 5 parts to Each GUID
{11111111-2222-3333-4444-555555555555}

The “Packed GUID” is a regrouping of these numbers, removing the dashes and braces. So, for the first group (11111111) the order in the original GUID appears as ABCDEFGH. The order in the Packed GUID is HGFEDCBA. The Second group (2222) is appended in reverse again (from ABCD to DCBA). The third group is also appended in reverse (from ABCD to DCBA).

The last two groups are a little more complex. Each pair is switched such that for group four (4444) the original GUID appears as ABCD but the packed guid appears as BADC. The last group (555555555555) follows this order as well so for the order ABCDEFGHIJKL the packed GUID will be appended reordered as BADCFEHGJILK.

In VBScript Terms this can be expressed as the following:

Function PackGUID(guid)  
   PackGUID = ""  
   '*  
   Dim temp  
   temp = Mid(guid,2,Len(guid)-2)  
   Dim part  
   part = Split(temp,"-")  
   Dim pack  
   pack = ""  
   Dim i, j  
   For i = LBound(part) To UBound(part)
      Select Case i
     Case LBound(part), LBound(part)+1, LBound(part)+2
         For j = Len(part(i)) To 1 Step -1  
            pack = pack & Mid(part(i),j,1)  
         Next  
     Case Else
        For j = 1 To Len(part(i)) Step 2  
            pack = pack & Mid(part(i),j+1,1) & Mid(part(i),j,1)  
         Next  
     End Select
   Next  
   '*  
   PackGUID = pack  
End Function

So, this was the first step for the time saving measure of extracting the right code. The next issue was how to get the codes in the first place. I knew that there had to be an easier way than opening up InstallShield and Copying out the Codes manually. The first thing that came to mind was the Summary Information Stream of an MSI file (as you can see by right-clicking on an MSI and going to Properties, then Summary). There was a lot of information about this, particularly from MSDN. This lead me to getting the Package Code:

   'create installer object
   Set objDictionary = CreateObject("Scripting.Dictionary")
   Set oInstaller = CreateObject("WindowsInstaller.Installer")
   'open msi in read-only mode
   Set oDatabase = oInstaller.OpenDatabase(MSIPath, 0)
   ' Get Package Code from Summary Information Stream   
   Set streamobj = oDatabase.SummaryInformation(0) '0 = read only
   objDictionary("PackageCode") = streamobj.Property(9)

This is useful, but the Code that HKCR\Installer\Product\{Packed GUID} references comes from the MSI Product Code. So I went back to the drawing board and researched VBScript Installer Database Product Code some more and found this, followed by this gem. From here it was very easy to pull out GUID’s from the MSI packages, and I didn’t even need to do it on a machine with InstallShield actually Installed. The Windows Installer takes care of everything.

Complete code (from start to finish):

'Created by:   Chris Bennett
'Created Date: 22/06/2010
'Description:
'   Opens up MSI file(s) Passed as Arguments and returns ProductName, ProductCode,
'   The HKCR key created from ProductCode (a Packed GUID of ProductCode), the PackageCode
'   and the UpgradeCode of the MSI. Much quicker than getting these out of the MSI's the
'   Manual Way.

'References:
'  http://msdn.microsoft.com/en-us/library/aa369794%28VS.85%29.aspx
'  http://www.eggheadcafe.com/forumarchives/platformsdkmsi/Jan2006/post25948124.asp

For Each MSIPath in WScript.Arguments
  Set MSIDetails = EvaluateMSI(MSIPath)
  WScript.Echo MSIPath & ": "
  WScript.Echo "   Product Name: " & MSIDetails("ProductName")
  WScript.Echo "   Product Code: " & MSIDetails("ProductCode")
  WScript.Echo "   Product Key : " & "HKCR\Installer\Products\" & PackGUID(MSIDetails("ProductCode"))
  WScript.Echo "   Package Code: " & MSIDetails("PackageCode")
  WScript.Echo "   Upgrade Code: " & MSIDetails("UpgradeCode")
  WScript.Echo ""
Next
 
Function EvaluateMSI(MSIPath)
   On Error Resume Next
   'create installer object
   Set oInstaller = CreateObject("WindowsInstaller.Installer")
   'open msi in read-only mode
   'Set oDatabase = oInstaller.OpenDatabase("d:\USERDATA\BennettCm\Desktop\EN-UNHIDE-OUTLOOK-1-01.msi", 0)
   Set oDatabase = oInstaller.OpenDatabase(MSIPath, 0)
   Set objDictionary = CreateObject("Scripting.Dictionary")
 
   ' Get Package Code from Summary Information Stream   
   Set streamobj = oDatabase.SummaryInformation(0) '0 = read only
   objDictionary("PackageCode") = streamobj.Property(9)
 
   ' Get Product Name from MSI Database
   Set View = oDatabase.OpenView("Select `Value` From Property WHERE `Property`='ProductName'")
   View.Execute
   Set ProductName = View.Fetch
   objDictionary("ProductName") = ProductName.StringData(1)
 
   ' Get Product Code from MSI Database
   Set View = oDatabase.OpenView("Select `Value` From Property WHERE `Property`='ProductCode'")
   View.Execute
   Set ProductCode = View.Fetch
   objDictionary("ProductCode") = ProductCode.StringData(1)
 
   ' Get Upgrade Code from MSI Database
   Set View = DB.OpenView("Select `Value` From Property WHERE `Property`='UpgradeCode'")
   View.Execute
   Set UpgradeCode = View.Fetch
   objDictionary("UpgradeCode") = UpgradeCode.StringData(1)
 
   Set EvaluateMSI = objDictionary
   On Error Goto 0
End Function
 
Function PackGUID(guid)  
   PackGUID = ""  
   '*  
   Dim temp  
   temp = Mid(guid,2,Len(guid)-2)  
   Dim part  
   part = Split(temp,"-")  
   Dim pack  
   pack = ""  
   Dim i, j  
   For i = LBound(part) To UBound(part)
      Select Case i
     Case LBound(part), LBound(part)+1, LBound(part)+2
         For j = Len(part(i)) To 1 Step -1  
            pack = pack & Mid(part(i),j,1)  
         Next  
     Case Else
        For j = 1 To Len(part(i)) Step 2  
            pack = pack & Mid(part(i),j+1,1) & Mid(part(i),j,1)  
         Next  
     End Select
   Next  
   '*  
   PackGUID = pack  
End Function

Run from cscript it outputs like:

P:\Folder\To\MSI\MSI Name.msi: 
   Product Name: MSI Name
   Product Code: {A2961B30-4875-4535-AE36-DA064263BA50}
   Product Key : HKCR\Installer\Products\03B1692A57845354EA63AD602436AB05
   Package Code: {5F735447-A72D-4571-8C8D-AD80E2B30F22}
   Upgrade Code: {A2961B30-4875-4535-AE36-DA064263BA50}

I created a Batch File that also makes use of this script to trawl through folders pulling out the information and dumping it into a Codes.txt file.

@ECHO OFF
REM Created By:   Chris Bennett
REM Created Date: 22/06/2010
REM Description:
REM    Finds MSI''s under the current Directory (or passed Directory)
REM    Calls GetMSICodes.vbs to extract the MSI Package,Product and Upgrade Codes
REM    Saves these codes into Codes.txt
 
CD /D %~dp0
IF /I "%~1" NEQ "" CD /D %1
FOR /F "tokens=* delims=" %%A IN ('DIR *.MSI /B /S') DO (
  cscript.exe GetMSICodes.vbs "%%A" //Nologo >> Codes.txt
  )

Hope you enjoyed, I certainly did ;)

Cheers, Chris.


Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!