＜Ansible＞「win_command」モジュールで文字化けしたときの対処方法

#はじめに
以前、Ansibleの`win_command`モジュールでコマンドを実行した際、出力結果が文字化けしてしまうということがありました。
その際に`ansible 2.10.x`の`win_command`モジュール（`ansible.windows.win_command`）の`output_encoding_override`を使用したことで、文字化けが解決しました。
この記事では、Ansibleの`win_command(ansible.windows.win_command)`モジュールの`output_encoding_override`の使い方について説明します。
※公式ドキュメントは[こちら](https://docs.ansible.com/ansible/latest/collections/ansible/windows/win_command_module.html)から確認できます
# パラメータ（アーギュメント）
`output_encoding_override`の前に、`win_command(ansible.windows.win_command)`のパラメータについて簡単に説明します。
使うことは少ないかもしれませんが、`win_command(ansible.windows.win_command)`にもパラメータは存在します。

|パラメータ  |選択肢：<font color="MediumBlue">デフォルト</font>  |機能  |
|---|:-:|---|
|**chdir**  |－|コマンド実行前に移動するディレクトリのパスを指定する。|
|**creates**  |－|作成するファイルのパスを指定する。<br>指定したファイルが既に存在する場合、この処理は実行されない。|
|**free_form**  |－|実際に、この名のパラメータがあるわけではない。<br>コマンドを指定することが必須となる。|
|**output_encoding_override**  |－|この記事で扱うパラメータ。<br>2.9以前のバージョンには存在しない。<br>[System.Text.Encoding]::GetEncodings()の出力に基づく有効な文字コードを指定する（[Encoding.GetEncodings Method](https://docs.microsoft.com/dotnet/api/system.text.encoding.getencodings)）。<br>これにより、エンコーディングされた出力結果を書き換える。|
|**removes**  |－|削除するファイルのパスを指定する。<br>指定したファイルが存在しない場合、この処理は実行されない。|
|**stdin**  |－|コマンドの引数を指定する。<br>※[標準入力 (stdin) で引数を渡す理由](https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#why-pass-args-over-stdin)|
#動作環境
以下の環境で動作を確認しました。
・コントロールノード：RHEL 8.3
・ターゲットノード：Windows Server 2019
どちらもAWSのAmazon EC2を使用しています。

Ansibleのバージョンは`2.10.3`です。

```
$ ansible --version
ansible 2.10.3
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /root/.pyenv/versions/3.9.0/lib/python3.9/site-packages/ansible
  executable location = /root/.pyenv/versions/3.9.0/bin/ansible
  python version = 3.9.0 (default, Dec  3 2020, 08:36:30) [GCC 8.3.1 20191121 (Red Hat 8.3.1-5)]
```
#`output_encoding_override`の使い方

#####文字化けしたときの状態
文字化けした際、下記のPlaybookを使用していました。
ターゲットノードであるWindows Serverでコマンド`w32tm /query /status`を実行させて、コントロールノードであるRHELに結果を表示するという処理内容になっています。

```YAML
---
- name: '文字化け'
  hosts: windows
  gather_facts: no
  tasks:
  - name: 'コマンド実行'
    ansible.windows.win_command: w32tm /query /status
    register: qiita

  - name: '結果表示'
    debug:
      msg: "{{ qiita }}"
...
```
実行すると、次のような結果が表示されます。

```
# ansible-playbook test.yml

PLAY [文字化け] ********************************************************************

TASK [コマンド実行] ******************************************************************
changed: [10.10.10.10]

TASK [結果表示] ********************************************************************
ok: [10.10.10.10] => {
    "msg": {
        "changed": true,
        "cmd": "w32tm /query /status",
        "delta": "0:00:00.078167",
        "end": "2021-08-03 12:12:25.005529",
        "failed": false,
        "rc": 0,
        "start": "2021-08-03 12:12:24.927362",
        "stderr": "",
        "stderr_lines": [],
        "stdout": "?[?C???W?P?[?^?[: 0 (?x?????)\n?K?w: 4 (???駱?? - (S)NTP ?????)\n???x: -23 (?e?B?b?N????? 119.209ns)\n???[?g?x??: 0.0006471s\n???[?g???U: 7.7908155s\n?Q?? ID: 0xA9FEA97B (?\\?[?X IP:  123.45.67.89)\n??I??????????: 2021/08/03 0:06:57\n?\\?[?X: 123.45.67.89,0x9 \n?|?[?????O??u: 9 (512s)\n\n",
        "stdout_lines": [
            "?[?C???W?P?[?^?[: 0 (?x?????)",
            "?K?w: 4 (???駱?? - (S)NTP ?????)",
            "???x: -23 (?e?B?b?N????? 119.209ns)",
            "???[?g?x??: 0.0006471s",
            "???[?g???U: 7.7908155s",
            "?Q?? ID: xxxxxxxxxx (?\\?[?X IP:  123.45.67.89)",
            "??I??????????: 2021/08/03 0:06:57",
            "?\\?[?X: 123.45.67.89,0x9 ",
            "?|?[?????O??u: 9 (512s)",
            ""
        ]
    }
}

PLAY RECAP *********************************************************************
10.10.10.10                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```
結果が文字化けして表示されています。
[こちら](https://docs.microsoft.com/en-us/dotnet/api/system.text.encoding.getencodings?view=net-5.0#remarks)にも記載があるのですが、Unicode 以外のエンコーディングの多くは不完全で、多くの文字が "?" に変換されるそうです。
#####文字化けしない状態
文字化けしないように表示するには、下記のように、`output_encoding_override`で文字コードを指定します。
今回は`shift_jis`を使用しています。

```YAML
---
- name: '綺麗に出力'
  hosts: windows
  gather_facts: no
  tasks:
  - name: 'コマンド実行'
    ansible.windows.win_command: w32tm /query /status
    args:
      output_encoding_override: shift_jis
    register: qiita

  - name: '結果表示'
    debug:
      msg: "{{ qiita }}"
...
```
実行します。

```
# ansible-playbook test.yml

PLAY [綺麗に出力] ********************************************************************

TASK [コマンド実行] ******************************************************************
changed: [10.10.10.10]

TASK [結果表示] ********************************************************************
ok: [10.10.10.10] => {
    "msg": {
        "changed": true,
        "cmd": "w32tm /query /status",
        "delta": "0:00:00.078159",
        "end": "2021-08-03 12:16:22.825437",
        "failed": false,
        "rc": 0,
        "start": "2021-08-03 12:16:22.747277",
        "stderr": "",
        "stderr_lines": [],
        "stdout": "閏インジケーター: 0 (警告なし)\n階層: 4 (二次参照 - (S)NTP で同期)\n精度: -23 (ティックごとに 119.209ns)\nルート遅延: 0.0006471s\nルート分散: 7.7908155s\n参照 ID: 0xA9FEA97B (ソース IP:  123.45.67.89)\n最終正常同期時刻: 2021/08/03 0:15:29\nソース: 123.45.67.89,0x9 \nポーリング間隔: 9 (512s)\n\n",
        "stdout_lines": [
            "閏インジケーター: 0 (警告なし)",
            "階層: 4 (二次参照 - (S)NTP で同期)",
            "精度: -23 (ティックごとに 119.209ns)",
            "ルート遅延: 0.0006471s",
            "ルート分散: 7.7908155s",
            "参照 ID: xxxxxxxxxx (ソース IP:  123.45.67.89)",
            "最終正常同期時刻: 2021/08/03 0:15:29",
            "ソース: 123.45.67.89,0x9 ",
            "ポーリング間隔: 9 (512s)",
            ""
        ]
    }
}

PLAY RECAP *********************************************************************
10.10.10.10                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```
文字化けせず、正常に出力されています。
余談ですが、以下のようにすることで、必要な情報のみ表示することもできます。

```YAML
---
- name: '綺麗に出力'
  hosts: windows
  gather_facts: no
  tasks:
  - name: 'コマンド実行'
    ansible.windows.win_command: w32tm /query /status
    args:
      output_encoding_override: shift_jis
    register: qiita

  - name: '結果表示（最終正常同期時刻）'
    debug:
      msg: "{{ qiita.stdout_lines | select('match', '最終*') | list }}"
...
```
```
# ansible-playbook test.yml

PLAY [綺麗に出力] ********************************************************************

TASK [コマンド実行] ******************************************************************
changed: [10.10.10.10]

TASK [結果表示（最終正常同期時刻）] ********************************************************************
ok: [10.10.10.10] => {
    "msg": [
        "最終正常同期時刻: 2021/08/03 0:15:29"
    ]
}

PLAY RECAP *********************************************************************
10.10.10.10                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```
# あとがき
バージョンが上がることで便利な機能が追加されるのはありがたいです。
今後も便利なものがあれば調べてまとめていきたいです。