PowerShellでAssemblyのLoad/Unloadを簡単に
Add-TypeでAssemblyをロードしちゃうと、Unloadできないじゃないですか。
あの仕様って、どんな嫌がらせなんですかね?
じゃあってんで、PowerShellプロセスを使い捨てにするのが一般的だと思うけど、そうすると一部は残したい、という場合にまたAdd-Typeしなきゃいけない。
解決方法を考えてみた。
Unloadできるのが一番ですが、、、手があるか調べきれなかったので、History泥棒をすることにしました。
しかも、コマンド実行毎に泥棒する方法がわからず、タイマーで泥臭くやってたりします。
名前付きパイプによるプロセス間通信のサンプルでもあります。
スクリプト
Start-Timer/Start-Taskに依存しています。
Powershellで非同期実行 - じゅんじゅんのきまぐれ
function Open-SandBox() { $baseName = 'SandBox' $exitCommand = "Close-$baseName" $pipeName = "Junjun$baseName" Write-Verbose "$baseName Init" $base = @" `$$pipeName = New-Object PSObject -Property @{ pipe = New-Object System.IO.Pipes.NamedPipeClientStream '.', '$pipeName', InOut id = 0 timer = `$null keep = `$null } `$$pipeName.pipe.Connect() `$$pipeName | Add-Member -MemberType ScriptMethod 'Write' { Param(`$msg) `$wb = [System.Text.Encoding]::UTF8.GetBytes(`$msg) `$wbl = [BitConverter]::GetBytes(`$wb.Length) `$$pipeName.pipe.Write(`$wbl, 0, `$wbl.Length) `$$pipeName.pipe.Write(`$wb, 0, `$wb.Length) } `$$pipeName | Add-Member -MemberType ScriptMethod 'SendKeepHistory' { if(`$$pipeName.keep -ne `$null) { `$$pipeName.keep | %{ `$$pipeName.Write(`$_) } `$$pipeName.keep = `$null } } `$$pipeName | Add-Member -MemberType ScriptMethod 'SendHistory' { `$h = Get-History -Count 1 if(`$h.Id -eq `$$pipeName.id + 1) { `$$pipeName.Write(`$h.CommandLine) `$$pipeName.id = `$h.Id `$$pipeName.SendKeepHistory() } elseif(`$h.Id -gt `$$pipeName.id) { Get-History -Count (`$h.Id - `$$pipeName.id) | %{ if(`$_.Id -gt `$$pipeName.id) { `$$pipeName.Write(`$_.CommandLine) `$$pipeName.id = `$_.Id `$$pipeName.SendKeepHistory() } } } } Register-EngineEvent ([System.Management.Automation.PsEngineEvent]::Exiting) -Action { Remove-Timer `$$pipeName.timer.Name `$$pipeName.SendHistory() `$$pipeName.SendKeepHistory() `$$pipeName.Write('exit') `$$pipeName.pipe.Close() } | Out-Null `$$pipeName.timer = Start-Timer { `$$pipeName.SendHistory() } 5000 function $exitCommand() { exit } Write-Host $baseName 'Start' "@ while($res.result -eq $null -or $res.result.Count -eq 0 -or $res.result[$res.result.Count - 1] -ne $exitCommand) { if($res.result -ne $null) { if($res.result.Count -gt 0) { $res.result[0] = '' } for($i=1; $i -lt $res.result.Count; $i++) { $res.result[$i] = $res.result[$i] -replace '''', '''''' } if($res.result.Count -gt 1) { $run = $base + "`n`$$pipeName.id=''`n@(" + ($res.result -join ''',''').Substring(2) + ''')' + @" | %{ Write-Host `$_ if(!`$$pipeName.id.StartsWith('a')) { `$$pipeName.id = (Read-Host 'run?(Y/n/abort/all)') } if(`$$pipeName.id -eq 'all' -or (`$$pipeName.id -ne 'n' -and `$$pipeName.id -ne 'abort')) { iex `$_ if(`$$pipeName.keep -eq `$null) { `$$pipeName.keep = @() } `$$pipeName.keep += `$_ } } `$$pipeName.id = 0 "@ } } if($run -eq $null) { $run = $base } $res = Start-Task { $pipe = New-Object System.IO.Pipes.NamedPipeServerStream $args[1], InOut $pipe.WaitForConnection() $lenb = New-Object byte[] 4 $loop = $true while($loop) { $len = $pipe.Read($lenb, 0, $lenb.Length) $len = [BitConverter]::ToInt32($lenb, 0) $buf = New-Object byte[] $len $len = $pipe.Read($buf, 0, $buf.Length) $cmd = [System.Text.Encoding]::UTF8.GetString($buf, 0, $len) if($cmd -match '^exit') { $loop = $false } else { $cmd } } $pipe.Close() } -Silent -MessageData $pipeName PowerShell -NoLogo -NoExit -EncodedCommand $([Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($run))) } Write-Host $baseName 'End' }
Open-SandBoxすると、子PowerShellを開きます。(同じウインドウ)
そこで、Add-Typeやら何やらする。
環境刷新したくなったら「exit」すると、一旦親に戻るものの、再度子PowerShellが起動します。
この時、前の子PowerShellのHistory(もどき)を渡されるので、取捨できます。
無限ループです。
終わりたい場合は、子PowerShellで「Close-SandBox」してください。
解説他
子PowerShellは随時Historyを親に送ります。
新しい子PowerShellは、もらったHistoryのそれぞれの実行確認をします。
(「Y/n/all/abort」は「n」で実行しない、「all」で以降実行、「abort」で以降停止、他は実行、です。だいたい)
子PowerShellで、Close-SandBoxすると、SandBoxモードが終わります。
コマンド実行毎にHistoryを盗みたかったのですが、方法不明のため、5秒タイマーで盗んでます。
5秒以内に、$MaximumHistoryCountを超える入力があると、ロストします。
(そういう想定の場合は、タイマー間隔を短くするか$MaximumHistoryCountを増やしてください)
子から親へは名前付きパイプで転送しています。
.Net remotingのIPCチャネルは結局名前付きパイプらしいので。
Exitのイベント捕捉は、stuncloudさんのブログで知りました!(ありがとうございます!)
HistoryInfo、シリアライズできないなんて聞いてないよー。
デフォルトコンストラクターがないのです。
Export-Clixml&Import-Clixmlでファイル経由の方がスマートかつ便利なんですけど、、、。
ファイル経由を回避するにはこの方法でした、、、。
みんな、メモリーディスクを確保すると良いよ!