harry’s memorandum

おれおれメモ

Win32OLEでインストール済みソフトウェア一覧を取得してみたよ

リーマンは時折無駄としか思えない人力作業を強制させられるときがあります。インベントリを人力で調べるなんて拷問ですよ。
ささっとvbsを書いて終わりのつもりだったのですが妙に遅い...半端ではない遅さ。

hostname = "remote_host"
set locator = CreateObject("WbemScripting.SWbemLocator")
Set wmi = locator.ConnectServer(hostname, "ROOT\cimv2", "user", "password")
Set items = wmi.ExecQuery("Select * from Win32_Product",,48)

For Each item in items
    Wscript.Echo "Caption: " & item.Caption
    Wscript.Echo "Version: " & item.Version
Next

どうやら win32_productクラスがよくないようです。「win32_product slow」とググるとワンサカ出てきます。MSI経由で情報を取得するため、超がつくほど遅いようです。

インストール済みアプリケーションの情報取得はレジストリ経由の方が速そう。ただx86とx64のアプリケーションは別のレジストリで管理しているので注意が必要です。

require 'win32ole'
require 'socket'

module REG
  arch = ENV["PROCESSOR_ARCHITECTURE"] == "AMD64" ? x64 : "x86"
  
  UNINSTALLS = 
    if arch == "x64"
      [
        "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\uninstall",
        "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\uninstall",
      ]
    else
      [
        "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\uninstall",
      ]
    end

  HKEY_CLASSES_ROOT   = 0x80000000
  HKCR                = 0x80000000
  HKEY_CURRENT_USER   = 0x80000001
  HKCU                = 0x80000001
  HKEY_LOCAL_MACHINE  = 0x80000002
  HKLM                = 0x80000002
  HKEY_USERS          = 0x80000003
  HKEY_CURRENT_CONFIG = 0x80000005
  HKEY_DYN_DATA       = 0x80000006
end

include REG

# File activesupport/lib/active_support/core_ext/hash/keys.rb
class Hash
  def symbolize_keys!
    keys.each do |key|
      self[(key.to_sym rescue key) || key] = delete(key)
    end
    self
  end
end

class Software
  def initialize(hostname = ".", opt = {})
    wmi = conn_server(hostname, opt)
    @reg = wmi.Get("StdRegProv")
  end

  def conn_server(hostname, opt)
    locator = WIN32OLE.new("WbemScripting.SWbemLocator")
    if Socket.gethostname == hostname || "." == hostname
      locator.ConnectServer("." ,"root\\default")
    else
      locator.ConnectServer(hostname ,"root\\default", opt[:user], opt[:pass])
    end
  end
  
  def each_subkey
    in_param = @reg.Methods_("EnumKey").InParameters.SpawnInstance_()
    in_param.hDefKey = REG::HKLM
    REG::UNINSTALLS.each {|key|
      in_param.sSubKeyName = key
      out_param = @reg.ExecMethod_("EnumKey", in_param)
      out_param.sNames.each {|subkey|
        yield  key + "\\" + subkey
      }
    }
  end

  def each_products
    in_param = @reg.Methods_("GetStringValue").InParameters.SpawnInstance_()
    in_param.hDefKey = REG::HKLM
    self.each_subkey {|key|
      product = {}
      in_param.sSubKeyName = key
      %w(DisplayVersion DisplayName).each {|label|
        in_param.sValueName = label
        out_param = @reg.ExecMethod_("GetStringValue",in_param)
        product[label] = out_param.sValue.to_s 
      }
      yield product.symbolize_keys! 
    }
  end
end

こういうのはWin32OLEで書くほうが楽しいです。こんなかんじで実行すればOK.

so = Software.new
so.each_products {|h|
 next if h[:DisplayName].empty?
 p h
}