AnsibleでWindowsを操作する準備をする

# はじめに

Ansible 1.7よりWindowsの操作ができるようになったので、実施するための準備を書き出してみる。

（2015-01-18: 現状に合わせて大きく書き直した）
（2015-03-27: 「Ansible 1.9.0.1を使用する場合の追加作業」を追加）
（2015-04-29: 1.9.1で「Ansible 1.9.0.1を使用する場合の追加作業」が解決されたため、文章を修正）
（2016-02-27: やっと時間が取れたので遅ればせながら文章を2.0に対応）
（2016-04-17: ネットワークプロファイルがパブリックでも利用できるようになっていたのとansible_winrm_*が1.9.5にバックポートされていたのに対応）
（2017-06-03: 2.3.1でpython 3でも動作するようになった）

## 今回の環境

最初に試した段階では以下の通り：
操作されるWindowsは8.1 UpdateおよびWindows Server 2012 R2を使用している。
操作する側はCentOS 7.0.1406を使用しており、Ansible 1.7.1以降で確認している。

# Windows側の準備

AnsibleはWindowsに対してWindows Remote Management(WinRM)というものを使用して操作を行う。
そのため、Windows側に手を入れWinRMの起動と構成を行う必要がある。

まずPowerShellを**「管理者として実行」**する。
ちなみにAnsibleはWindows側にPowerShell 3.0以上を要求するが、Windows 8.1やWindows Server 2012 R2はPowerShell 4.0が初期からインストールされているので問題ない。
どのWindowsのバージョンでPowerShell 3.0以上を利用可能かは後述の「付記：Ansibleで操作可能なWindowsのバージョン」を参照。

[Windows側の準備を行ってくれるスクリプトが公式に用意されている](https://github.com/ansible/ansible/blob/devel/examples/scripts/ConfigureRemotingForAnsible.ps1)ので、用意されたスクリプトをダウンロードする。[^1]

[^1]: PowerShell 4.0以降、Invoke-WebRequestのエイリアスとしてwgetあるいはcurlが使用できる。従ってスクリプトファイルをダウンロードする箇所は次のように書くこともできる。（-Uriも省略できる） `wget https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1 -OutFile ConfigureRemotingForAnsible.ps1`

（以下、PowerShellコンソールのプロンプトを「PS >」で表す）

```
PS > mkdir C:\work
PS > cd C:\work
PS > Invoke-WebRequest -Uri https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1 -OutFile ConfigureRemotingForAnsible.ps1
```

ネットワークプロファイルがパブリック以外の場合、ダウンロードしたConfigureRemotingForAnsible.ps1を実行する。[^2]
[^2]: `Get-ExecutionPolicy`を実行して、RemoteSignedより緩い設定になっているなら`.\ConfigureRemotingForAnsible.ps1`だけで良い。ExecutionPolicyは特に変更していなければWindows 2012 R2を除きRestrictedになっている。今のところWindows 2012 R2だけはRemoteSigned。ExecutionPolicyについて詳しくは http://qiita.com/kikuchi/items/59f219eae2a172880ba6 辺り。

ネットワークプロファイルはWindows 8/2012以降の場合PowerShell上で`Get-NetConnectionProfile -IPv4Connectivity Internet`を実行するとNetworkCategoryの値で知ることができる。Windows 7ではネットワークの共有センターなどで確認できる。[^3]

[^3]: Windows 8/2012以降の場合、`Set-NetConnectionProfile -InterfaceAlias (Get-NetConnectionProfile -IPv4Connectivity Internet).InterfaceAlias -NetworkCategory Private`でネットワークプロファイルをプライベートに変更できる。GUIで実施するならマウスを右下に持っていき、「設定＞PC設定の変更＞ネットワーク」で「接続済み」となっているネットワークをクリックして、デバイスとコンテンツの検索がオフならばパブリックなのでオンに変更する。Windows 7ではネットワークの共有センターなどで変更する。

```
PS > powershell -ExecutionPolicy RemoteSigned .\ConfigureRemotingForAnsible.ps1
```

ネットワークプロファイルがパブリックの場合は-SkipNetworkProfileCheckオプションを追加して実行する。

```
PS > powershell -ExecutionPolicy RemoteSigned .\ConfigureRemotingForAnsible.ps1 -SkipNetworkProfileCheck
```

## 付記：Ansibleで操作可能なWindowsのバージョン

Ansibleで操作可能なWindowsのバージョンは以下のようにまとめることができる。

PowerShell 3.0以上が最初からインストールされている
→Windows 8以降、あるいはWindows Server 2012以降

最初からインストールされているのはPowerShell 2.0以前だが、PowerShell 3.0をインストールすることができる
→Windows 7、Windows Server 2008 R2

PowerShellが最初からインストールされているわけではないが、PowerShell 3.0をインストールすることができる
→Windows Server 2008

Windows Server 2008とコードベースが同じにもかかわらず、Windows VistaにはPowerShell 3.0は提供されなかった。

以下のページに表としてまとまっている。
http://ja.wikipedia.org/wiki/Windows_PowerShell

Windows 7、Windows Server 2008 (R2)にPowerShell 3.0をインストールするためのスクリプトは[公式ドキュメント](http://docs.ansible.com/intro_windows.html)からリンクされる以下にあるが、PowerShell 3.0をインストールするのに必要な.NET Framework 4以上をインストールする部分がコメントアウトされているので、そこは手動で行う必要がある。
https://github.com/cchurch/ansible/blob/devel/examples/scripts/upgrade_to_ps3.ps1
もっとも、このスクリプトがやってることはインストーラをダウンロードしてインストールしているだけなので、適当にWebブラウザからダウンロードしてインストールするのでも問題ない。

# Ansible側の準備

一方、Ansible側にもWinRMと通信するための準備が必要である。

pipをインストールし[^4]、pipでpywinrmをインストール。

[^4]: 本文ではpipをインストールするのに汎用的な`curl -sL https://bootstrap.pypa.io/get-pip.py | python`というコマンドにしたが、CentOSだとEPELにpython-pipがあるので`yum install python-pip`でも良い。Ubuntuなんかにもpython-pipがあった。

```
# curl -sL https://bootstrap.pypa.io/get-pip.py | python
# pip install pywinrm
```

Ansible 1.9.xまでは以下のようなインベントリファイルを作成する。
ここで指定するWindows側のユーザはAdministratorsグループに所属していないとWinRM接続ができずansibleコマンド実行時にエラーになる。
ドメインに所属している場合はDomain Adminsグループ、Enterprise Adminsグループでも良い。

ansible_winrm_server_cert_validation=ignoreはPython 2.7.9以降の場合に必要となる。このパラメタはAnsible 2.0/1.9.5以降、かつpywinrmが0.1.1以降の場合のみ使用されるが、これらの条件を満たしていない場合に書いても無害である。（→[Python 2.7.9以降、HTTPSの証明書の検証を行うようになった仕様変更](#python-279以降httpsの証明書の検証を行うようになった仕様変更)）

```text:hosts
[windows]
<Windowsの解決可能なホスト名かIPアドレス>

[windows:vars]
ansible_ssh_user=<Windows側のユーザ名>
ansible_ssh_pass=<Windows側ユーザのパスワード>
ansible_ssh_port=5986
ansible_connection=winrm
ansible_winrm_server_cert_validation=ignore
```

Ansible 2.0ではansible_ssh_で始まるパラメタ名はdeprecateになった。
2.0時点ではまだ上の書き方もできるが、今後以下のようになる。

```text:hosts
[windows]
<Windowsの解決可能なホスト名かIPアドレス>

[windows:vars]
ansible_user=<Windows側のユーザ名>
ansible_password=<Windows側ユーザのパスワード>
ansible_port=5986
ansible_connection=winrm
ansible_winrm_server_cert_validation=ignore
```

パスワードを平文で保存するのが嫌ならばAnsible Vaultに入れるなり、実行時に-kオプションで入力するなりすること。

動作確認。

```
# ansible windows -i hosts -m win_ping -vvvv
```

なお、Ansible 2.3.1以降、python 3でも動作する。

# 旧バージョンでのバグ

## winrm connection pluginのバグ (1.7.0 - 1.7.2)

Ansible 1.7.0から1.7.2ではWinRM接続に失敗しエラーになるのでAnsibleのソースを1箇所修正する。
https://github.com/ansible/ansible/issues/8720 （タイトルにhttpsとか書いてあるがhttpでも普通に発生する）
1.8.0にて修正されたので、以降のバージョンではこの作業は不要。

```
# sed -i.bak '90s/exc\.args\[0\]/exc/' /usr/lib/python2.7/site-packages/ansible/runner/connection_plugins/winrm.py
```

## Windows系モジュールの欠落 (1.8.0 - 1.8.1)

Ansible 1.8.0および1.8.1ではAnsibleのWindows系のモジュールファイルのうち拡張子がps1のものが欠落している。なのでwin_pingモジュール実行前に、以下を実行してダウンロードしておく。
1.7.xや1.8.2以降ではこの作業は不要。

```
# for x in {setup,slurp,win_feature,win_get_url,win_group,win_msi,win_ping,win_service,win_stat,win_user}; do wget https://raw.githubusercontent.com/ansible/ansible-modules-core/release1.8.0/windows/${x}.ps1 -P /usr/lib/python2.7/site-packages/ansible/modules/core/windows/; done
```

これを行わなかった場合、以下のようなエラーメッセージが出力される。

```
FAILED => module win_ping not found in configured module paths.  Additionally, core modules are missing. If this is a checkout, run 'git submodule update --init --recursive' to correct this problem.
```

このバグの原因はパッケージ化する際に使用される https://github.com/ansible/ansible/blob/devel/setup.py のpackage_dataの指定が足りないためである。

## Execution PolicyがRestrictedの場合のエラー (1.9.0.1)

Ansible 1.9.0.1ではWindows側のPowershellのExecution PolicyがRestrictedだとWinRM接続時のAnsible実行が失敗する。この時、エラーメッセージの一部に「<モジュール名>.ps1 cannot be loaded because running scripts」と表示される。
（Windows 8.1 Updateで確認）

Windows側でExecution Policyを例えばRemoteSignedに変更すれば実行可能になる。
この変更はOS再起動後も有効である。

```
PS > Set-ExecutionPolicy RemoteSigned -Force
```

Restrictedの状態のままでもrawモジュールは使えるので、「Ansible側の準備」が完了していればAnsible側から以下のように実行してRemoteSignedに変更することもできる。

```
# ansible windows -i hosts -m raw -a 'powershell Set-ExecutionPolicy RemoteSigned -Force'
```

この現象は1.9.0.1でのみ発生する。
（正確には1.9.0でも発生するはずだが、一瞬で更新されたバージョンなので特に確認していない）

# HTTPプロトコルでの接続を許可する

Windows側でConfigureRemotingForAnsible.ps1を実行すると、HTTPSプロトコルを使用したWinRM接続が許可されるようになる。
WinRM接続ではHTTPSプロトコル以外にHTTPプロトコルを使用することもできるが、ConfigureRemotingForAnsible.ps1ではHTTPプロトコルは許可されないようになっている。

本来HTTPSプロトコルだけを許可すればAnsibleでWindowsを操作するのに事足りるが、何らかの理由でHTTPプロトコルでの接続も許可したい場合は、追加で以下のコマンドを実行すれば良い。

```
PS > winrm set winrm/config/service '@{AllowUnencrypted="true"}'
```

これはWinRMの設定を変更するコマンドである。
現在の設定内容は`winrm get winrm/config`で確認できる。

Windows側でHTTPプロトコルを許可した場合、HTTPSで使用する5986番ポート以外に、5985番ポートでの接続が可能な状態になる。

従ってAnsible側のインベントリファイルは以下のようにしても接続可能になる。

```text:hosts
[windows]
<Windowsの解決可能なホスト名かIPアドレス>

[windows:vars]
ansible_user=<Windows側のユーザ名>
ansible_password=<Windows側ユーザのパスワード>
ansible_port=5985
ansible_connection=winrm
```

Ansible 2.0/1.9.5以降ansible_winrm_(キー)という名前のパラメタをセットすると、pywinrmへキーと値のセットを渡すようになった。
そのため、2.0/1.9.5以降ではプロトコルはansible_winrm_schemeパラメタで指定可能である。
ansible_winrm_schemeの初期値はansible_portの値が5985の場合のみhttp、それ以外ならhttpsである。

```text:hosts
[windows]
<Windowsの解決可能なホスト名かIPアドレス>

[windows:vars]
ansible_user=<Windows側のユーザ名>
ansible_password=<Windows側ユーザのパスワード>
ansible_port=80
ansible_connection=winrm
ansible_winrm_scheme=http
```

Ansible 1.9.4まではプロトコルを指定することはできず、5985番ポートを使う場合には自動的にHTTPが選ばれ、5986番ポートを使うことにした場合は自動的にHTTPSが選ばれるようになっている。[^5]

[^5]: 正確には、5985番の場合はまずHTTPで接続しに行って、繋がらなければHTTPSで接続する。それ以外のポート番号ならまずHTTPSで接続しに行って、繋がらなければHTTPで接続する（ただし、1.9からはHTTPSで接続しに行って繋がらなければそこで終了する）。という処理になっている。

# HTTPSプロトコル使用時に関するメモ

## 自己署名証明書の期限

ConfigureRemotingForAnsible.ps1によってWindows側では自己署名証明書（いわゆるオレオレ証明書）が作られるが、有効期限は作成から1年になる。しかしWindows側で時計を5年過去に戻してConfigureRemotingForAnsible.ps1を実行してから時計を正しい時刻まで進めた場合でも、特にエラーになることなく操作できている。

## Python 2.7.9以降、HTTPSの証明書の検証を行うようになった仕様変更

Python 2.7.9が2014-12-10にリリースされたが、Ansible側でPython 2.7.9を使用して、HTTPSプロトコルを使ってWinRM接続しようとすると次のようなエラーになる。
`WINRM CONNECTION ERROR: 500 WinRMTransport. [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:581)`
これは自己署名証明書に対しての[バグではなく仕様変更](https://www.python.org/dev/peps/pep-0476/)なのでPython 2.7.10以降でも同様だろう。

Ansible 2.0/1.9.5以降、かつpywinrmが0.1.1以降である場合、ansible_winrm_server_cert_validation=ignoreでPython 2.7.9以降のHTTPSの証明書の検証を行わないようにしてエラーを回避することができる。
このオプションは2.7.8以前の場合に付けたとしても無害である。

```text:hosts
[windows]
<Windowsの解決可能なホスト名かIPアドレス>

[windows:vars]
ansible_user=<Windows側のユーザ名>
ansible_password=<Windows側ユーザのパスワード>
ansible_port=5986
ansible_connection=winrm
ansible_winrm_server_cert_validation=ignore
```

Ansible 1.9.4まではansible/runner/connection_plugins/winrm.py の _winrm_connectメソッドの最初にでも次を追加すればとりあえず解決する。

```py
        import ssl
        ssl._create_default_https_context = ssl._create_unverified_context
```

情報元：https://github.com/ansible/ansible/pull/10132

# 実践編

1. [AnsibleでChocolateyを使ってWindowsアプリをインストールする](http://qiita.com/yunano/items/5677685b61816f6e62a3)
2. [AnsibleでWindows ServerにActive Directoryを構成する](http://qiita.com/yunano/items/8bddf084007671a38f57)
3. [AnsibleでWindowsをSSHサーバにする](http://qiita.com/yunano/items/cf2b212022c1a91cf48d)

----