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.
Comments
Anonymous
February 19, 2009
The comment has been removedAnonymous
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.