Edited at

たとえ途中で再起動と管理者権限が必要でも、自動化をあきらめたらそこで試合終了ですよ…?


前書き

Windowsで自動化していると面倒なケースに遭遇します。

それは再起動を挟みかつ管理者権限が必要なケースです。

たとえば、以下のようなケースになります。

・Aというアプリをインストールしたあとに再起動をしてBというアプリをインストールする

・WindowsUpdateでインストールしたあとに再起動をしてコントロールパネルの設定を実施する

今回は、Windowsにおいて再起動を挟んで管理者権限で実行するスクリプトの書き方について検討してみます。


ログインの自動化

再起動後に自動的にログインを行う方法はマイクロソフトのサポート情報として紹介されています。

How to turn on automatic logon in Windows

https://support.microsoft.com/en-us/help/324737/how-to-turn-on-automatic-logon-in-windows

これをPowerShellで記載すると下記のようになります。

ログインの自動化を有効

$RegPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"

$DefaultUsername = "IEUser"
$DefaultPassword = "Passw0rd!"
Set-ItemProperty -LiteralPath $RegPath 'AutoAdminLogon' -Value "1" -type String
Set-ItemProperty -LiteralPath $RegPath 'DefaultUsername' -Value "$DefaultUsername" -type String
Set-ItemProperty -LiteralPath $RegPath 'DefaultPassword' -Value "$DefaultPassword" -type String

ログインの自動化を無効

$RegPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon'

Set-ItemProperty -LiteralPath $RegPath "AutoAdminLogon" -Value "0" -type String


案1:Run,RunOnceレジストリを利用する

ログイン後のスクリプトの実行を行うために、Run/RunOnceというレジストリキーが存在しています。

Run and RunOnce Registry Keys

https://docs.microsoft.com/en-us/windows/win32/setupapi/run-and-runonce-registry-keys

下記のレジストリキーに文字列のエントリを追加することで、そのレジストリの値で指定されたパスのスクリプトが実行されます。


  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run

  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run

  • HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce

  • HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce

HKEY_LOCAL_MACHINEとHKEY_CURRENT_USERの違いは全てのユーザで行うか、特定のユーザで行うかの違いです。

Runに登録されたスクリプトはログインのたびに実行されるようになります。

RunOnceに登録されたスクリプトは1度実行したあとに削除されます。

ただし、標準ユーザでログインした場合や、セーフモードでログインした場合は実行されません。

セーフモードでも実行したい場合はエントリ名の先頭に「*」を付与します。

RunOnceのエントリはスクリプト実行前に削除されます。これをスクリプト実行後に変更したい場合はエントリ名の先頭に「!」を指定します。

これらのキーのいずれかから実行されるプログラムは、キーの下に登録されている他のプログラムの実行を妨げるため、実行中にキーに書き込むべきではありません。

また、RunOnce下でエントリを継続的に再作成すべきではないです。

これらのスクリプトから実行される実行の権限ですが、HKEY_CURRENT_USERのRunOnceを除きすべて管理者権限がありません。

つまり、HKEY_LOCAL_MACHINEのレジストリの変更や、インストールなどの権限の厳しい作業を行うことはできません。

これらをまとめると以下のようになります。

キー
実行時のエントリ
実行時の管理者権限

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run
残る
なし

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
残る
なし

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce
消える
あり

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce
消える
なし


実行権限の確認方法と使用例

実行権限を確認するには以下のようなスクリプトを、Run,RunOnceで実行して検証します。


chkfunc.ps1

Param(

[String]$path
)
#https://serverfault.com/questions/95431/in-a-powershell-script-how-can-i-check-if-im-running-with-administrator-privil
$cur = [Security.Principal.WindowsIdentity]::GetCurrent()
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal($cur)
$isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
$tm = Get-Date -Format "yyyy/MM/dd HH:mm:ss"

$txt = $tm + " " + $cur.Name + " " + $isAdmin

Add-Content -LiteralPath $path -Value $txt


このスクリプトではパラメータで与えられたファイルに実行時の時刻と、実行ユーザ、管理者権限の有無を登録します。

このスクリプトをRunOnce,Runを使用して実行するには以下のようにレジストリに登録します。

$v = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell -ExecutionPolicy RemoteSigned -File c:\share\chkfunc.ps1 c:\share\hkcu_run.txt'

New-ItemProperty -LiteralPath 'HKCU:Software\Microsoft\Windows\CurrentVersion\Run' -Name 'test1' -PropertyType 'String' -Value $v -force

$v = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell -ExecutionPolicy RemoteSigned -File c:\share\chkfunc.ps1 c:\share\hkcu_runonce.txt'
New-ItemProperty -LiteralPath 'HKCU:Software\Microsoft\Windows\CurrentVersion\RunOnce' -Name 'test2' -PropertyType 'String' -Value $v -force

$v = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell -ExecutionPolicy RemoteSigned -File c:\share\chkfunc.ps1 c:\share\hklm_run.txt'
New-ItemProperty -LiteralPath 'HKLM:Software\Microsoft\Windows\CurrentVersion\Run' -Name 'test3' -PropertyType 'String' -Value $v -force

$v = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell -ExecutionPolicy RemoteSigned -File c:\share\chkfunc.ps1 c:\share\hklm_runonce.txt'
New-ItemProperty -LiteralPath 'HKLM:Software\Microsoft\Windows\CurrentVersion\RunOnce' -Name 'test4' -PropertyType 'String' -Value $v -force

Windows7の環境で2回再起動を繰り返した結果は以下のようになります。


hkcu_run.txt

2019/09/04 13:44:56 IEWIN7\IEUser False

2019/09/04 13:48:32 IEWIN7\IEUser False


hkcu_runonce.txt

2019/09/04 13:44:55 IEWIN7\IEUser False



hklm_run.txt

2019/09/04 13:44:55 IEWIN7\IEUser False

2019/09/04 13:48:32 IEWIN7\IEUser False


hklm_runonce.txt

2019/09/04 13:44:53 IEWIN7\IEUser True


runonceの場合、1度のみ実行されていることと、hklm以下のRunOnceのみが管理者権限で実行できることが確認できます。

なお、この結果はWindows10でも同じでした。


使いどころ

再起動後、一度だけ実行すればよいという状況では有効かもしれません。

もし、複数の再起動がある場合は後述のタスクスケジューラを利用したほうがいいでしょう。


案2:タスクスケジューラを利用する

Windowsが標準で搭載しているタスクスケジューラを利用することで、「ログイン後」「最上位の特権」でスクリプトを自動で実行することが可能です。

タスクスケジューラは管理者権限のあるプロセスからであれば以下のコマンドで利用できます。

schtasks

ログイン後に実行するタスクを登録するスクリプトは以下になります

schtasks /create /tn タスク名 /tr スクリプトのパス /sc onlogon /rl highest /F

/sc オプションでどのタイミングで実行するかを指定します。今回はログイン後なのでonlogonとなります。

/rl hightが最上位の特権をあらわしており、/Fで同名のタスクがあった場合に上書きを行います。

また、、タスクを削除するには以下のようになります。

schtasks /Delete /tn "MyTask" /F


サンプル

今回は再起動後にTortoiseSVNをインストールしてみる例を考えてみます。

一般的なインストーラの自動化方法についてはココに記載しています。

再起動前に実行するスクリプト settask.ps1


settask.ps1

# 管理者権限で実行

# 再起動後に実行するタスクを登録
# 最近のバージョンはRegister-ScheduledTaskが使えそう.
$run = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell -ExecutionPolicy RemoteSigned -File C:\share\after_restart.ps1'
schtasks /create /tn "MyTask" /tr $run /sc onlogon /rl highest /F
if ($lastexitcode -ne 0) {
exit
}

# 検証用のイベントログを登録できるようにSource「test」を追加しておく
New-EventLog -LogName Application -Source test -ea SilentlyContinue

# ログインの自動化
# http://get-cmd.com/?p=4679
$RegPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
$DefaultUsername = "IEUser"
$DefaultPassword = "Passw0rd!"
Set-ItemProperty -LiteralPath $RegPath 'AutoAdminLogon' -Value "1" -type String
Set-ItemProperty -LiteralPath $RegPath 'DefaultUsername' -Value "$DefaultUsername" -type String
Set-ItemProperty -LiteralPath $RegPath 'DefaultPassword' -Value "$DefaultPassword" -type String

Restart-Computer -Force


再起動後に実行するスクリプト after_restart.ps1


after_restart.ps1

Param(

[String]$path
)
#https://serverfault.com/questions/95431/in-a-powershell-script-how-can-i-check-if-im-running-with-administrator-privil
$cur = [Security.Principal.WindowsIdentity]::GetCurrent()
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal($cur)
$isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
$tm = Get-Date -Format "yyyy/MM/dd HH:mm:ss"

$txt = $tm + " " + $cur.Name + " " + $isAdmin

Add-Content -LiteralPath $path -Value $txt


再起動前に実行するスクリプトではタスクスケジューラにタスクを登録して、自動ログインを有効にしたのち、再起動をおこなっています。

再起動後に実行するスクリプトではインストーラを実行したのち、タスクを削除して自動ログインを無効にしています。

今回は、古い環境を考慮してschtasks コマンドを利用しましたが新しいPowerShellが使える環境ではRegister-ScheduledTaskの使用も検討可能です。


リモートからの実行

リモートから再起動を制御できるようになると、同時に複数の端末に対しての自動化が可能になります。

今回は下記の記事の「方法4:schtasks (タスクスケジューラ)」と「方法5:PowerShell (WinRM)」を利用しまして再起動後にTortoiseSVNをリモートからインストールする例を考えてみました。

別端末(Windows)のプログラムを標準機能でリモート起動する方法まとめ

https://qiita.com/0829/items/5518256b348521ac358c

$password = ConvertTo-SecureString "Passw0rd!" -AsPlainText -Force

$cred = New-Object System.Management.Automation.PSCredential("WORKGROUP\IEUser", $password)
$computerName = "IEWin7"

# 再起動前管理者権限がいらないリモート側の処理
Invoke-Command -ComputerName $computerName -Credential $cred -ScriptBlock {
Get-ChildItem -LiteralPath "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall","HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" | %{$_.GetValue("DisplayName")}

# ここで管理者権限で実行させたいスクリプトを書いてしまうのも手
Set-Content -Path c:\share\autologin.ps1 -Value @"
Set-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' 'AutoAdminLogon' -Value 1 -type String
Set-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' 'DefaultUsername' -Value IEUser -type String
Set-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' 'DefaultPassword' -Value Passw0rd! -type String
"@

}

# リモート側で再起動前に実行させたい管理者権限のあるタスクを登録して実行
$run = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell -ExecutionPolicy RemoteSigned -File c:\share\autologin.ps1'
schtasks /create /S IEWIN7 /U IEUser /P Passw0rd! /TN MyTaskInit /sc ONCE /sd 1900/01/01 /st 00:00 /tr $run /rl highest
schtasks /run /S IEWIN7 /U IEUser /P Passw0rd! /TN MyTaskInit
schtasks /delete /S IEWIN7 /U IEUser /P Passw0rd! /TN MyTaskInit /F

# リモート側で再起動後にさせたいタスクを登録
$run = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell -ExecutionPolicy RemoteSigned -File C:\share\after_restart.ps1'
schtasks /create /tn "MyTask" /S IEWIN7 /U IEUser /P Passw0rd! /tr $run /sc onlogon /rl highest /F

# 再起動(応答まで、すごく時間がかかる)
Restart-Computer -ComputerName IEWin7 -Credential $cred -Wait -Force

# 再起動後管理者権限がいらないリモート側の処理
Invoke-Command -ComputerName $computerName -Credential $cred -ScriptBlock {
# 管理者権限がいらないリモート側の処理
Get-ChildItem -LiteralPath "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall","HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" | %{$_.GetValue("DisplayName")}
}

(1)まず、WinRM経由で管理者権限のいらない再起動前の処理を行います。

このときに、管理者権限が必要な処理用のスクリプトを操作対象の端末に作成してます。

(2)操作対象の端末のタスクスケジューラに管理者権限の必要なスクリプトを実行するタスクを追加して実行します。

(3)再起動後に行うスクリプトをタスクスケジューラに登録します。

(4)再起動を実施します。

(5)再起動後、管理者権限の必要のない処理をWinRM経由で行います。


まとめ

今回は自動化を行う上で、再起動があって、管理者権限も必要であるというケースに対応する方法を検討してみました。

昔、偉い人が「あきらめたらそこで試合終了ですよ…?」といいましたが、一見無理っぽいケースにおいても結構やれる方法はあったりします。


参考

How to turn on automatic logon in Windows

https://support.microsoft.com/en-us/help/324737/how-to-turn-on-automatic-logon-in-windows

Automatic Logon in Windows 10 using PowerShell

http://get-cmd.com/?p=4679

Run and RunOnce Registry Keys

https://docs.microsoft.com/en-us/windows/win32/setupapi/run-and-runonce-registry-keys

Reboot and Resume PowerShell Script

https://www.codeproject.com/Articles/223002/Reboot-and-Resume-PowerShell-Script

Windows で任意のコマンド(タスク)を自動実行する (schtasks)

https://maku77.github.io/windows/admin/schtasks.html

Continue Your Automation To Run Once After Restarting a Headless Windows System

https://cloudywindows.io/post/continue-your-automation-to-run-once-after-restarting-a-headless-windows-system/

In a PowerShell script, how can I check if I'm running with administrator privileges?

https://serverfault.com/questions/95431/in-a-powershell-script-how-can-i-check-if-im-running-with-administrator-privil

別端末(Windows)のプログラムを標準機能でリモート起動する方法まとめ

https://qiita.com/0829/items/5518256b348521ac358c