最近、Windows端末やMS Officeの情報を扱うために、WMI
(Windows Management Instrumentation) や ospp.vbs
を使う機会がありました。
Python3で扱う方法を探してみたところ、
- WMIは
WMI
パッケージを使う ospp.vbs
はsubprocess.run()
を使う- VBScriptファイルのため
があったため、それらを試してみることにしました。
環境
- ThinkPad X201s
- Windows10 x64
- Python 3.5.1 32bit
- WMI 1.4.9
- pywin32 220
- Microsoft Office
セットアップ
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 Fun !! ~ WMI (Windows Management Instrumentation) に興味がある方・システム管理者必見! ~
- C# WMI - 宇宙仮面のC# Programming
- Win32クラス | IT資産管理 - WhiteFox
- PythonでWMIの内部を覗く - Infrastructure Engineer`s Notes
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クラスのデータを取ってみました。
- Win32_BaseBoard
- Win32_CDROMDrive
- 付属していないため、データ取れず
- Win32_ComputerSystem
- Win32_DesktopMonitor
- Win32_DiskDrive
- Win32_Fan
- データ取れず
- Win32_NetworkAdapterConfiguration
- Win32_OperatingSystem
- Win32_Processor
- Win32_PhysicalMemory
- Win32_VideoController
- Win32_DisplayControllerConfigurationはobsolete
- Win32_Product
- インストールされたアプリを列挙するのに、結構時間がかかることに注意
結果は、GitHubにwmi_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のインスタンスを生成して確認するのが確実だという記載がありました。
- Hey, Scripting Guy! インストールされている Word のバージョンを調べる方法はありますか - Microsoft Office
- windows - WMI "installed" query different from add/remove programs list? - Stack Overflow
インスタンスのプロパティを見ると、バージョン以外にもインストールされているアプリの絶対パスが取得できるため、これを利用します。
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