win_package モジュールのちょっと気になる挙動
Ansible に win_package
というモジュールがあります。
このモジュールは Windows に対して MSI または EXE 形式のパッケージをインストールまたはアンインストールすることができます。
今回はこの win_package
モジュールを用いたパッケージのアンインストール時のちょっと気になる挙動を確認しました。
前提
- Ansible: バージョン 2.9.13
- ターゲットノード: Windows Server 2016 Data Center
- パッケージ: Visual Studio 2013 の Visual C++ 再頒布可能パッケージ
Playbook
Playbook は win_package
モジュールを使うタスク 1 つだけ用意します。
パラメータには引数で情報を渡すようにして、検証で使い回せるように一部は default(omit)
フィルタを設定します。
# playbook.yml --- - hosts: win2016 gather_facts: no become: yes tasks: - name: Validate win_package win_package: path: "{{ path | default(omit) }}" product_id: "{{ product_id | default(omit) }}" arguments: "{{ arguments | default(omit) }}" state: "{{ state }}"
Inventory
ターゲットノードの情報です(雑なのはご勘弁)。
Playbook への引数の渡し方は色々ありますが、今回の検証ではインベントリに変数を設定して、検証パターンによって書き換えます。
# inventory win2016 ansible_host=192.168.2.30 [all:vars] ansible_ssh_port=5986 ansible_connection=winrm ansible_winrm_server_cert_validation=ignore ansible_user=ansible ansible_password=ansible ansible_become_user=ansible ansible_become_method=runas path=<インストーラのパス> product_id=<パッケージのレジストリ キー> arguments=<インストーラ実行時の引数> state=present | absent
インストール
まずは win_package
モジュールを使って Windows に パッケージをインストールします。
インベントリ内の変数は以下のように設定しておきます。
# inventory (snip) path=\\192.168.1.108\Users\Public\vcredist_x64.exe product_id="{730ca3c6-815d-4b47-abc9-5082acd0267f}" arguments=/quiet /norestart state=present
path
はネットワークフォルダ上のインストーラを指定するため、UNC 形式です。
product_id
は Windows の HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall
にある対象パッケージのレジストリ キーです。
Visual Studio 2013 の Visual C++ 再頒布可能パッケージ のレジストリ キーは {730ca3c6-815d-4b47-abc9-5082acd0267f}
になります。
$ ansible-playbook -i inventory playbook.yml -K -vvv (snip) TASK [win_package] ****************************************************************************************************************************************************************************************************************** task path: /home/nnstt1/home-lab/playbook/win_package.yml:6 Using module file /usr/local/lib/python3.6/site-packages/ansible/modules/windows/win_package.ps1 Pipelining is enabled. <192.168.2.30> ESTABLISH WINRM CONNECTION FOR USER: ansible on PORT 5986 TO 192.168.2.30 EXEC (via pipeline wrapper) changed: [win2016] => { "changed": true, "rc": 0, "reboot_required": false } META: ran handlers META: ran handlers PLAY RECAP ************************************************************************************************************************************************************************************************************************** win2016 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
無事にインストールされました。
アンインストール
次に、いくつかのパターンでアンインストールをしてみます。
レジストリ キー (product_id) のみ設定
設定する変数を product_id
と state
だけにします。
このパターンは公式ドキュメントの Example にあるものです。
# inventory (snip) product_id="{730ca3c6-815d-4b47-abc9-5082acd0267f}" state=absent
これを実行すると……
$ ansible-playbook -i inventory win_package.yml -K -vvv (snip) TASK [win_package] ****************************************************************************************************************************************************************************************************************** task path: /home/nnstt1/home-lab/playbook/win_package.yml:6 Using module file /usr/local/lib/python3.6/site-packages/ansible/modules/windows/win_package.ps1 Pipelining is enabled. <192.168.2.30> ESTABLISH WINRM CONNECTION FOR USER: ansible on PORT 5986 TO 192.168.2.30 EXEC (via pipeline wrapper) fatal: [win2016]: FAILED! => { "changed": false, "msg": "failed to run uninstall process (\"\\\"C:\\ProgramData\\Package Cache\\{730ca3c6-815d-4b47-abc9-5082acd0267f}\\vcredist_x64.exe\\\" /uninstall\"): \"1\" 個の引数を指定して \"SearchPath\" を呼び出し中に例外が発生しました: \"Could not find file '\\.exe'.\"", "reboot_required": false } PLAY RECAP ************************************************************************************************************************************************************************************************************************** win2016 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
エラーとなってしまいました。
このパターンでの win_package
モジュールの動きは以下のようです。
今回対象としている Visual Studio 2013 の Visual C++ 再頒布可能パッケージ の UninstallString は
"C:\ProgramData\Package Cache\{730ca3c6-815d-4b47-abc9-5082acd0267f}\vcredist_x64.exe" /uninstall
となっています。
ここに記載された "C:\(snip)\vcredist_x64.exe"
のパスを正しく認識できずにエラーとなっているようです。
試しに、UninstallString を色々書き換えて実行してみました。
C:\(snip)\vcredist_x64.exe /uninstall
→ エラー
"C:\(snip)\vcredist_x64.exe /uninstall"
→ エラー
"C:\(snip)\vcredist_x64.exe"
+ 変数 arguments に/uninstall
追加→ エラー
C:\(snip)\vcredist_x64.exe
+ 変数 arguments に/uninstall
追加→ 成功
どうやらダブルクォートがあることでインストーラのパスを判断できなくなっているようです。 レジストリ書き換えればアンインストールできましたが、実用的ではありません。
インストーラのパス (path) のみ設定
次に、設定する変数を path
と state
だけにします。
このパターンも公式ドキュメントの Example にあるにはあります。
# inventory (snip) path=\\192.168.1.108\Users\Public\vcredist_x64.exe state=absent
これを実行すると……
$ ansible-playbook -i inventory win_package.yml -K -vvv (snip) TASK [win_package] ****************************************************************************************************************************************************************************************************************** task path: /home/nnstt1/home-lab/playbook/win_package.yml:6 Using module file /usr/local/lib/python3.6/site-packages/ansible/modules/windows/win_package.ps1 Pipelining is enabled. <192.168.2.30> ESTABLISH WINRM CONNECTION FOR USER: ansible on PORT 5986 TO 192.168.2.30 EXEC (via pipeline wrapper) fatal: [win2016]: FAILED! => { "changed": false, "msg": "product_id is required when the path is not an MSI or the path is an MSI but not local", "reboot_required": false } PLAY RECAP ************************************************************************************************************************************************************************************************************************** win2016 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
このパターンもエラーとなりました。
インストーラが EXE の場合は変数 product_id
が必須なようです。
レジストリ キー (product_id) とインストーラのパス (path) を設定
では、変数 product_id
と path
両方を指定してみます。
# inventory (snip) path=\\192.168.1.108\Users\Public\vcredist_x64.exe product_id="{730ca3c6-815d-4b47-abc9-5082acd0267f}" state=absent
これを実行すると……
$ ansible-playbook -i inventory win_package.yml -K -vvv (snip) TASK [win_package] ****************************************************************************************************************************************************************************************************************** task path: /home/nnstt1/home-lab/playbook/win_package.yml:6 Using module file /usr/local/lib/python3.6/site-packages/ansible/modules/windows/win_package.ps1 Pipelining is enabled. <192.168.2.30> ESTABLISH WINRM CONNECTION FOR USER: ansible on PORT 5986 TO 192.168.2.30 EXEC (via pipeline wrapper)
このまま応答が返ってきませんでした。
Windows 側のプロセスを確認すると、vcredist_x64.exe
が立ち上がっていました。
arguments=/quiet
が無いため GUI のプロセスとして立ち上がって操作を待ってしまっている状態、と推測しています。
当該プロセスを終了すると、Ansible のほうはエラーとなりました。
fatal: [win2016]: FAILED! => { "changed": false, "msg": "unexpected rc from uninstall \\\\192.168.1.108\\Users\\Public\\vcredist_x64.exe: see rc, stdout and stderr for more details", "rc": 1, "reboot_required": false, "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": [] } PLAY RECAP ************************************************************************************************************************************************************************************************************************** win2016 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
変数すべて設定
結局すべての変数を指定しちゃうのですが、これでいけるでしょう。
インストール時との違いは state=absent
なだけです。
#inventory path=\\192.168.1.108\Users\Public\vcredist_x64.exe product_id="{730ca3c6-815d-4b47-abc9-5082acd0267f}" arguments=/quiet /norestart state=absent
これを実行すると……
$ ansible-playbook -i inventory win_package.yml -K -vvv (snip) TASK [win_package] ****************************************************************************************************************************************************************************************************************** task path: /home/nnstt1/home-lab/playbook/win_package.yml:6 Using module file /usr/local/lib/python3.6/site-packages/ansible/modules/windows/win_package.ps1 Pipelining is enabled. <192.168.2.30> ESTABLISH WINRM CONNECTION FOR USER: ansible on PORT 5986 TO 192.168.2.30 EXEC (via pipeline wrapper) changed: [win2016] => { "changed": true, "rc": 0, "reboot_required": false } META: ran handlers META: ran handlers PLAY RECAP ************************************************************************************************************************************************************************************************************************** win2016 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
changed=1
になっているし、無事に成功したようです。
「結局 state
変えるだけでいいじゃん」と思いつつ、Windows 側を見てみると……
残ってる!!!(分かりづらくてスミマセン。。)
どういうことでしょうか、ちゃんと changed=1
になっているのにアンインストールされていません。
結局どうすれば
上記までのパターンでは、今回対象としている Visual Studio 2013 の Visual C++ 再頒布可能パッケージ のアンインストールはできませんでした。 こうなってしまえばモジュールのソース読むしかありません。
どうやら arguments=/uninstall
という設定が必要なようです。
レジストリの UninstallString に指定されている /uninstall
を自前で用意してあげないとアンインストールの挙動をしてくれません。
結果は割愛しますが、以下の設定でアンインストールできました。
#inventory path=\\192.168.1.108\Users\Public\vcredist_x64.exe product_id="{730ca3c6-815d-4b47-abc9-5082acd0267f}" arguments=<i><b>/uninstall</b></i> /quiet /norestart state=absent
「じゃあ state=absent
にしてインストールすることもできるかも」と考えたのですが、ちゃんと事前にパッケージがインストールされているかレジストリ キーを見て判断しているようです。
おわりに
ということで、win_package
モジュールのちょっと気になる挙動を調べてみました。
他のパッケージのレジストリの UninstallString を見てみると、ほとんど MsiExec.exe /I{xxxx}
となっていました(↓これは ZABBIX Agent)。
これが MSI 形式でインストールされたパッケージなのでしょうか。
今回のようなパターンはほとんど遭遇しないのでは、と思います。 ただ、このような想定外な挙動をした場合の調査方法とは今後に活かしていきたいですね。