時々思い出したようにPowerShellの記事を書いてみます。
スクリプトでよくあるのが、sudoで実行時に権限があるスクリプトの許可をしたいというケースです。
Windowsは組み込みsudoがないので面倒でしたが、現状ならscoopでsudoをインストールするといいです。
scoop install sudo
これでsudo ./your_script.ps1とできるので特権が必要なときに、必要な権限を渡すことができます。
さて今回の記事は、Windowsにおいて実行中のスクリプトや関数にて特権が必要な場合に、sudoを使わずにUACダイアログを出して昇格したPowerShellで同関数を実行し直してほしいというケースです。
通常の特権昇格フロー + Windows Diffender操作のため利用には注意してください。
この2つを自動化できるのは運用で便利ですが、誤った利用は技術を良くない方向に追い込みます。 チーム内での潤滑な運用のための利用に留めることを推奨します。
概要
悪用禁止だけど、チーム内で使うには便利です。 特に、PowerShellのExecution Policyやダブルクリック問題はだいたいこれで解決するのが定番です。(chocolateyやscoopもこの手法)
サンプル
今回は、Windows Defenderによってdotnetのビルドが遅いので除外して対応しようという記事を使ってやってみます。
サンプルスクリプトを置いておきます。
https://github.com/guitarrapc/PowerShellUtil/tree/master/WindowsDefender
Windows PowerShellで次のコマンドを実行すると、Windows Defenderのスキャン除外パスに指定したパス + Visual Studioのパスが入り、除外プロセスにVisual Studioやdotnet、msbuildが入ります。
実行前に十分に気をつけてください
iex (new-object net.webclient).downloadstring('https://raw.githubusercontent.com/guitarrapc/PowerShellUtil/master/WindowsDefender/remote_exec.ps1')
Windows PowerShellを特権のない状態で起動して実行すると、昇格するか聞かれます。

yを押した時だけ、UACダイアログがでて関数がそこで再実行されます。


仕組み
処理の本体は、Add-DefenderExclusionForDevEnvです。
キーはここです。
https://gist.github.com/guitarrapc/96f6259724b723d07ed34f81564fcf99
自分が特権で実行されているかは、これで検出できます。
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) if (!$currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
自分の関数名を、自動変数 $MyInvocationから取ります。
$me = $MyInvocation.MyCommand
自分の関数の定義を、Get-Commandから取ります。
$myDefinition = (Get-Command $me).Definition
これで関数文字列が生成できます。
$myfunction = "function $me { $myDefinition }"
新規プロセスでPowerShell.exeを実行するときに、実行するコマンド文字列を組み立てます。 今のパスに移動します。
$cd = (Get-Location).Path $commands = "Set-Location $cd; $myfunction; Write-Host 'Running $me'; $me; Pause"
さて、生成したコマンド文字列は関数の改行が含まれており、このままではPowerShell.exeの -Command引数に渡せません。 そこで、バイナリにしてBase64文字列を -EncodedCommandにわたすことで解釈させます。
このあたりは、PowerShellを使ったワーム攻撃でもよく使われる手法です。
$bytes = [System.Text.Encoding]::Unicode.GetBytes($commands) $encode = [Convert]::ToBase64String($bytes) $argumentList = "-NoProfile","-EncodedCommand", $encode
あとは、PowerShellを特権で起動するため、-Verb RunAsを指定して先程の引数を食わせます。
PowerShell.exeの実行時に、-Waitをつけることで、起動したPowerShell.exeが自動で閉じません。
$p = Start-Process -Verb RunAs powershell.exe -ArgumentList $argumentList -Wait -PassThru
おわりに
最近のほげもげみてると、こういう記事書くのは心配です。 ただ、標準で用意されている方法を用途を限定して使うことまで阻害されるのは望ましくないものです。
攻撃にすでに利用もされている方法でもあるので、UACは自分でプロセスを起動させるほうがパブリックにはいいです。(ChocolateyやScoopがそう)
どうか正しく使われますように。