Windows 10 Proの初期設定をAnsibleなどでやってみる。勝手に再起動とかありえないし

# 前書き

Windows 10 1607から使用できるようになったWindows Subsystem for Linux。
ベータ版ということもあり、特に入力周りに問題があり表示が崩れるためまだ常用するとは行かないが、それでもAnsibleを実行するくらいの役には十分立つ。

一方、Windows 10はOSに更新プログラムが当たると勝手に再起動することがあるのが非常に困りものである。
そこを何とかしたい、というのが当初のモチベーション。

それを含めたWindows 10 Pro用の初期設定をほとんど自動でやってしまおうというのがこの記事の目的である。
よくあるMacの初期設定をAnsibleで、ってやつのWindows版を書きたかったという話でもある。

# 本文

## 注意事項

これはWindows 10 1607で実施しているが、将来この記事を書き直すことがあれば将来のバージョンに対応したものに書き換わっている可能性がある。

今回、関係するファイルはすべて同じフォルダに置くものとしている。
Ansibleのplaybookとインベントリファイルは改行コードがLFで文字コードがUTF-8、それ以外は改行コードがCRLFで文字コードがWindows-31Jとする。

## 1st_step: 必要なOS再起動への対処

Windows Subsystem for Linuxを使うには、開発者モードを有効化し、Windows Subsystem for Linuxを機能として有効化するのだが、実際に使えるようになるまでに一度OS再起動が必要になってしまう。[^1]
[^1]: 次期バージョンであるCreators Updateでは再起動しなくても良くなる模様。
そこで再起動前の処理と再起動後の処理を別々のバッチファイルに分け、かつ再起動後にその分の処理を自動で実行されるようにすることとする。
つまり最初の1つのバッチファイルを実行したらセットアップ完了まで何も触らなくても良いようにしている（エラー発生時はエラー内容を確認したいので除く）。

というわけで1st_step.batという名前のバッチファイルをまず作る。このバッチファイルは管理者ユーザで実行する必要がある。

```bat:1st_step.bat
pushd %TEMP%

:: 開発者モードを有効化する
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1"

:: Windows Subsystem for Linuxを機能として有効化する
:: lxrunを実行する前にOS再起動が必要
powershell -Command "Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux -NoRestart"

:: ネットワークプロファイルをプライベートに変更する
powershell -Command "Set-NetConnectionProfile -InterfaceAlias (Get-NetConnectionProfile -IPv4Connectivity Internet).InterfaceAlias -NetworkCategory Private"

:: Ansibleのターゲットとなる準備をするスクリプトを実行する
powershell -Command "curl https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1 -OutFile ConfigureRemotingForAnsible.ps1"
powershell -ExecutionPolicy RemoteSigned .\ConfigureRemotingForAnsible.ps1

:: 後続の実行内容をOS再起動後次回ログイン時に実行するようにする
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnceEx\1" /t REG_SZ /f /v "next" /d "%~dp02nd_step.bat"

:: 自動ログイン
powershell -Command "Set-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -name AutoAdminLogon -value 1"
powershell -Command "Set-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -name DefaultPassword -value ((Select-String ansible_password %~dp0windows.hosts).foreach{ if ($_ -match '=(.*)$') { $matches[1] }}[0])"

:: OS再起動
shutdown -r -t 0
```

レジストリ「HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnceEx\（適当な何か）」キーに適当な名前の値と、実行したいバッチファイルをデータとして登録することでOS再起動後に登録したバッチファイルが実行される。
また「HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon」のAutoAdminLogonとDefaultPasswordを使って一時的に自動ログインが行われるようにして、OS再起動後に何も触らなくても勝手にバッチファイルが実行されるようにしている。
その際のログインユーザは現在と同じユーザとなり、RunOnceExに書いた実行したいバッチファイルは管理者権限付きで実行される。
「HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnceEx\（適当な何か）」の方はOS再起動直後に自動で消滅するのでこちらで消す必要はない。

なお、レジストリへの登録が片やreg addで、片やSet-ItemPropertyで行っているのはパスワードの取得がPowershell使う方法しか思いつかなかったからと、どちらの方法もメモとして残しておきたかったからだ。
このパスワードは以下のように記述したAnsibleのインベントリファイル（この後のAnsibleの実行で使用する）のansible_password項に平文で書いてあったものをもらってきている。当然ansible_user項は現在のユーザである。

```windows.hosts
[local_windows]
local_windows

[local_windows:vars]
ansible_host=localhost
ansible_user=(ユーザ名)
ansible_password=(パスワード)
ansible_port=5986
ansible_connection=winrm
ansible_winrm_server_cert_validation=ignore
```

何度か出てくる「%~dp0」はこのバッチファイルの置かれているフォルダのパスに置換される。
http://pentan.info/server/windows/cmd/dp0.html

「ネットワークプロファイルをプライベートに変更する」「Ansibleのターゲットとなる準備をするスクリプトを実行する」は[AnsibleでWindowsを操作する準備をする](http://qiita.com/yunano/items/f9d5652a296931a09a70)に書いた通りの事前準備である。
今回Ansibleで設定するのはWindows Subsystem for Linuxの中ではなくWindows側なので、操作される準備が必要になる。

このバッチファイルだけecho offしてないのは処理の様子を見たかったからでありわざとである。
Windowsだとcdで別のドライブに移れないのでpushdを使っている。cd /dとか打つくらいならpushdの方が好みなので。

## 2nd_step: AnsibleのPlaybookを動かすまで

OS再起動後に実行されるバッチファイルは2nd_step.batというファイル名にしてある。

```bat:2nd_step.bat
@echo off
pushd "%~dp0"

:: Windows Subsystem for Linuxをインストールする
lxrun /install /y

:: Ansibleをインストールしてplaybookを実行する
sc start winrm
bash -c "add-apt-repository ppa:ansible/ansible -y; apt update; apt install ansible python-pip -y; pip install pywinrm; ansible-playbook -i windows.hosts  -e script_dir='%~dp0' 2nd_step.yml"

:: エラー発生時は実行内容を確認するため一旦停止
pause
```

Windows Subsystem for Linuxを`lxrun /install /y`でインストールしているが、これだとrootユーザがパスワードなしで作られる。
まあWindows Subsystem for Linuxは今はSSHサーバとか上げてないし、外から直接触れないからそんなに問題にはならないだろうから今回はそのままにしてあるが、外から触れるようだと何らかの手当は必要だろう。

winrmサービスは自動だが遅延開始なので、OS起動直後に利用したい場合は明示的にサービスを開始しないとまだ開始されていないことがあり得ることに注意。
もっともこのバッチファイルはAnsibleのplaybookが実行されるまでのインストールに掛かる時間がかなりあるので、そのような配慮をする必要はないが一応サービスを上げておくこととした。

後はWindows Subsystem for Linux内でAnsibleをインストールしてplaybookである2nd_step.ymlを実行。
Windows Subsystem for Linux内で実行したいコマンドは`bash -c "(コマンド)"`で渡すことができる。

またplaybookの最後でOSを再起動するので、どこかでエラーが出た時だけpauseに辿り着いてエラー内容を確認することができる、という寸法である。

2nd_step.ymlの中身は以下の通り。

```yaml:2nd_step.yml
- hosts: all
  gather_facts: no
  tasks:
  - name: 電源接続時にモニターを切らないようにする
    tags: setting
    win_command: powercfg -x monitor-timeout-ac 0

  - name: NuGetをPackageProviderに追加する
    # PolicyFileEditorのインストールに必要
    tags: setting
    win_shell: Install-PackageProvider -Name NuGet -Force

  - name: PolicyFileEditorの存在確認
    tags: setting
    win_shell: Get-Package | ? { $_.Name -eq "PolicyFileEditor" }
    register: exist_pfe
    
  - name: PolicyFileEditorをインストール
    tags: setting
    win_shell: Install-Module -Name PolicyFileEditor -Force
    when: not exist_pfe.stdout
    
  - name: PolicyFileEditorを使ってローカルグループポリシーを変更する
    # 大抵はOS再起動後に適用される
    tags: setting
    win_shell: ipmo PolicyFileEditor; Set-PolicyFileEntry -Path "{{ item.path | default('$env:windir\System32\GroupPolicy\Machine\registry.pol') }}" -Key "{{ item.key }}" -ValueName {{ item.value_name }} -Data {{ item.data }} -Type {{ item.type | default('DWord') }}
    with_items:
    # 「自動更新を構成する」を有効に
    - 
      key: Software\Policies\Microsoft\Windows\WindowsUpdate\AU
      value_name: NoAutoUpdate
      data: 0
    # 「自動更新を構成する」で"4 - 自動ダウンロードしインストール日時を選択"
    - 
      key: Software\Policies\Microsoft\Windows\WindowsUpdate\AU
      value_name: AUOptions
      data: 4
    # 「自動更新を構成する」で"自動メンテナンス時にインストールする"にチェック
    - 
      key: Software\Policies\Microsoft\Windows\WindowsUpdate\AU
      value_name: AutomaticMaintenanceEnabled
      data: 1
    # 「自動更新を構成する」の"インストールを実行する日"（この設定は使用されない）
    - 
      key: Software\Policies\Microsoft\Windows\WindowsUpdate\AU
      value_name: ScheduledInstallDay
      data: 0
    # 「自動更新を構成する」の"インストールを実行する時間"（この設定は使用されない）
    - 
      key: Software\Policies\Microsoft\Windows\WindowsUpdate\AU
      value_name: ScheduledInstallTime
      data: 3
    # 「自動更新を構成する」で"ほかのMicrosoft製品の更新プログラムのインストール"にチェック
    - 
      key: Software\Policies\Microsoft\Windows\WindowsUpdate\AU
      value_name: AllowMUUpdateService
      data: 1
    # 「自動更新を直ちにインストールすることを許可する」を無効に
    - 
      key: Software\Policies\Microsoft\Windows\WindowsUpdate\AU
      value_name: AutoInstallMinorUpdates
      data: 0
    # 「スケジュールされた自動更新のインストールで、ログオンしているユーザーがいる場合には自動的に再起動しない」を有効に
    - 
      key: Software\Policies\Microsoft\Windows\WindowsUpdate\AU
      value_name: NoAutoRebootWithLoggedOnUsers
      data: 1
    # 「Cortanaを許可する」を無効に
    - 
      key: Software\Policies\Microsoft\Windows\Windows Search
      value_name: AllowCortana
      data: 0

  - name: 各種レジストリによる設定変更
    tags: setting
    win_regedit:
      key: '{{ item.key }}'
      value: '{{ item.value }}'
      data: '{{ item.data }}'
      datatype: '{{ item.datatype | default("dword") }}'
    with_items:
    # 固定キー機能がキー入力によって有効になるのを防ぐ
    # https://msdn.microsoft.com/en-us/library/dd373652%28VS.85%29.aspx
    - key: HKCU:\Control Panel\Accessibility\StickyKeys
      value: Flags
      data: 2
      datatype: string
    # 「右シフトキーが8秒間押されたときにフィルターキーを有効にする」の無効化
    - key: HKCU:\Control Panel\Accessibility\Keyboard Response
      value: Flags
      data: 98
      datatype: string
    # 「ウィンドウを画面の横または隅にドラッグしたときに自動的に整列する」の無効化
    - key: HKCU:\Control Panel\Desktop
      value: WindowArrangementActive
      data: 0
      datatype: string
    # 「ときどきスタート画面におすすめを表示する」の無効化
    - key: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager
      value: SystemPaneSuggestionsEnabled
      data: 0
    # 「Windowsを使用するためのヒントやおすすめの方法を取得」の無効化
    - key: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager
      value: SoftLandingEnabled
      data: 0
    # エクスプローラーの「隠しファイル、隠しフォルダー、および隠しドライブを表示する」の有効化
    - key: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced
      value: Hidden
      data: 1
    # エクスプローラーの「登録されている拡張子は表示しない」の無効化
    - key: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced
      value: HideFileExt
      data: 0
    # エクスプローラーの「エクスプローラーで開く」を"PC"に
    - key: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced
      value: LaunchTo
      data: 1
    # エクスプローラーの「別のプロセスでフォルダーウィンドウを開く」の有効化
    - key: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced
      value: SeparateProcess
      data: 1
    # エクスプローラーの「保護されたオペレーションシステムファイルを表示しない」の無効化
    - key: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced
      value: ShowSuperHidden
      data: 1
    # 「小さいタスクバーボタンを使う」の有効化
    - key: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced
      value: TaskbarSmallIcons
      data: 1
    # 「（前略）メニューでコマンドプロンプトをWindows PowerShellに置き換える」の有効化
    - key: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced
      value: DontUsePowerShellOnWinX
      data: 0
    # 「タスクバーを固定する」の無効化
    - key: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced
      value: TaskbarSizeMove
      data: 1
    # エクスプローラーの「タイトルバーに完全なパスを表示する」の有効化
    - key: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CabinetState
      value: FullPath
      data: 1
    # タスクバーに「検索アイコンを表示する」
    - key: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Search
      value: SearchboxTaskbarMode
      data: 1
    # 「このコンピューターへのリモートアシスタンス接続を許可する」の無効化
    - key: HKLM:\SYSTEM\CurrentControlSet\Control\Remote Assistance
      value: fAllowToGetHelp
      data: 0
    # 「このコンピューターへのリモート接続を許可する」を選択
    - key: HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server
      value: fDenyTSConnections
      data: 0
    # 「ネットワークレベル認証でリモートデスクトップを実行しているコンピューターからのみ接続を許可する」の無効化
    - key: HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp
      value: UserAuthentication
      data: 0
    # Windows Timeサービスが問い合わせを行うntpサーバをtime.google.comに変更し、Client/Serverモードとし、問い合わせ間隔をMinPollIntervalからMaxPollIntervalの間とする
    - key: HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters
      value: NTPServer
      data: time.google.com,0x8
      datatype: string
    # Windows Timeサービスがntpサーバーに問い合わせを行う最小間隔を2^8秒とする
    - key: HKLM:\SYSTEM\CurrentControlSet\services\W32Time\Config
      value: MinPollInterval
      data: 8
    # Windows Timeサービスがntpサーバーに問い合わせを行う最大間隔を2^12秒とする
    - key: HKLM:\SYSTEM\CurrentControlSet\services\W32Time\Config
      value: MaxPollInterval
      data: 12
    # Windows Timeサービスがslewモードで同期を行う最大の時刻差を10分とする
    - key: HKLM:\SYSTEM\CurrentControlSet\services\W32Time\Config
      value: MaxAllowedPhaseOffset
      data: 600
    # Windows Timeサービスがslewモードで同期を行う際の修正実行サイクルを100000clock ticksとする
    # https://msdn.microsoft.com/ja-jp/library/cc773263%28v=ws.10%29.aspx によれば1clock ticks = 10^-7秒
    - key: HKLM:\SYSTEM\CurrentControlSet\services\W32Time\Config
      value: UpdateInterval
      data: 100000
    # Windows Timeサービスが同期を実施する、ntpサーバーに対する最大の遅れ秒数を上限なしとする
    - key: HKLM:\SYSTEM\CurrentControlSet\services\W32Time\Config
      value: MaxPosPhaseCorrection
      data: 4294967295
    # Windows Timeサービスが同期を実施する、ntpサーバーに対する最大の進み秒数を10分にする
    - key: HKLM:\SYSTEM\CurrentControlSet\services\W32Time\Config
      value: MaxNegPhaseCorrection
      data: 600
    # Windows Timeサービスのログをイベントに出力する
    - key: HKLM:\SYSTEM\CurrentControlSet\services\W32Time\Config
      value: EventLogFlags
      data: 3

  - name: Windows Timeサービスにネットワーク接続時のみ起動するトリガーイベントを設定する
    # https://support.microsoft.com/ja-jp/kb/2385818
    tags: setting
    win_command: sc triggerinfo w32time start/networkon stop/networkoff

  - name: タスクスケジューラーの履歴を有効化する
    tags: setting
    win_command: wevtutil sl Microsoft-Windows-TaskScheduler/Operational /e:true

  - name: Windows Defenderの更新をタスクスケジューラーで実施する
    tags: setting
    win_shell: Register-ScheduledTask -TaskPath {{ "\ " }}-TaskName "Windows Defender" -User SYSTEM -RunLevel Highest -Action (New-ScheduledTaskAction -Execute "C:\Program Files\Windows Defender\MpCmdRun.exe" -Argument "-SignatureUpdate -MMPC") -Trigger (New-ScheduledTaskTrigger -Once -At "2016/1/1 00:01" -RepetitionInterval "01:30:00" -RandomDelay "00:01:00") -Settings (New-ScheduledTaskSettingsSet -RunOnlyIfNetworkAvailable) -Force

  - name: 開発者モードにすると勝手に起動されるWindowsのSSHサーバを停止する
    # http://yasuhide.blog.jp/archives/48155574.html
    # レジストリを直接変更して開発者モードにした場合は起動していないように見えるので特にやる必要ないかも
    tags: setting
    win_service:
      name: '{{ item }}'
      start_mode: disabled
      state: stopped
    with_items:
    - sshproxy
    - sshbroker
    ignore_errors: yes

  - name: PolicyFileEditorを使ってスタート画面のレイアウトを一旦ストアだけにする
    tags: start_layout
    win_shell: ipmo PolicyFileEditor; Set-PolicyFileEntry -Path $env:windir\System32\GroupPolicy\Machine\registry.pol -Key Software\Policies\Microsoft\Windows\Explorer -ValueName {{ item.value_name }} -Data {{ item.data }} -Type {{ item.type }}
    with_items:
    # 「スタート画面のレイアウト」を有効に
    - value_name: LockedStartLayout
      data: 1
      type: DWord
    # 「スタート画面のレイアウト」で読み込むファイルの指定
    - value_name: StartLayoutFile
      data: '{{ script_dir }}start_layout_store.xml'
      type: ExpandString

  - name: OS再起動後に実行するスクリプトの設定
    tags: restart
    win_regedit:
      key: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnceEx\2
      value: next
      data: '{{ script_dir }}3rd_step.bat'

  - name: OS再起動
    tags: restart
    win_command: shutdown -r -t 0
```

Ansible 2.2で追加されたwin_shellモジュールとwin_commandモジュールだが、結果としてpowershellで実行させたいものはwin_shellモジュールで、cmdで実行させたいものはwin_commandモジュールで実行するのが見た目にわかりやすそうだったのでそうしている。
もちろんwin_commandはパイプとか使えないのでcmdでもパイプとかを含む場合はwin_shellを使わないとならないが、今回そのようなものは1つもない。

ローカルグループポリシーで操作できるものはレジストリを触っても同じ効果が出るはずだが、ここではPolicyFileEditorというツールを使ってローカルグループポリシーを操作することにしている。
これはレジストリを触って値を変更するとローカルグループポリシーエディタ（OSに元からついてるやつ）で見ると変更した値が反映されていないように見えるのが気持ち悪いからである。

ローカルグループポリシーで最大の目的である勝手に再起動を防ぐのをやっている。
この設定で更新プログラムがインストールされて、OS再起動が必要でも再起動待ちの状態になる。
「自動更新を構成する」の（この設定は使用されない）と書かれているものは、ローカルグループポリシーエディタで設定すると使われないけど勝手に設定されるやつをこっちでも設定しておいた、という話。
あとCortana重いから切る。

レジストリの変更でもいろいろやっている。この辺りは趣味の問題でもある。
NTPクライアントの設定は本当にこれで正しいのか調べても正直確信が持てない。

「Windows Defenderの更新をタスクスケジューラーで実施する」のところではいくつかのテクニックが使用されている。
どうも単体の\をplaybook内に記述するのが普通にはできないようで、{{ "\ " }}なんて{{ }}で囲んだ上にスペースまで巻き込むようにしてやっとできた。
あと、毎日決まった時刻に繰り返し実行するタスクは「毎日」、継続時間を「1日間」で作るものだと学んでいたが、Windows 10 1607ではその手法では期待通りの動きにならない。「1回」、継続時間を「無期限」にする必要がある。
https://shimajun.blogspot.jp/2016/08/windows-10-anniversary-update_7.html

「PolicyFileEditorを使ってスタート画面のレイアウトを一旦ストアだけにする」のは「予定と近況」などのタイルはまったく使わないので全消去したいが、全消去したレイアウトファイルは適用に失敗するようなので、適当な何か1つだけ残したものをまず適用してからその1つを削除しようというもの。
ストアだけ残したレイアウトファイルをstart_layout_store.xmlという名前にしている。これは`Export-Startlayout –Path (エクスポートファイルのフルパス)`でエクスポートしたものを編集したものだ。
適当な何か1つ、としたが要素名が「start:Tile」のものと「start:SecondaryTile」のものがあり、「start:SecondaryTile」のものはコマンドで削除する方法が見当たらなかったので残すのは「start:Tile」のものである必要があり、また必ず存在するもので、かつ後で一緒にタスクバーからも消したいものを1つ選んだ結果がストアである。

```xml:start_layout_store.xml
<LayoutModificationTemplate Version="1" xmlns="http://schemas.microsoft.com/Start/2014/LayoutModification">
  <LayoutOptions StartTileGroupCellWidth="6" />
  <DefaultLayoutOverride>
    <StartLayoutCollection>
      <defaultlayout:StartLayout GroupCellWidth="6" xmlns:defaultlayout="http://schemas.microsoft.com/Start/2014/FullDefaultLayout">
        <start:Group Name="" xmlns:start="http://schemas.microsoft.com/Start/2014/StartLayout">
          <start:Tile Size="2x2" Column="0" Row="0" AppUserModelID="Microsoft.WindowsStore_8wekyb3d8bbwe!App" />
        </start:Group>
      </defaultlayout:StartLayout>
    </StartLayoutCollection>
  </DefaultLayoutOverride>
</LayoutModificationTemplate>
```

で、これを適用するにはまたOS再起動が必要になるので3rd_stepへ続く。
自動実行のためにまた「HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnceEx\（適当な何か）」キーを使用するが、前回と同じ名前だと実行してくれないようだったので、適当な何かの部分は前回は「1」だが今回は「2」。

## 3rd_step: アプリのインストールに関するあれこれ

今度のバッチファイルは3rd_step.batとしている。

```bat:3rd_step.bat
@echo off
pushd "%~dp0"

:: playbookを実行する
sc start winrm
bash -c "ansible-playbook -i windows.hosts -e script_dir='%~dp0' 3rd_step.yml"

:: エラー発生時は実行内容を確認するため一旦停止
pause
```

3rd_step.batの内容について説明は2nd_step.batのところの内容でカバーできているので、playbookである3rd_step.ymlの説明に移る。

```yaml:3rd_step.yml
- hosts: all
  gather_facts: no
  tasks:
  - name: スタートレイアウトを解除する
    tags: start_layout
    win_shell: ipmo PolicyFileEditor; Remove-PolicyFileEntry -Path $env:windir\System32\GroupPolicy\Machine\registry.pol -Key Software\Policies\Microsoft\Windows\Explorer -ValueName {{ item }}
    with_items:
      - LockedStartLayout
      - StartLayoutFile

  -
    tags: app
    block:
    - name: chocolateyからautohotkey.installをインストールさせる。初回は失敗するがディレクトリは作られる
      # 現在、 https://autohotkey.com/download/ からダウンロードするのに一手間必要になったため
      win_chocolatey:
        name: autohotkey.install

    rescue:
    - name: autohotkey.installのバージョン取得
      win_shell: choco search autohotkey.install --exact | sls 'autohotkey\.install' | % { $_.ToString().split(' ')[1] }
      register: ahk_version
      changed_when: no

    - name: autohotkeyのインストーラの存在確認
      win_stat:
        path: C:\Users\yuuki\AppData\Local\Temp\chocolatey\autohotkey.install\{{ ahk_version.stdout_lines[0] }}\autohotkey.installInstall.EXE
      register: ahk_stat
    
    - name: autohotkeyのインストーラのダウンロード
      win_get_url:
        url: https://github.com/Lexikos/AutoHotkey_L/releases/download/v{{ ahk_version.stdout_lines[0] }}/AutoHotkey_{{ ahk_version.stdout_lines[0] }}_setup.exe
        dest: C:\Users\yuuki\AppData\Local\Temp\chocolatey\autohotkey.install\{{ ahk_version.stdout_lines[0] }}\autohotkey.installInstall.EXE
        #force: no # エラーになる。GitHubからダウンロードする場合のみ？
      when: not ahk_stat.stat.exists

    - name: 再度autohotkeyのインストールを試みる
      win_chocolatey:
        name: autohotkey.install

  - name: アプリのインストール
    tags: app
    win_chocolatey:
      name: '{{ item.name }}'
      ignore_dependencies: '{{ item.ignore_dependencies | default("no") }}'
      ignore_checksums: '{{ item.ignore_checksums | default("no") }}'
    with_items:
    - name: everything
    - name: linkshellextension
    - name: sudo
    - name: mobaxterm
      ignore_checksums: yes
    - name: sublimetext3
    - name: googlechrome
    - name: firefox
    - name: adobereader
    - name: sysinternals
    - name: dropbox
      ignore_dependencies: yes
    - name: keepass
    - name: keepass-keepasshttp
    - name: winscp

  - name: Everythingを「管理者として実行」しないようにし、サービスとして登録する
    tags: app
    win_command: '"C:\Program Files\Everything\Everything.exe" -disable-run-as-admin -install-service'

  - name: MobaXtermのCygUtilsプラグインを配置
    # 条件不明だが、たまにインストールされていないことがあるため
    tags: app
    win_get_url:
      url: http://mobaxterm.mobatek.net/CygUtils.plugin
      dest: 'C:\Program Files (x86)\Mobatek\MobaXterm Personal Edition'
      force: no

  - name: OS再起動後に実行するスクリプトの設定
    tags: restart
    win_regedit:
      key: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnceEx\3
      value: next
      data: '{{ script_dir }}4th_step.bat'

  - name: OS再起動
    tags: restart
    win_command: shutdown -r -t 0
```

2nd_step.ymlで適用したレイアウトファイルの適用を解除してあるが、これは適用したままだとレイアウトがこの内容で固定されるため、この後のコマンドで変更できないからである。
レイアウトの内容自体は解除後も適用された内容のままである。
これでまたOS再起動が必要になる。

3rd_step.ymlではアプリのインストールも行っている。
これらは別に2nd_step.ymlでやっても良かったが、先に設定の反映をしたかったので時間が掛かるものを後に回した。

Chocolateyでインストールされるいくつかのアプリはautohotkey.portalに依存しているが、残念ながら現在autohotkey.portalのインストールは失敗する。
これは https://autohotkey.com/download/ をWebブラウザで開けばわかるがCAPTCHAを入力しないと本来のダウンロードURLに進めなくなったため。
日本以外でそのような書き込みをまだ見たことがないのでもしかしたらおま国なのかもしれない。
http://techmeou.hatenadiary.jp/entry/2016/10/06/005430

そこでGitHubからダウンロードできるautohotkey.installの方をまずインストールしている。
これをインストールしておけばautohotkey.portalに依存しているアプリもignore_dependenciesをyesにする必要があるがインストール可能となる。

## 4th_step: 後始末とか

最後に実行されるバッチファイル4th_step.batは以下の通り。

```bat:4th_step.bat
@echo off
pushd "%~dp0"

:: スタートメニューとタスクバーからストアのピン留めを外す
powershell -Command "((New-Object -Com Shell.Application).NameSpace('shell:AppsFolder').Items() | ? { $_.Path -match 'WindowsStore' }).Verbs() | ? { $_.Name -match '&[KP]' } | %% { $_.DoIt() }"

:: playbookを実行する
sc start winrm
bash -c "ansible-playbook -i windows.hosts 4th_step.yml"

:: エラー発生時は実行内容を確認するため一旦停止
if errorlevel 1 (
  pause
)
```

スタート画面のレイアウトにストアだけ残しておいたので、タスクバーにあるものもろとも外してしまう。
これはAnsibleでやると（winrm経由でやると、だろうか？）、`((New-Object -Com Shell.Application).NameSpace('shell:AppsFolder')`が空で返ってきてしまうのでバッチファイル側でないとできなかった。

4th_step.ymlではOS再起動は行わないため、pauseを行うのはerrorlevelが1の時、としてみた。
bash -cのエラー時に1以外のリターンコードになる可能性があるかは不明。

```yaml:4th_step.yml
- hosts: all
  gather_facts: no
  tasks:
  - name: 自動ログインの解除(AutoAdminLogon)
    tags: setting
    win_regedit:
      key: HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
      value: AutoAdminLogon
      data: 0
      datatype: dword

  - name: 自動ログインの解除(DefaultPassword)
    tags: setting
    win_regedit:
      key: HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
      value: DefaultPassword
      state: absent
```

これが最後の処理なので自動ログインを解除して、OS起動時に認証が必要な状態に戻す。

# 今後の目標

この企画は2016-09から始めて、たまに手を加えている程度のものなので将来に渡ってもあまり進捗はないと思われるが、一応やりたいことを挙げておく。

* 一部の処理をsysprepで行う

1台でansible-playbookを実行するだけでたくさんのWindowsマシンの初期設定ができるようにしたかったのだが、現状バッチファイル側で行っていることも多く、そこまで持っていくには一部の処理はsysprepに一部の処理を記載したUnattend.xmlを食わせる必要がありそう。この辺りまだ調査してない。
もっともそこまでやるなら1台だけでansible-playbookを実行する理由もなくなる気がするのだが。たぶん最初に実行するバッチファイルもUnattend.xmlに定義するだろうから、それを1台だけに限定する必要が感じられない。

* いらないアプリのアンインストール

後で`powershell -Command "Get-AppxPackage | ? { $_.Name -match 'Candy|LINE|Netflix|FarmVille' } | Remove-AppxPackage"`とか実行しているのだが、このコマンドはこれら不要なアプリのインストールが完了するまでは実行しても意味がないのでバッチファイルに組み込めていない。
そもそも不要なアプリがインストールされるのを阻害したかったのだけど、その方法が見つかっていない。これもUnattend.xmlでできると予想している。

* GUIアプリの設定

設定ファイルやレジストリの書き換えでやるには辛くてGUIで設定した方が良さそうなものの設定はAutoHotKeyを駆使するしかないかな。そもそもChocolateyでインストールできない、サイレントインストールもできないアプリとかインストールしたければそうせざるを得ない。

* Windows Subsystem for Linuxの設定自体はほとんどしていない

表示崩れ対策はSSHサーバインストールしてSSHで繋いで使うとかなのかなあ。
まだ試していない。
ここをどうにかしないとまだ常用する気にはなれない。

* その他、まだ達成していないことがいくつか

特にスタートアップに登録するためのコマンドを見つけられなかったのが心残りである。

---