Python3 + WMI + ospp.vbsで、Windows端末やMS Office情報を取得してみた

最近、Windows端末やMS Officeの情報を扱うために、WMI(Windows Management Instrumentation) や ospp.vbsを使う機会がありました。

Python3で扱う方法を探してみたところ、

があったため、それらを試してみることにしました。

   

環境

 

セットアップ

WMIはpipでインストールします。

>virtualenv -p c:\python35-32\python.exe env
>env\Scripts\activate
(env) >pip install wmi

 
WMIパッケージを使うにはpywin32が必要なため、そちらもインストールします。

ただ、

  • PyPIからpywin32をインストール
  • sourceforgeのbuildからインストール

という方法ではエラーとなりました。

# PyPIのpywin32
(env) >pip install pywin32
...
  Could not find a version that satisfies the requirement pywin32 (from versions: )
  No matching distribution found for pywin32

# sourceforgeから
(env) >pip install https://sourceforge.net/projects/pywin32/files/pywin32/Build%20220/pywin32-220.win32-py3.5.exe/download
...
Command "python setup.py egg_info" failed with error code 1 in C:\Users\<user>\AppData\Local\Temp\pip-3zs7858g-build

 
そこで、以下を参考に、

  • PyPIのpypiwin32を使う
  • easy_installを使う

どちらかの方法でインストールします。
参考:python - How to install win32com module in a virtualenv? - Stack Overflow

# pypiwin32を使う
(env) >pip install pypiwin32
...
Successfully installed pypiwin32-219

# easy_installを使う方法
(env) >easy_install https://sourceforge.net/projects/pywin32/files/pywin32/Build%20220/pywin32-220.win32-py3.5.exe/download
...
Finished processing dependencies for pywin32==220

 

WMIによるWindows端末の情報取得について

WMIクラスなどの資料

WMIではいろいろとできますが、今回はWindows端末情報を取得してみます。

以下の資料を参考に、ソフトウェアやソフトウェアのクラスを使います。

 
日本語の場合は、以下の資料が参考になりました。

 

WMIパッケージの使い方

公式のチュートリアルが充実しているため、そちらが参考になります。
wmi Tutorial — WMI v1.4.9 documentation

ローカルの情報を取得するコードは以下となります。

client = wim.WMI()
for info in client.Win32_OperatingSystem():
    print("Caption: {}".format(info.Caption))
    #=> Microsoft Windows 10 Home

for info in client.Win32_NetworkAdapterConfiguration():
    print("Description: {}".format(info.Description))
    #=> Intel(R) 82577LM Gigabit Network Connection
    #=> Intel(R) Centrino(R) Advanced-N 6250 AGN

 
今回は、手元のThinkPadで以下のWin32クラスのデータを取ってみました。

 
結果は、GitHubwmi_results.txtとして上げておきました。公開するとまずそうな部分は、*でマスクしてあります。
py-wmi_sample/wmi_results.txt at master · thinkAmi-sandbox/py-wmi_sample

 

リモート端末の情報を取得

WMIはローカルだけではなく、指定したリモートホストの情報も取得できます。

 

ファイアウォールの設定変更

デフォルトの場合、おそらくWMIの受信をファイアウォールでブロックしていると思います。

そのため、Windowsファイアウォールで以下の規則を有効にします。

  • 受信
    • Windows Management Instrumentation (DCOM 受信)
    • Windows Management Instrumentation (WMI 受信)

 
あるいは、Active Directory環境などでグループポリシーが使える場合には、以下のパスにあるWindowsファイアウォール:着信リモート管理の例外を許可する有効にします。

コンピュータの構成 > ポリシー > 管理用テンプレート > ネットワーク > ネットワーク接続 > Windowsファイアウォール > ドメインプロファイル > Windowsファイアウォール:着信リモート管理の例外を許可する > 有効

その後、gpupdate /forceして、即反映させます。

 
なお、このままだと接続元を制限していないため、グループポリシー内の要請されない着信メッセージを許可するIPアドレス192.168.1.0/24 などを指定して制限をかけておきます。
参考: グループ・ポリシーでWindowsファイアウォールをまとめて管理する - @IT

 
wmicコマンドでファイアウォール設定の動作確認をします。

# 設定が成功している場合
>WMIC /NODE:"ok_host" OS GET CSName
CSName
ok_host

# 設定が失敗している場合
>WMIC /NODE:"ng_host" OS GET CSName
ノード - ng_host
エラー:
説明 = RPC サーバーを利用できません。

 

 

Pythonスクリプトの実行

WMIインスタンスを作る際にホスト名を与えれば、リモートの情報を取得できます。

client = wim.WMI("remote_host")
for info in client.Win32_OperatingSystem():
    print("Caption: {}".format(info.Caption))
    #=> Microsoft Windows 7 Professional

 

ospp.vbsによるMS Officeの情報取得

最近のMS Officeのプロダクトキーについて

最近のMS Officeのプロダクトキーは、

  • パッケージにあるカードに記載されているプロダクトキー
  • ディスクからインストールする際のプロダクトキー
    • Microsoftアカウント画面から確認できる方はこちら

の2種類があります。
参考:ディスクからインストール | パッケージ製品 (FPP) インストール | Microsoft Office

 
ただ、Microsoftアカウント画面では所有しているプロダクトキーは分かるものの、どの端末にどのプロダクトキーがインストールされているのかは分かりません。

そのため、MS Officeがインストールされている端末にて、ospp.vbsを使ってプロダクトキーの末尾5桁を確認します。

 

ospp.vbsの場所について

ospp.vbsのある場所は、MS Officeがインストールされている場所(C:\Program Files (x86)\Microsoft Office\Office15 等)です。

ただ、WMIのWin32_Productから取得しようとすると、

client = wim.WMI()
for s in client.Win32_Product():
    if "Office" in s.Name:
        print(s.InstallLocation)
        # => C:\Program Files (x86)\Microsoft Office\

と、Win32_Product.InstallLocationからでは、Officeのバージョンが書かれているディレクトリの1つ上までしか取得できません。

そのため、Python + WMIで取得するには、バージョン番号とインストールされているビット版の組み合わせから、インストールパスの取得をゴリゴリ書く必要がありそうでした。

 
なにか良い方法がないかを調べてみたところ、以下の記事にて、Microsoft Wordのインスタンスを生成して確認するのが確実だという記載がありました。

 
インスタンスのプロパティを見ると、バージョン以外にもインストールされているアプリの絶対パスが取得できるため、これを利用します。

 
MS Office複数インストールされている場合にはうまくいかなそうな気もします。ただ、最近のOfficeでは異なるバージョンをインストールするときの注意点があることからも、複数インストールはあまり考えなくても良さそうと判断しました。

 
PythonからOfficeのオブジェクトを使うには、WMIを使う時にインストールしたpywin32を使います。

 
今回はどこにでも入っていそうなExcelで試してみたところ、バージョンを含むパスが取得できました。

import win32com

excel = win32com.client.Dispatch("Excel.Application")
print(excel.Path)
# => C:\Program Files (x86)\Microsoft Office\Office15

excel.Quit()

 

Pythonからospp.vbsを実行する

vbsファイルを起動するには、

  • os.system()
  • subprocess.call()
  • subprocess.run()

などが考えられますが、os.systemは非推奨であり、また、Python3.5からsubprocess.run()が使えるとのことだったので、今回はsubprocess.run()を使うことにしました。
参考:17.5. subprocess — サブプロセス管理 — Python 3.5.1 ドキュメント

excel = win32com.client.Dispatch("Excel.Application")
office_dir = excel.Path
excel.Quit()

ospp_path = os.path.join(office_dir, "ospp.vbs")
subprocess.run(["cscript", ospp_path, "/dstatus"])

 
実行結果は以下となりました。

Microsoft (R) Windows Script Host Version 5.8
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

---Processing--------------------------
---------------------------------------
SKU ID: ***
LICENSE NAME: Office 15
LICENSE DESCRIPTION: ***
LICENSE STATUS:  ---LICENSED---
ERROR CODE: 0 as licensed
Last 5 characters of installed product key: *****
---------------------------------------
---------------------------------------
---Exiting-----------------------------

 
なお、ospp.vbsリモートホストのライセンス状況も調べられます。その場合は、

subprocess.run(["cscript", ospp_path, "/dstatus", "remote_host"])

のように、subprocess.run()の引数の最後にリモートホスト名を追加します。環境によっては、その後にユーザ名とパスワードも必要になります)

また、手元のOfficeとリモートのOfficeが異なるバージョンの場合は、

<No installed product keys detected>

のように表示されることもあります。

 

その他参考

今回作るにあたり、悩んだことなどをまとめておきます。

WMI.Win32_Product.Nameでcp932以外の文字コードが入る

インストールしているソフトウェアによっては発生します。自分の手元ではVisual Studioまわりで

UnicodeEncodeError: 'cp932' codec can't encode character '\xe9' in position 12: illegal multibyte sequence

というエラーが発生しました。

その場合は、Name.encode("utf-8")のように、バイト列表現としました。

 

WMI.Win32_Productで列挙されるアプリ名をソートする

デフォルトの場合、アプリ名でのソートはされておらず見づらいところがあります。

そこで、以下のようにソートして表示しました。
参考: [Python] コレクション型をソート - Qiita

# Nameプロパティをキーとした辞書を作る
products = { s.Name: s for s in self.client.Win32_Product() }

# 辞書のキー順にソート
for k, v in sorted(products.items()):
    self.show(v, PROPERTIES)

 

ソースコード

GitHubに上げておきました。
thinkAmi-sandbox/py-wmi_sample