tl;dr
Windows Server で OS 起動時にとあるスクリプトを起動させたいと思って、調べたり、教えて頂いたりしたことをメモ。そして、それらを Ansible や Serverspec で管理することを前提として最適な方法を検討してみたい。(試した環境は Windows Server 2012 だけど Windows Server 2012 R2 でもイケるはず)
勘違い
当初は...
上記のように
に起動したいスクリプトを放り込んでいた。
上記の状態だと、リモートデスクトップでログオンした際にスクリプトが起動した。
OS が起動した際にはスクリプトは起動してくれないという事実に気付いたのはだいぶん後だった。
いや、違う
俺がやりたいのは
OS が起動した時には既に起動していて欲しい。
ということで
ローカルグループポリシーを使う場合
- バッチスクリプトの用意
起動したいスクリプトを以下のようにバッチスクリプトにしておく。
python.exe C:\Users\Administrator\Documents\python\o-re-no-service03.py
- ローカルグループポリシーエディタを起動する
以下のように gpedit.msc と入力してローカルグループポリシーエディタを起動する。
以下のように Computer Configuration をクリックして Scripts(Startup/Shutdown) をクリック、そして Startup をダブルクリックする。
- バッチスクリプトを選択する
以下のように Startup Properties が開くので Add... をクリックして起動したいバッチスクリプトを選択する。
スクリプトの引数まで指定することが出来るが、今回は特に引数無し。
以下のようにスクリプトが登録されたことを確認する。
スクリプトが登録されると以下のように C:\Windows\System32\GroupPolicy\Machine\Scripts に保存されている隠しファイル(script.ini)に設定が書き込まれる。
中身は以下のような内容となっている。
[Startup] 0CmdLine=C:\Users\Administrator\Documents\python\start.bat 0Parameters=
- スクリプトが起動していることを確認
再起動後、スクリプトを確認する場合には以下のように PowerShell スクリプトで確認するか、タスクマネージャで確認することになる。
PS C:\Users\Administrator> Get-WmiObject Win32_Process -Filter "name = 'python.exe'" | select -First 1 CommandLine -Expa ndProperty CommandLine python.exe C:\Users\Administrator\Documents\python\o-re-no-service03.py
以下のようにタスクマネージャで確認することが出来る。
- ちょっとした罠
以下のように C:\Windows\System32\GroupPolicy\Machine\Scripts\Startup にスクリプトを放り込めば...と思ったけど、このフォルダにスクリプトを放り込んでも OS 起動時ではなく、ログオン時にスクリプトが起動されるので期待した動作にはならない。
タスクスケジューラを利用する場合
- 同僚 O 氏有難うございますmm
同僚の O 氏の教えて頂いたタスクスケジューラを利用する方法。
- 構成管理ツールで管理する場合には....
個人的にこちらの方法が Ansible 等で管理する場合には良さそうという結論になった。理由については以下の通り。
- 登録したスタートアップタスクを XML で書き出すことが出来る - 書きだした XML を PowerShell を使ってインポートすることが出来る - パラメータは XML を修正することで、ある程度は修正可能 - ローカルグループポリシーエディタで書き出される script.ini を弄るのは怖い、敷居が高い
細かいタスク登録手順については以下の記事が詳しい、美しい。
ウィザードに従って、タスクトリガーにてコンピュータの起動時を選択する。
Ansible + PowerShell を使って OS 起動時にスクリプトを実行させる例
必ず手動で一度は登録しなければいけない...
以下の通り、登録済みのタスクを Export するところから始める。
Export したタスクは以下のような XML フォーマットになっている。
<?xml version="1.0" encoding="UTF-16"?> <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> <RegistrationInfo> <Date>2016-03-31T23:40:31.1912347</Date> <Author>WIN-XXXXXXXXXXX\Administrator</Author> </RegistrationInfo> <Triggers> <BootTrigger> <Enabled>true</Enabled> </BootTrigger> </Triggers> <Principals> <Principal id="Author"> <UserId>S-1-5-18</UserId> <RunLevel>LeastPrivilege</RunLevel> </Principal> </Principals> <Settings> <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries> <AllowHardTerminate>true</AllowHardTerminate> <StartWhenAvailable>false</StartWhenAvailable> <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable> <IdleSettings> <StopOnIdleEnd>true</StopOnIdleEnd> <RestartOnIdle>false</RestartOnIdle> </IdleSettings> <AllowStartOnDemand>true</AllowStartOnDemand> <Enabled>true</Enabled> <Hidden>false</Hidden> <RunOnlyIfIdle>false</RunOnlyIfIdle> <WakeToRun>false</WakeToRun> <ExecutionTimeLimit>P3D</ExecutionTimeLimit> <Priority>7</Priority> </Settings> <Actions Context="Author"> <Exec> <Command>C:\Users\Administrator\Documents\python\start.bat</Command> </Exec> </Actions> </Task>
PowerShell を利用して XML ファイルをインポートしてタスクを登録する
以下のような PowerShell スクリプトを作成して Export した XML ファイルをインポートしてタスクを登録する。
# # Import Task # $XmlFile = "C:\path\to\" + $args[0] + ".xml" $Xml = (get-content $XmlFile | out-string) Register-ScheduledTask ` -Xml "$Xml" ` -TaskName $args[0]
Export した XML ファイルを C:\path\to\ 以下に保存してスクリプトを実行するとタスクが登録される。
PS C:\oreno> .\import-task.ps1 Oreno-Script TaskPath TaskName State -------- -------- ----- \ Oreno-Script Ready
念のために確認。
PS C:\oreno> Get-ScheduledTask -TaskName Oreno-Script TaskPath TaskName State -------- -------- ----- \ Oreno-Script Ready
Ansible の Script モジュールを利用してタスクを登録する
前のステップで利用したタスクをインポートする PowerShell スクリプトを利用して、以下のような Playbook を作成して Ansible 経由でタスクを登録してみる。
# # - files/scripts/ 以下のファイルをリモートホストの c:/path/to 以下にコピーする # - name: スクリプトファイルをアップロードする win_copy: src=scripts/ dest=c:/path/to # # - check-task.ps1 で登録済みのタスクをチェック # - import-task.ps1 で XML ファイルをインポートしてタスクを登録 # - name: 登録済みタスクが無いかをチェックする script: files/check-task.ps1 Oreno-Script always_run: yes failed_when: no changed_when: no register: task_info - name: デバッグ出力 debug: var=task_info.stdout - name: PowerShell スクリプトと XML を利用してイベントベースのタスクを登録する script: files/import-task.ps1 Oreno-Script when: task_info.stdout != "Oreno-Script\r\n" register: result_info - name: デバッグ出力 debug: var=result_info.stdout
冪等性を出来るだけ担保する為に登録済みタスクをチェックするスクリプトも以下のように用意しておく。
# # 登録済みのタスクをチェックする # Get-ScheduledTask -TaskName $args[0] | select -First 1 TaskName -ExpandProperty TaskName
最終的には以下のようなファイル構成となる。
$ tree roles/oreno-script/ roles/oreno-script/ ├── files │ ├── check-task.ps1 │ ├── import-task.ps1 │ └── xml │ └── Oreno-Script.xml └── tasks └── main.yml 3 directories, 4 files
Playbook を流してみる
早速、Playbook を流してみる。
$ ansible-playbook -i hosts default.yml PLAY *************************************************************************** TASK [oreno-script : スクリプトファイルをアップロードする] *************************************** ok: [ec2-xx-xxx-xx-xx.ap-northeast-1.compute.amazonaws.com] TASK [oreno-script : デバッグ出力] *************************************************** ok: [ec2-xx-xxx-xx-xx.ap-northeast-1.compute.amazonaws.com] => { "task_info.stdout": "VARIABLE IS NOT DEFINED!" } TASK [oreno-script : 登録済みタスクが無いかをチェックする] *************************************** ok: [ec2-xx-xxx-xx-xx.ap-northeast-1.compute.amazonaws.com] TASK [oreno-script : デバッグ出力] *************************************************** ok: [ec2-xx-xxx-xx-xx.ap-northeast-1.compute.amazonaws.com] => { "task_info.stdout": "" } TASK [oreno-script : PowerShell スクリプトと XML を利用してイベントベースのタスクを登録する] ************** changed: [ec2-xx-xxx-xx-xx.ap-northeast-1.compute.amazonaws.com] TASK [oreno-script : デバッグ出力] *************************************************** ok: [ec2-xx-xxx-xx-xx.ap-northeast-1.compute.amazonaws.com] => { "result_info.stdout": "\r\nTaskPath TaskName \r\n-------- -------- \r\n\\ Oreno-Script \r\n\r\n\r\n" } PLAY RECAP ********************************************************************* ec2-xx-xxx-xx-xx.ap-northeast-1.compute.amazonaws.com : ok=6 changed=1 unreachable=0 failed=0
流し終わったら、OS を再起動する。
登録されたことを確認する...Serverspec で
以下のようにテストを書く。
require 'spec_helper' context "自動起動スクリプトがタスクスケジューラに登録、有効になっている場合..." do describe command("powershell -Command \"Get-ScheduledTask -TaskName 'Oreno-Script' | select -First 1 TaskName -ExpandProperty TaskName\"") do its(:stdout) { should match /Oreno-Script/ } end describe command("powershell -Command \"Get-ScheduledTask -TaskName 'Oreno-Script' | select -First 1 State -ExpandProperty State\"") do its(:stdout) { should match /Running/ } end end context "スクリプトが正常に起動している場合..." do describe process("python.exe") do it { should be_running } its(:CommandLine) { should match /o-re-no-service03.py/ } end end
テストを走らすと以下のように。
$ bundle exec rake serverspec:ec2-xx-xxx-xx-xx /home/vagrant/.rbenv/versions/2.2.3/bin/ruby -I/home/vagrant/git/sample-ansible-win/vendor/bundle/ruby/2.2.0/gems/rspec-core-3.4.1/lib:/home/vagrant/git/sample-ansible-win/vendor/bundle/ruby/2.2.0/gems/rspec-support-3.4.1/lib /home/vagrant/git/sample-ansible-win/vendor/bundle/ruby/2.2.0/gems/rspec-core-3.4.1/exe/rspec --pattern spec/\{oreno-script\}/\*_spec.rb 自動起動スクリプトがタスクスケジューラに登録、有効になっている場合... Command "powershell -Command "Get-ScheduledTask -TaskName 'Oreno-Script' | select -First 1 TaskName -ExpandProperty TaskName"" stdout should match /Oreno-Script/ Command "powershell -Command "Get-ScheduledTask -TaskName 'Oreno-Script' | select -First 1 State -ExpandProperty State"" stdout should match /Running/ スクリプトが正常に起動している場合... Process "python.exe" should be running CommandLine should match /o-re-no-service03.py/ Finished in 7.77 seconds (files took 0.67635 seconds to load) 4 examples, 0 failures
おけけ。
考察
OS 起動時にスクリプトを実行する方法
構成管理ツールで手軽に管理出来そうなのは
タスクマネージャを使うパターンが良さそう。理由としては、繰り返しになるけど以下の通り。
- 登録したスタートアップタスクを XML で書き出すことが出来る
- 書きだした XML を PowerShell を使ってインポートすることが出来る
- パラメータは XML を修正することで、ある程度は修正可能
- タスクスケジューラのコンソールから Status が確認出来る(実行している場合には
Running
となる) - ローカルグループポリシーエディタで書き出される script.ini を弄るのは怖い、敷居が高い
以上。