Alex Shevchuk

Always listen to experts. They’ll tell you what can’t be done, and why. Then do it. - Lazarus Long

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

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

  • Comments 3
  • Likes

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.

 

Attachment: ListProducts.vbs
Comments
  • I have got an error 0x80004005 at line 34, symbol 5, source Msi API Error.

  • 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!

  • 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.

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment