tech.guitarrapc.cóm

Technical updates

PowerShell でsudo っぽいものを内蔵した関数を作る

時々思い出したようにPowerShell の記事を書いてみます。

スクリプトでよくあるのが、sudo で実行時に権限があるスクリプトの許可をしたいというケースです。

Windows は組み込みsudo がないので面倒でしたが、現状なら scoop で sudo をインストールするといいと思います。

https://scoop.sh/scoop.sh

scoop install sudo

これで sudo ./your_script.ps1 とできるので特権が必要なときに、必要な権限を渡すことができます。

さて今回の記事は、Windows において実行中のスクリプトや関数が特権が必要な場合に、sudo を使わずにUACダイアログを出して昇格したPowerShellで同関数を実行し直してほしいというケースです。

通常の特権昇格フロー + Windows Diffender操作のため利用には注意してください。

この2つが自動化できるのは運用上は非常に便利ですが、誤った利用は技術を良くない方向に追い込みます。 チーム内での潤滑な運用での利用に留めることを推奨します。

目次

TL;DR

悪用禁止だけど、チーム内で使うには便利です。 特に、PowerShell のExecution Policy やダブルクリック問題はだいたいこれで解決するのが定番です。(chocolatey や scoop もこの手法)

サンプル

今回は、Windows Defender によってdotnet のビルドが遅いので除外して対応しようという記事を使ってやってみます。

baba-s.hatenablog.com

サンプルスクリプトを置いておきます。

PowerShellUtil/WindowsDefender at master · guitarrapc/PowerShellUtil · GitHub

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 を特権のない状態で起動して実行すると、昇格するか聞かれます。

UAC昇格が聞かれる

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

特権に昇格されたPowerShellで関数が再実行される

実行結果

仕組み

処理の本体は、Add-DefenderExclusionForDevEnv です。

PowerShellUtil/Add-DefenderExclusionForDevEnv.ps1 at master · guitarrapc/PowerShellUtil · GitHub

キーはここです。

gist.github.com

自分が特権で実行されているかは、これで検出できます。

$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 がそう)

どうか正しく使われますように。