diff --git a/lib/resources/package.rb b/lib/resources/package.rb index f5d0ffa57..6600149ac 100644 --- a/lib/resources/package.rb +++ b/lib/resources/package.rb @@ -184,14 +184,27 @@ module Inspec::Resources end end - # Determines the installed packages on Windows - # Currently we use 'Get-WmiObject -Class Win32_Product' as a detection method - # TODO: evaluate if alternative methods as proposed by Microsoft are still valid: + # Determines the installed packages on Windows using the Windows package registry entries. # @see: http://blogs.technet.com/b/heyscriptingguy/archive/2013/11/15/use-powershell-to-find-installed-software.aspx class WindowsPkg < PkgManagement def info(package_name) + search_paths = [ + 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', + 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', + ] + + # add 64 bit search paths + if inspec.os.arch == 'x86_64' + search_paths << 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' + search_paths << 'HKCU:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' + end + # Find the package - cmd = inspec.command("Get-WmiObject -Class Win32_Product | Where-Object {$_.Name -eq '#{package_name}'} | Select-Object -Property Name,Version,Vendor,PackageCode,Caption,Description | ConvertTo-Json") + cmd = inspec.command <<-EOF.gsub(/^\s*/, '') + Get-ItemProperty (@("#{search_paths.join('", "')}") | Where-Object { Test-Path $_ }) | + Where-Object { $_.DisplayName -like "#{package_name}*" -or $_.PSChildName -like "#{package_name}" } | + Select-Object -Property DisplayName,DisplayVersion | ConvertTo-Json + EOF begin package = JSON.parse(cmd.stdout) @@ -199,10 +212,13 @@ module Inspec::Resources return nil end + # What if we match multiple packages? just pick the first one for now. + package = package[0] if package.is_a?(Array) + { - name: package['Name'], + name: package['DisplayName'], installed: true, - version: package['Version'], + version: package['DisplayVersion'], type: 'windows', } end diff --git a/test/helper.rb b/test/helper.rb index 3891cf61a..e2120ad83 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -173,7 +173,7 @@ class MockLoader # ports on freebsd 'sockstat -46l' => cmd.call('sockstat'), # packages on windows - "Get-WmiObject -Class Win32_Product | Where-Object {$_.Name -eq 'Microsoft Visual C++ 2008 Redistributable - x64 9.0.30729.6161'} | Select-Object -Property Name,Version,Vendor,PackageCode,Caption,Description | ConvertTo-Json" => cmd.call('win32_product'), + 'f18912b2e36924b367a110c31da6b835a1c217cd10014c7312b7435bf79a601c' => cmd.call('get-item-property-package'), # service status upstart on ubuntu 'initctl status ssh' => cmd.call('initctl-status-ssh'), # service config for upstart on ubuntu diff --git a/test/integration/default/package_spec.rb b/test/integration/default/package_spec.rb index e32e0a781..3886cf5e0 100644 --- a/test/integration/default/package_spec.rb +++ b/test/integration/default/package_spec.rb @@ -28,3 +28,31 @@ end describe package('nginx') do it { should_not be_installed } end + +# the following test will iterate over all packages retrieved via WMI and verifies that the +# optimized package implementation is returning the same results +if os.windows? + # compare optimized version with wmi results + packages = powershell("Get-WmiObject -Class Win32_Product | % { $_.Name }").stdout.strip.split("\n") + + packages.each { |pkg| + package_name = pkg.strip + # get wmi package information + cmd = powershell("Get-WmiObject -Class Win32_Product | Where-Object {$_.Name -eq '#{package_name}'} | Select-Object -Property Name,Version,Vendor,PackageCode,Caption,Description | ConvertTo-Json") + wmi_package = JSON.parse(cmd.stdout) + wmi_info = { + name: wmi_package['Name'], + installed: true, + version: wmi_package['Version'], + type: 'windows', + } + + # get registry package information + info = package(package_name).info + + # compare results + describe wmi_info do + it { should eq info} + end + } +end diff --git a/test/unit/mock/cmd/get-item-property-package b/test/unit/mock/cmd/get-item-property-package new file mode 100644 index 000000000..36e581f2b --- /dev/null +++ b/test/unit/mock/cmd/get-item-property-package @@ -0,0 +1,4 @@ +{ + "DisplayName": "Chef Client v12.12.15", + "DisplayVersion": "12.12.15.1" +} diff --git a/test/unit/mock/cmd/win32_product b/test/unit/mock/cmd/win32_product deleted file mode 100644 index 591e53378..000000000 --- a/test/unit/mock/cmd/win32_product +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Name": "Microsoft Visual C++ 2008 Redistributable - x64 9.0.30729.6161", - "Version": "9.0.30729.6161", - "Vendor": "Microsoft Corporation", - "PackageCode": "{9C7D912C-6EDE-47A4-962E-7A83663440BA}", - "Caption": "Microsoft Visual C++ 2008 Redistributable - x64 9.0.30729.6161", - "Description": "Microsoft Visual C++ 2008 Redistributable - x64 9.0.30729.6161" -} diff --git a/test/unit/resources/package_test.rb b/test/unit/resources/package_test.rb index 97db0f2c4..8e5c4f552 100644 --- a/test/unit/resources/package_test.rb +++ b/test/unit/resources/package_test.rb @@ -53,10 +53,10 @@ describe 'Inspec::Resources::Package' do # windows it 'verify windows package parsing' do - resource = MockLoader.new(:windows).load_resource('package', 'Microsoft Visual C++ 2008 Redistributable - x64 9.0.30729.6161') - pkg = { name: 'Microsoft Visual C++ 2008 Redistributable - x64 9.0.30729.6161', installed: true, version: '9.0.30729.6161', type: 'windows' } + resource = MockLoader.new(:windows).load_resource('package', 'Chef Client v12.12.15') + pkg = { name: 'Chef Client v12.12.15', installed: true, version: '12.12.15.1', type: 'windows' } _(resource.installed?).must_equal true - _(resource.version).must_equal '9.0.30729.6161' + _(resource.version).must_equal '12.12.15.1' _(resource.info).must_equal pkg end