Skip to content

Instantly share code, notes, and snippets.

@btm
Last active January 4, 2016 20:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save btm/8673443 to your computer and use it in GitHub Desktop.
Save btm/8673443 to your computer and use it in GitHub Desktop.
utilities for getting microsoft installer product code data from ruby
# Reads a product code from an MSI file
# Checks if that product code is installed on the system
require 'rubygems'
require 'ffi'
require 'pathname'
PRODUCT_CODE_LENGTH = 38
module Win32
extend FFI::Library
ffi_lib 'msi'
attach_function :msi_open_package, :MsiOpenPackageExA, [ :string, :int, :pointer ], :int
attach_function :msi_get_product_property, :MsiGetProductPropertyA, [ :pointer, :pointer, :pointer, :pointer ], :int
attach_function :msi_get_product_info, :MsiGetProductInfoA, [ :pointer, :pointer, :pointer, :pointer ], :int
attach_function :msi_close_handle, :MsiCloseHandle, [ :pointer ], :int
end
def print_usage
puts "Checks if a provided Microsoft Installer Product is already installed"
puts "Usage: ruby #{__FILE__} example.msi"
end
def get_product_code(package_path)
product_code = 0.chr * (PRODUCT_CODE_LENGTH + 1)
product_code_length = FFI::Buffer.new(:long).write_long(PRODUCT_CODE_LENGTH + 1)
pkg_ptr = FFI::MemoryPointer.new(:pointer, 4)
status = Win32.msi_open_package(package_path, 1, pkg_ptr)
case status
when 0
# success
when 1619
raise "msi_open_package #{status}: ERROR_INSTALL_PACKAGE_OPEN_FAILED"
else
raise "msi_open_package returned #{status}"
end
status = Win32.msi_get_product_property(pkg_ptr.read_pointer, "ProductCode", product_code, product_code_length)
raise "Unexpected status #{status}" unless status == 0
Win32.msi_close_handle(pkg_ptr)
product_code
end
def product_installed?(product_code)
#UINT MsiGetProductInfo(
# _In_ LPCTSTR szProduct,
# _In_ LPCTSTR szProperty,
# _Out_ LPTSTR lpValueBuf,
# _Inout_ DWORD *pcchValueBuf
#);
buffer = 0.chr
buffer_length = FFI::Buffer.new(:long).write_long(0)
status = Win32.msi_get_product_info(product_code, "VersionString", buffer, buffer_length)
return false if status == 1605 # ERROR_UNKNOWN_PRODUCT (0x645)
if ( status != 234 )
raise "Unexpected status #{status}"
end
buffer_length = FFI::Buffer.new(:long).write_long(buffer_length.read_long + 1)
buffer = 0.chr * buffer_length.read_long
status = Win32.msi_get_product_info(product_code, "VersionString", buffer, buffer_length)
raise "msi_get_product_info: unexpected status: #{status}" if status != 0
true
end
if ARGV.length < 1
print_usage
exit 1
end
# MsiOpenPackage expects a perfect absolute Windows path to the MSI
package_path = Pathname.new(ARGV[0]).realpath.to_s.gsub(/\//, '\\')
pc = get_product_code(package_path)
puts "Product Code: #{pc}"
puts "Installed?: #{product_installed?(pc)}"
# opens an msi file and gets the ProductCode
require 'rubygems'
require 'ffi'
require 'pathname'
module Win32
extend FFI::Library
ffi_lib 'msi'
attach_function :msi_open_package, :MsiOpenPackageExA, [ :string, :int, :pointer ], :int
attach_function :msi_get_product_property, :MsiGetProductPropertyA, [ :pointer, :pointer, :pointer, :pointer ], :int
attach_function :msi_close_handle, :MsiCloseHandle, [ :pointer ], :int
end
def print_usage
puts "Checks if a provided Microsoft Installer Product is already installed"
puts "Usage: ruby #{__FILE__} example.msi"
end
def get_product_code(package_path)
product_code = 0.chr
product_code_length = FFI::Buffer.new(:long).write_long(0)
pkg_ptr = FFI::MemoryPointer.new(:pointer, 4)
puts "Opening package: #{package_path}"
status = Win32.msi_open_package(package_path, 1, pkg_ptr)
case status
when 0
# success
when 1619
raise "msi_open_package #{status}: ERROR_INSTALL_PACKAGE_OPEN_FAILED"
else
raise "msi_open_package returned #{status}"
end
status = Win32.msi_get_product_property(pkg_ptr.read_pointer, "ProductCode", product_code, product_code_length)
if ( status != 234 )
raise "Unexpected status #{status}"
end
product_code_length = FFI::Buffer.new(:long).write_long(product_code_length.read_long + 1)
product_code = 0.chr * product_code_length.read_long
status = Win32.msi_get_product_property(pkg_ptr.read_pointer, "ProductCode", product_code, product_code_length)
raise "msi_get_product_property: #{status}" unless status == 0
Win32.msi_close_handle(pkg_ptr)
product_code
end
if ARGV.length < 1
print_usage
exit 1
end
# MsiOpenPackage expects a perfect absolute Windows path to the MSI
package_path = Pathname.new(ARGV[0]).realpath.to_s.gsub(/\//, '\\')
puts get_product_code(package_path)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment