AnsibleでWindowsにプログラムをインストールする（win_msiとwin_package）

# ansibleでWindowsにプログラムをインストールする（win_msiとwin_package）

プロビジョニング・ツール（環境構築ツール）の[Ansible](http://docs.ansible.com/ansible/)がバージョン2.0となりました。Linuxだけではなく、Windows対応も充実してきていました。

今回は、Windowsへアプリケーションをインストールする操作を、Ansibleで実施する際のメモです。

## win_msiとwin_package

Ansible 2.0では、Windows OSの管理ノードに対して、アプリケーションのインストーラファイルからインストールする作業を直接サポートするモジュールとして従来からのwin_msiモジュールに加えてwin_packageモジュールが提供されています。

### win_msiモジュール

win_msiモジュールは、指定したパスにあるmsi形式のインストーラファイルを実行します。ただし、同じバージョンがインストール済みかどうかはチェックせずに常にmsiexecを実行します。そのため、Ansible上ではこのモジュールの実行結果がインストールが実施されたかどうかによらず常にchangedになってしまいます。

また、インストール可能なファイルはMSI形式に限定されます。

win_msiモジュールのマニュアルは次です。
http://docs.ansible.com/ansible/win_msi_module.html

使用例は次のようになります。

```
  - name: アナログ時計プログラムのインストール
    win_msi: path="\\\\fs.example.local\\installers\\Analog Clock-0.1.0.msi"
```

サイレントインストール・オプションを指定しなくてもいいのがうれしいですね。

しかしながら、毎回changedになってしまうのはいただけません。

### win_packageモジュール

win_packageモジュールは、指定したパスにあるインストーラファイル（msi形式でもexe形式でも可）を実行します。また、インストール済みかどうかを調べてインストール済みであればchangedにはなりません。ただ、インストール済みかどうかの判断に使う情報としてProduct_IDを指定する必要があります。win_packageモジュールは、既にインストール済みかを判断する際に、このProduct_IDを使用します。

win_packageモジュールのマニュアルは次です。
http://docs.ansible.com/ansible/win_package_module.html

使用例は次のようになります。

```
  - name: アナログ時計プログラムのインストール
    win_package:
      path="\\\\fs.example.com\\installers\\Analog Clock-0.1.0.msi"
      Product_Id="122902F6-FD9F-40FC-80A7-8EA75CF382E1"
      arguments="/q"
```

サイレントインストールのオプションを指定します。MSI形式であれば"/q"ですが、それ以外のインストーラはそれぞれで用意されているサイレントインストールのオプションを調べて記述します。

このProduct_IDを取得するには、いったん対象ソフトウェアをインストールしてからOSのコマンドで、あるいはレジストリを調べるのが手っ取り早いです。インストールせずに調べるとしたら、Windows SDKに含まれるOrcaを使うか、後述する方法で調べるかあたりとなります。

インストールされているプログラムのProduct_Idをコマンドで調べるにはPowerShellからGet-WmiObjectコマンドで確認できます。次のWikiページにGet-WmiObjectでソフトウェア情報を取得する方法を記述しています。

[Windows Management Instrumentation - インストールされているソフトウェアの情報を取得する](http://www.torutk.com/projects/swe/wiki/Windows_Management_Instrumentation#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%82%8B%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E3%81%AE%E6%83%85%E5%A0%B1%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B)

または、レジストリを調べることでも確認できます。

```
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
```

この下に、Product_Idのツリーが並んでいます。MSI形式インストーラでインストールされたものは大抵がUUIDとなっています。その他のインストーラでインストールしたものは、ソフトウェア名になっているものがあります。


MSI形式インストーラファイルからProduct_Idを調べるには、次のURLで紹介されているPowerShellスクリプトを使うと便利です。
http://www.scconfigmgr.com/2014/08/22/how-to-get-msi-file-information-with-powershell/

### PowerShellスクリプトでエラー

```
"5" 個の引数を指定して "InvokeMember" を呼び出し中に例外が発生しました: "OpenDatabase,DatabasePath,OpenMode
発生場所 行:1 文字:114
+ ... ase", "InvokeMethod", $Null, $windowsInstaller, @($Path.FullName, 0))
+                                                       ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : COMException

```

このエラーは、MSIファイル名を相対パス（またはファイル名のみ指定）で指定したとき、$Path.FullNameが実在とは別なパスを返したために発生しました。

* D:\users\work\foo.msi
 * $Path.FullName -> C:\Users\torutk\foo.msi

デバッグのためスクリプト中で$Path.FullNameを表示させると、環境変数USERPROFILEの直下にあるパスとなっていました。

```
PS> $pwd
Path
----
D:\users\work

PS> [System.environment]::CurrentDirectory
C:\Users\torutk
```

$Path.FullNameは、PowerShellコンソール上でカレントディレクトリを移動させても、それを反映していないように見えます。これを次のように書き換えます。

```diff
- @($Path.FullName, 0)
+ @((Resolve-Path $Path).Path, 0)
```



## ドメイン環境でのUNCの使用は問題あり

workgroupでは問題なく使えたUNCパスですが、ドメイン環境では問題が発生しました。
"double hop"問題と言われ、クライアント(ここではAnsible実行マシン)、管理ノード、ファイル共有サーバーがそれぞれ別のマシンである場合、認証の制約でファイルへのアクセス権が得られないといったものです。
回避策は、UNCの使用をやめて、httpでインストーラーファイルを取得することです。