Ansibleで WindowsServerへセキュリティアップデート（KB）を個別にインストールする　その２　～　ディレクトリ配下にある複数MSUファイルをlsコマンドで動的に取得してループしてインストール

## 0.はじめに

Windows2012ServerR2へAnsibleからMSUファイルを利用してKBをインストールするサンプルのAnsible playbookです。
Chefでも似たようなことはできると思います。

本投稿は前回の[投稿](http://qiita.com/tbuchi888/items/b371b8a71319fa4dbc8a)のアップデート版です。
patchディレクトリ配下にあるMSUファイルをlsコマンドで動的に取得し、MSUファイル分ループしてインストールする形へアップデートしたもの([blockのloopをwith_itmes * include](http://qiita.com/tbuchi888/items/c03e7cbce8608b1c55f1) , [jinja2 filter regex_replace](http://qiita.com/tbuchi888/items/80fded8d11366e967290) をなどを駆使)
なお、前回には無い、blook処理とKBインストール前後でKBのインストールチェックを追加していますが
再起動をしないと反映されないKBの場合？は、
インストール後のチェックコマンド部分（`win_patch_child.yml`の17～20行目、）をコメントアウトするか、
インストール後のチェックについては別のプレイブックで実施、またはServerspec等で確認をお願いします。

以下は前回より変更ありません。
rawモジュールなどを使った泥臭いやり方ですが、Windows関連の事例が少ないのであくまで参考までに。
実際にはserviceの上げ下げやreboot、上位のLB制御なども必要だと思いますがシンプルにKBのインストールのみを記載しています。
また、インストールについてはDISMではなくpkgmgrを使用しています。

## 1.大まかな流れ

### Ansibleサーバ側で
0.　事前にプレイブック実行ディレクトリにpatchディレクトリ作成と配下に対象のmsuファイルを手動/自動でダウンロードしておく

**`win_patch.yml`**
1.　ls コマンドでpatchディレクトリ配下のファイル名を動的に取得し、playbookのlsコマンド結果変数へ格納
2.　lsコマンド結果変数で存在するファイル分、動的にループ（`with_items`）し、拡張子がMSUファイルの場合のみ、KBファイルのインストールを行うプレイブック**`win_patch_child.yml`**を呼出す(`include`)　、取得したMSUファイル名を`regex_replace`により正規表現で動的に加工し、KB番号など変数としてセット

### 以下Ansible ServerからターゲットのWindowsへ
**`win_patch_child.yml`**
1.　msuファイルをコピーするためのディレクトリを作成
2.　msuファイルをコピー
3.　msuファイルを圧縮解凍
4.　pkgmgrでサイレントインストール

## 2.playbook等

``` win_patch.yml
---
- hosts: win
# Don't gather hosts facts for performance
  gather_facts: no
# Setting the task
  tasks:
   - name: "Get Win pacth name"
     shell: ls patch
     delegate_to: localhost
     register: command_result

   - name: "Install Win patch"
     include: win_patch_child.yml kb_msu={{ item }}
     with_items: "{{ command_result.stdout_lines }}"
     when: "'.msu' in item"
     vars:
       kb_src_dr:             "patch"
       kb_dest_dr:            "c:/work/"
       kb_src:                "{{ kb_src_dr }}/{{ kb_msu }}"
       kb_dest:               "{{ kb_dest_dr }}/{{ kb_msu }}"
       kb_xml:                "{{ kb_msu | regex_replace('.msu','.xml') }}"
       kb_no:                 "{{ kb_msu | regex_replace('^.*-KB(\\d*)-.*msu$','\\1') }}"
       check_command:         "wmic qfe | findstr {{ kb_no }}"
# When I use wusa/pkgmagr as follows vars.
       decompression_command: "wusa {{kb_dest}} /extract:{{ kb_dest_dr }}"
       install_command:       "pkgmgr /n:{{ kb_dest_dr }}{{ kb_xml }} /quiet /norestart"
       uninstall_command:     "wusa /uninstall /kb:{{ kb_no }} /quiet /norestart"
# When you use expand/Dism, you comment out a line of wusa/pkgmagr, and please use a variable as follows.
#       kb_cab:                "{{ kb_msu | regex_replace('.msu','.cab') }}"
#       decompression_command: "expand -f:{{kb_cab}} {{ kb_dest_dr }}{{ kb_msu }} {{ kb_dest_dr }}KB{{ kb_no }}"
#       install_command:       "Dism /Online /Add-Package /PackagePath:{{ kb_dest_dr }}KB{{ kb_no }} /quiet /norestart"
#       uninstall_command:     "wusa /uninstall /kb:{{ kb_no }} /quiet /norestart"
```

``` win_patch_child.yml
---
  - name: Check KB
    raw: "{{ check_command }}"
    register: command1_result
    failed_when: command1_result.rc not in [ 0 , 1 ]

  - block:
      - debug: msg="Block START ----------------------"
      - name: create directory
        win_file: path={{ kb_dest_dr  }} state=directory
      - name: copy the file
        win_copy: src={{ kb_src }} dest={{ kb_dest }} mode=0755
      - name: decompression the msu file
        raw: "{{ decompression_command }}"
      - name: install KB
        raw: "{{ install_command }}"
      - name: Check KB
        raw: "{{ check_command }}"
        register: command2_result
        failed_when: "'KB{{ kb_no }}' not in command2_result.stdout"
      - debug: msg="Block END ------------------------"
    when: "'KB{{ kb_no }}' not in command1_result.stdout"

    rescue:
      - debug: msg="Rescue START ---------------------"
      - debug: msg="install {{ kb_no }} is error"
      - name: uninstall KB
        raw: "{{ uninstall_command }}"
      - debug: msg="Rescue END -----------------------"

    always:
      - debug: msg="Always START ---------------------"
      - debug: msg="before '{{ check_command }}' {{ command1_result.stdout_lines }}"
        when: command1_result is defined
      - debug: msg="after  '{{ check_command }}' {{ command2_result.stdout_lines }}"
        when: command2_result is defined
      - debug: msg="Always END -----------------------"

  - debug: msg="Because KB{{ kb_no }} had been already installed, I skipped installation.
                This is result of the check command '{{ check_command }}'
                {{ command1_result.stdout_lines }}"
    when: "'KB{{ kb_no }}' in command1_result.stdout"
```

インベントリファイル `hosts`

``` hosts
[win]
win0[1:2]

[win:vars]
ansible_ssh_user=ansible
ansible_ssh_pass=ansibleansible
ansible_ssh_port=5985
ansible_connection=winrm
```

## 3.その他

patchディレクトリ配下

```
patch/
- Windows8.1-KB3079904-x64.msu
- Windows8.1-KB3140735-x64.msu
- openssl-1.0.1e-48.el6_8.1.x86_64.rpm
```

`ansible --version`

```
# ansible --version
ansible 2.2.0 (devel 394430a61e) last updated 2016/06/28 13:25:07 (GMT +900)
  lib/ansible/modules/core: (detached HEAD 3c6f2c2db1) last updated 2016/06/28 13:25:30 (GMT +900)
  lib/ansible/modules/extras: (detached HEAD 1c36665545) last updated 2016/06/28 13:25:37 (GMT +900)
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```