puppetlabs/puppet — pull request #851 by joshcooper (2012-06-12 00:28:52+00:00)
Title: (#11868) Use `Installer` automation interface to query package state

Previously, Puppet recorded MSI packages it had installed in a YAML
file. However, if the file was deleted or the system modified, e.g.
Add/Remove Programs, then Puppet did not know the package state had
changed.

Also, if the name of the package did not change across versions, e.g.
VMware Tools, then puppet would report the package as insync even though
the installed version could be different than the one pointed to by the
source parameter.

Also, `msiexec.exe` returns non-zero exit codes when either the package
requests a reboot (194), the system requires a reboot (3010), e.g. due
to a locked file, or the system initiates a reboot (1641). This would
cause puppet to think the install failed, and it would try to reinstall
the packge the next time it ran (since the YAML file didn't get
updated).

This commit changes the msi package provider to use the `Installer`
Automation (COM) interface to query the state of the system[1]. It will
now accurately report on installed packages, even those it did not
install, including Puppet itself (#13444). If a package is removed via
Add/Remove Programs, Puppet will re-install it the next time it runs.

The MSI package provider will now warn in the various reboot scenarios,
but report the overall install/uninstall as successful (#14055).

When using the msi package resource, the resource title should match the
'ProductName' property in the MSI Property table, which is also the
value displayed in Add/Remove Programs, e.g.

package { 'Microsoft Visual C++ 2008 Redistributable - x86 9.0.30729.4148':
ensure => installed,
...
}

In cases where the ProductName does not change across versions, e.g.
VMware Tools, you MUST use the PackageCode as the name of the resource
in order for puppet to accurately determine the state of the system:

package { '{0E3AA38E-EAD3-4348-B5C5-051B6852CED6}':
ensure => installed,
...
}

You can obtain the PackageCode in ruby using:

require 'win32ole'
installer = WIN32OLE.new('WindowsInstaller.Installer')
db = installer.OpenDatabase(path, 0)
puts db.SummaryInformation.Property(9)

where is the path to the MSI.

The msi provider does not automatically compare PackageCodes when
determining if the resource is insync, because the source MSI could be
on a network share, and we do not want to copy the potentially large
file just to see if changes need to be made.

The msi provider does not use the `Installer` interface to perform
install and uninstall, because I have not found a way to obtain useful
error codes when reboots are requested. Instead the methods
`InstallProduct` and `ConfigureProduct` raise exceptions with the
general 0x80020009 error, which means 'Exception occurred'. So for now
we continue to use msiexec.exe for install and uninstall, though the msi
provider may not uninstall multi-instance transforms correctly, since
the transform (MST) used to install the package needs to be respecified
during uninstall. This could be resolved by allowing uninstall_options
to be specified, or figuring out how to obtain useful error codes when
using the `Installer` interface.

[1] http://msdn.microsoft.com/en-us/library/windows/desktop/aa369432(v=vs.85).aspx