From MSI to WiX, Part 17 - Windows Installer Automation Interface, Part 2

The main page for the series is here.

 

Introduction

Today we will explore the database of installed products.

In standalone administartive tools scripts you need to create an Installer object using the following commands:

Dim Installer

Set Installer = Wscript.CreateObject("WindowsInstaller.Installer")

In scripting custom action MSI engine will make Installer and Session objects available without requiring any action from the user.

 

Querying the database of installed products

We can use the Products property of the Installer object to get the list of the installed or advertised products on the current system.  The following script will print out the names of all installed products.  Use cscript ListProducts.vbs to run the script:

ListProducts.vbs

Option Explicit

' Connect to Windows Installer object

Dim installer

Set installer = Wscript.CreateObject("WindowsInstaller.Installer")

Dim product, products

Set products = installer.Products

For Each product In products

    Wscript.Echo installer.ProductInfo(product, "InstalledProductName")

Next

Set products = Nothing

Set installer = Nothing

Wscript.Quit 0

Products property of the Installer object returns a StringList object which contains Product ID's of all installed or advertised products on the current system.  To get the properties of the Product object use the ProductInfo property of the Installer object.

Here is an updated script which shows how to get some properties of the installed product:

Option Explicit

Const msiInstallStateBadConfig = -6

Const msiInstallStateInvalidArg = -2

Const msiInstallStateUnknown = -1

Const msiInstallStateAdvertised = 1

Const msiInstallStateAbsent = 2

Const msiInstallStateDefault = 5

' Connect to Windows Installer object

Dim installer

Set installer = Wscript.CreateObject("WindowsInstaller.Installer")

Dim product, products

Dim productState

Set products = installer.Products

For Each product In products

WScript.Echo GenerateProductElement() & vbCrLf

WScript.Echo GeneratePackageElement() & vbCrLf

WScript.Echo "</Product>" & vbCrLf

Next

Set products = Nothing

Set installer = Nothing

Wscript.Quit 0

Function GenerateProductElement()

Dim script

script = "<Product Id=""" & product & """" & vbCrLf

script = script & ProductProperty("InstalledProductName", "Name", 9) & vbCrLf

script = script & ProductProperty("VersionString", "Version", 9) & vbCrLf

script = script & ProductProperty("Language", "Language", 9) & vbCrLf

script = script & ProductProperty("Publisher", "Manufacturer", 9)

script = script & ProductProperty("InstallLocation", "InstalledTo", 9) & vbCrLf

script = script & ProductProperty("InstallSource", "InstalledFrom", 9) & vbCrLf

script = script & ProductProperty("InstallDate", "InstalledOn", 9) & vbCrLf

script = script & ProductProperty("LocalPackage", "LocalPackage", 9) & vbCrLf

script = script & Space(9) & "State="""

Select Case installer.ProductState(product)

Case msiInstallStateAbsent:

script = script & "LocalDifferentUser"

Case msiInstallStateDefault:

script = script & "LocalCurrentUser"

Case msiInstallStateAdvertised:

script = script & "Advertised"

Case msiInstallStateBadConfig:

script = script & "Corrupt"

End Select

script = script & """"

GenerateProductElement = script & ">"

End Function

Function GeneratePackageElement()

Dim script

script = " <Package " & ProductProperty("PackageCode", "Id", 0) & " />"

GeneratePackageElement = script

End Function

Function ProductProperty(attribute, name, indent)

ProductProperty = Space(indent) & name & "=""" & installer.ProductInfo(product, attribute) & """"

End Function

This is how the output might look like:

<Product Id="{8DE19645-E93C-41AE-8B8F-5A2D00CB5763}"

Name="WiX Toolset Visual Studio Package"

Version="2.0.5325.0"

Language="1033"

Manufacturer="Microsoft Corporation"

InstalledTo=""

InstalledFrom="C:\Documents and Settings\Alex\Local Settings\Temporary Internet Files\Content.IE5\B6JQAD4I\"

InstalledOn="20080114"

LocalPackage="C:\WINDOWS\Installer\cf306dc.msi"

State="LocalCurrentUser">

<Package Id="{7400FFBF-8D4D-4AA1-820B-70A3761D5C5E}" />

</Product>

Products property of the Installer object returns all products currently installed.  You can limit the amount of returned products by using the ProductEx property of the Installer object instead.  This property lets you define an installation context and a security identifier (SID) to narrow the search for installed products.

 

ProductEx property returns collection of Product objects.  For this sample we will be using two of the properties of Product object to get the per-user/per-machine installation context and for per-user installation - the name of the user for which product is installed.

Here is an updated script:

Option Explicit

Const msiInstallStateBadConfig = -6

Const msiInstallStateInvalidArg = -2

Const msiInstallStateUnknown = -1

Const msiInstallStateAdvertised = 1

Const msiInstallStateAbsent = 2

Const msiInstallStateDefault = 5

Const msiContextUserManaged = 1

Const msiContextUserUnmanaged = 2

Const msiContextMachine = 4

Const msiContextAll = 7

' Connect to Windows Installer object

Dim installer

Set installer = Wscript.CreateObject("WindowsInstaller.Installer")

Dim product, products, features, prod

Dim productState

Dim useProductsEx

useProductsEx = True

if useProductsEx = True Then

' Enumerate all products for all users on the system

Set products = installer.ProductsEx("", "s-1-1-0", msiContextAll)

For Each prod In products

product = prod.ProductCode

WScript.Echo GenerateProductElement() & vbCrLf

WScript.Echo GeneratePackageElement() & vbCrLf

WScript.Echo "</Product>" & vbCrLf

Next

Else

Set products = installer.Products

For Each product In products

WScript.Echo GenerateProductElement() & vbCrLf

WScript.Echo GeneratePackageElement() & vbCrLf

WScript.Echo "</Product>" & vbCrLf

Next

End If

Set products = Nothing

Set installer = Nothing

Wscript.Quit 0

Function GenerateProductElement()

Dim script

script = "<Product Id=""" & product & """" & vbCrLf

script = script & ProductProperty("InstalledProductName", "Name", 9) & vbCrLf

script = script & ProductProperty("VersionString", "Version", 9) & vbCrLf

script = script & ProductProperty("Language", "Language", 9) & vbCrLf

script = script & ProductProperty("Publisher", "Manufacturer", 9) & vbCrLf

script = script & ProductProperty("InstallLocation", "InstalledTo", 9) & vbCrLf

script = script & ProductProperty("InstallSource", "InstalledFrom", 9) & vbCrLf

script = script & ProductProperty("InstallDate", "InstalledOn", 9) & vbCrLf

script = script & ProductProperty("LocalPackage", "LocalPackage", 9) & vbCrLf

script = script & Space(9) & "State="""

Select Case installer.ProductState(product)

Case msiInstallStateAbsent:

script = script & "LocalDifferentUser"

Case msiInstallStateDefault:

script = script & "LocalCurrentUser"

Case msiInstallStateAdvertised:

script = script & "Advertised"

Case msiInstallStateBadConfig:

script = script & "Corrupt"

End Select

    script = script & """" & vbCrLf

 

    Dim findUser

    findUser = True

    if useProductsEx = True Then

        script = script & Space(9) & "Context="""

        Select Case prod.Context

            Case msiContextUserManaged:

                script = script & "PerUser"

            Case msiContextUserUnmanaged:

                script = script & "PerUser"

            Case msiContextMachine:

                script = script & "PerMachine"

                findUser = False

        End Select

    End If

   

    If findUser = True Then

   Dim oSid

        Set oSid = GetObject("winmgmts:!Win32_Sid.SID='" & prod.usersid & "'")

       

        script = script & """" & vbCrLf

        script = script & Space(9) & "User=""" & oSid.ReferencedDomainName & "\" & oSid.AccountName

       

    Set oSid = Nothing

    End If

    script = script & """"

    GenerateProductElement = script & ">"

End Function

Function GeneratePackageElement()

    GeneratePackageElement = " <Package " & ProductProperty("PackageCode", "Id", 0) & " />"

End Function

Function ProductProperty(attribute, name, indent)

    ProductProperty = Space(indent) & name & "=""" & installer.ProductInfo(product, attribute) & """"

End Function

 

Here is what we might get as an output:

 

<Product Id="{8DE19645-E93C-41AE-8B8F-5A2D00CB5763}"

Name="WiX Toolset Visual Studio Package"

Version="2.0.5325.0"

Language="1033"

Manufacturer="Microsoft Corporation"

InstalledTo=""

InstalledFrom="C:\Documents and Settings\Alex\Local Settings\Temporary Internet Files\Content.IE5\B6JQAD4I\"

InstalledOn="20080114"

LocalPackage="C:\WINDOWS\Installer\cf306dc.msi"

State="LocalCurrentUser"

Context="PerMachine">

<Package Id="{7400FFBF-8D4D-4AA1-820B-70A3761D5C5E}" />

</Product>

<Product Id="{2119B6B3-7738-4F4E-A673-AF0E626CF58E}"

Name="Microsoft Best Practices Analyzer"

Version="1.0.0"

Language="1033"

Manufacturer="Microsoft Corporation"

InstalledTo=""

InstalledFrom="C:\Documents and Settings\Alex\Local Settings\Temporary Internet Files\Content.IE5\1QV9QXXV\"

InstalledOn="20071031"

LocalPackage="C:\WINDOWS\Installer\a7f1821.msi"

State="LocalCurrentUser"

Context="PerUser"

User="WORKHORSE\Alex">

<Package Id="{64B72558-064E-4267-A929-B431163F7568}" />

</Product>

 

To get the list of features installed for particular product use Features property of the Installer object.  This property takes one argument, which is a product code and returns a StringList object with the list of feature ID's.

Feature tree is hierarchical structure and every feature can have a parent.  To get the parent of the feature use FeatureParent property of the Installer object.  To get the state of the feature use the FeatureState property of the Installer object.

Here is an update to the script which shows a feature hierarchy for every installed product:

Const msiInstallStateNotUsed = -7

Const msiInstallStateBadConfig = -6

Const msiInstallStateIncomplete = -5

Const msiInstallStateSourceAbsent = -4

Const msiInstallStateInvalidArg = -2

Const msiInstallStateUnknown = -1

Const msiInstallStateBroken = 0

Const msiInstallStateAdvertised = 1

Const msiInstallStateAbsent = 2

Const msiInstallStateLocal = 3

Const msiInstallStateSource = 4

Const msiInstallStateDefault = 5

Function GenerateFeatureTable(indent)

Dim feature, script, parent

Set features = installer.Features(product)

For Each feature In features

' Iterate through every root-level feature

parent = installer.FeatureParent(product, feature)

If Len(parent) = 0 Then

script = script & GenerateFeatureElement(feature, indent)

script = script & GenerateSubFeatures(feature, indent + 4)

script = script & Space(indent) & "</Feature>" & vbCrLf

End If

Next

Set features = Nothing

GenerateFeatureTable = script

End Function

Function GenerateSubFeatures(feature, indent)

Dim child, script, features

Set features = installer.Features(product)

script = ""

For Each child In features

If feature = installer.FeatureParent(product, child) Then

script = script & GenerateFeatureElement(child, indent)

script = script & GenerateSubFeatures(child, indent + 4)

script = script & Space(indent) & "</Feature>" & vbCrLf

End If

Next

Set features = Nothing

GenerateSubFeatures = script

End Function

Function GenerateFeatureElement(feature, indent)

Dim script, state

script = Space(indent) & "<Feature Id=""" & feature & """ State="""

On Error Resume Next

state = installer.FeatureState(product, feature)

Select Case state

Case msiInstallStateBadConfig:

script = script & "Corrupt"

Case msiInstallStateIncomplete:

script = script & "InProgress"

Case msiInstallStateSourceAbsent:

script = script & "SourceAbsent"

Case msiInstallStateBroken:

script = script & "Broken"

Case msiInstallStateAdvertised:

script = script & "Advertised"

Case msiInstallStateAbsent:

script = script & "Absent"

Case msiInstallStateLocal:

script = script & "Local"

Case msiInstallStateSource:

script = script & "Source"

Case msiInstallStateDefault:

script = script & "Default"

Case Else

script = script & "Unknown"

End Select

GenerateFeatureElement = script & """>" & vbCrLf

End Function

 

This is how the output might look like:

 

<Product Id="{85F4CBCB-9BBC-4B50-A7D8-E1106771498D}"

Name="Orca"

Version="3.1.5299.0000"

Language="1033"

Manufacturer="Microsoft Corporation"

InstalledTo=""

InstalledFrom="C:\Program Files\Microsoft SDKs\Windows\V6.0A\Bin\"

InstalledOn="20071017"

LocalPackage="C:\WINDOWS\Installer\1c618f4a.msi"

State="LocalCurrentUser"

Context="PerMachine">

<Package Id="{FE01B6C5-85BF-4CA3-9B78-B44DEB8A4946}" />

<Feature Id="Orca" State="Local">

<Feature Id="OrcaHelp" State="Local">

</Feature>

<Feature Id="EvalComServer" State="Local">

</Feature>

<Feature Id="MergeModServer" State="Local">

</Feature>

<Feature Id="CUBFiles" State="Local">

<Feature Id="FullCUBFile" State="Local">

</Feature>

<Feature Id="LogoCUBFile" State="Local">

</Feature>

<Feature Id="XPLogoCUBFile" State="Local">

</Feature>

<Feature Id="MMCUBFile" State="Local">

</Feature>

</Feature>

</Feature>

</Product>

 

To get the list of all components currently installed on the system, use the Components property of the Installer object.  To determine if component belongs to a particular product, use the ComponentClients property.  This property returns a list of product codes of products with which current component is shared.  We can get an installation state of the component by calling ComponentState property of the Product object.  To get he full path where component is installed we need to call ComponentPath method of the Installer object.

 

Warning:   Presented implementation's performance is very poor.  It was not my goal to create commercial grade application.

 

Function GenerateComponentList(productCode, indent)

Dim result

Dim component, components, client, clients

Dim state

result = Space(indent) & "<Components>" & vbCrLf

Set components = installer.Components

For Each component in components

Set clients = installer.ComponentClients(component)

For Each client in clients

If client = productCode Then

If useProductsEx = True Then

result = result & Space(indent + 4) & "<Component Id=""" & component

result = result & """ State="""

state = prod.ComponentState(component)

Select Case state

Case msiInstallStateLocal:

result = result & "Local"

Case msiInstallStateSource:

result = result & "Source"

Case msiInstallStateNotUsed

result = result & "NotInstalled"

Case Else

result = result & state

End Select

result = result & """" & vbCrLf

result = result & Space(indent + 15) & "ComponentPath=""" & Installer.ComponentPath(productCode, component)

result = result & """ />" & vbCrLf

Else

result = result & Space(indent + 4) & "<Component Id=""" & component & """ />" & vbCrLf

End If

Exit For

End If

Next

Next

GenerateComponentList = result & Space(indent) & "</Components>" & vbCrLf

End Function

This is how the output might look like:

<Product Id="{8DE19645-E93C-41AE-8B8F-5A2D00CB5763}"

         Name="WiX Toolset Visual Studio Package"

         Version="2.0.5325.0"

         Language="1033"

         Manufacturer="Microsoft Corporation"

         InstalledTo=""

         InstalledFrom="C:\Documents and Settings\Alex\Local Settings\Temporary Internet Files\Content.IE5\B6JQAD4I\"

         InstalledOn="20080114"

         LocalPackage="C:\WINDOWS\Installer\cf306dc.msi"

         State="LocalCurrentUser"

         Context="PerMachine">

    <Package Id="{7400FFBF-8D4D-4AA1-820B-70A3761D5C5E}" />

    <Feature Id="Feature_VS2005" State="Local">

    </Feature>

    <Components>

        <Component Id="{39CFE791-2BE2-4B1A-852F-6A14E6AC00BC}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\lit.exe.config" />

        <Component Id="{B9BD83B1-4106-428A-A1C1-7473041B739F}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\mergemod.dll" />

        <Component Id="{0458E092-F02A-492C-9865-2AAF24251EB4}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\doc\WiX.chm" />

        <Component Id="{A4473BD2-EC34-4CB4-BBCF-E9F1B03B5462}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\candle.exe" />

        <Component Id="{40340083-FDB0-437B-B172-F3011F50250F}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\dark.exe.config" />

        <Component Id="{828C4493-20D4-44DC-8714-9241C2DB017A}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\wixca.dll" />

        <Component Id="{01D89793-D0CA-40B4-93C6-A6564807E162}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\lit.exe" />

        <Component Id="{97760B35-5DFA-487B-8664-2E725160E8AF}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\Visual Studio\sconce.dll" />

        <Component Id="{47F88195-5595-40B3-BB32-6837C185AD5A}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\Bitmaps\bannrbmp.bmp" />

        <Component Id="{96B9F8A5-F52B-4CA6-BFD5-6D943970F137}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\tallow.exe" />

        <Component Id="{02FE8BB5-85C6-4707-AD72-E5A66CE817AF}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\Visual Studio\1033\votiveui.dll" />

  <Component Id="{A1947047-971D-47DF-81DC-17CA6D95E281}" State="Local"

                   ComponentPath="02:\SOFTWARE\Microsoft\VisualStudio\8.0\Editors\{FA3CD31E-987B-443A-9B81-186104E8DAC1}\Extensions\wxs" />

        <Component Id="{C79B4FD7-9C59-4F5F-A3CF-CB97C15561B2}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\doc\wixloc.xsd" />

        <Component Id="{4C7E3258-FD99-4084-B8EE-001D3464AC48}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\Visual Studio\Templates\Project Items\Code\Code.vsdir" />

        <Component Id="{41074398-C485-4AA4-BD01-F4EB0E5DCD63}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\Visual Studio\Templates\Projects\Projects.vsdir" />

        <Component Id="{0BCBF6C8-3BCA-41BD-ACDD-B20866F43216}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\Visual Studio\Templates\Project Items\Resources\Resources.vsdir" />

        <Component Id="{647008F8-DF4E-4A8C-8BB0-8EC64CA54F4B}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\Visual Studio\Templates\Project Items\ProjectItems.vsdir" />

        <Component Id="{C1A158F8-1A3D-4439-9FB1-6F78E7201E0A}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\light.exe.config" />

        <Component Id="{9E3450EA-143F-4FAB-8A10-FEEC5D7A8274}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\winterop.dll" />

        <Component Id="{95BD12FA-2B78-4125-82C9-9FA82DDF7F61}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\doc\wix.xsd" />

        <Component Id="{5E2F000C-11C4-40B0-80EA-2FA6748E97F9}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\light.exe" />

        <Component Id="{E05A990C-7E04-4DF9-BFC2-845D172B7ABA}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\WixUI.wixlib" />

        <Component Id="{B871A32C-5620-4640-908C-1EB514F701FF}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\CPL.TXT" />

        <Component Id="{FBE0F9FC-853D-4AB4-B849-284FEC7C48F8}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\tallow.exe.config" />

        <Component Id="{D5CA815E-0A94-4A91-BE53-8B1ED271DF10}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\dark.exe" />

        <Component Id="{20B53A0F-BEE4-40DD-A0B3-54F3C16B876F}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\wix.dll" />

        <Component Id="{DEC3354F-F81E-414D-BC0E-85F507C65EDF}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\candle.exe.config" />

        <Component Id="{3FC43EEF-C5B8-46E9-9640-36B647C95197}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\bin\scaexec.dll" />

        <Component Id="{8D3A06FF-7EDB-44D8-AFC0-49FDE3ABBA3D}" State="Local"

                   ComponentPath="C:\Program Files\Windows Installer XML\Visual Studio\votive.dll" />

    </Components>

</Product>

In next part we will look at how to get the list of patches applied to a product and how to query cached copy of product's installer package.

Source code is attached.

 

ListProducts.vbs

Comments

  • Anonymous
    February 19, 2009
    The comment has been removed

  • Anonymous
    February 01, 2010
    I also get the same error on just one machine.. its so puzzling. I narrowed it down to this: oMSI.ProductsEx("", "s-1-1-0", 4)  FAILS! oMSI.ProductsEx("", "s-1-1-0", 1)  GOOD oMSI.ProductsEx("", "s-1-1-0", 2)  GOOD Still stuck. if you know a solution for this please let me know rbhkamal@gmail.com Thanks!

  • Anonymous
    April 11, 2011
    It seems its a LUA problem... tough I don't know why you would need Admin privileges to query about the machine state. Using msiContextMachine in the "context" Parameter withouth beign an administrator shows the error you described. If you launch the script from an elevated command prompt it works fine.