問題の簡単な説明:
スクリプトが生成する追加の PowerShell 実行空間からの散在していない出力が必要です。出力の個々の行の色を設定できる必要があります。
問題の長い説明:
複数のリモート サーバー上のベンダー アプリケーションに更新を展開するスクリプトを作成しました。当初、スクリプトは一度に 1 つの更新のみを展開することを目的としていましたが、複数の更新を対話的に処理するという新しい要件があります。そのため、実行空間を使用するようにスクリプトを書き直して、すべてのサーバーでセッションを開いて初期化できるようにしました。その後、ユーザーが更新を入力するたびに、初期化されたセッションごとに実行空間が作成され、展開作業が完了します。以前はジョブを使用していましたが、PSSession をジョブに渡すことができないため、実行空間に移動する必要がありました。
スクリプトが機能するようになりましたが、出力は望ましくありません。Receive-Job を呼び出して、スレッドごとにグループ化されたすべての出力を取得できるため、ジョブ バージョンの出力は適切でした。また、write-host を使用して出力をログに記録することもできます。これにより、色付きの応答が可能になります (つまり、更新の適用に失敗した場合は赤いテキスト)。ジョブの実行空間バージョンを取得して、出力をスレッドごとにグループ化することができましたが、書き込み出力を使用する必要がありました。write-host を使用するとすぐに出力が発生し、許容できない散在する出力が発生します。残念ながら、書き込み出力はカラー出力を許可していません。$host.UI.RawUI.ForegroundColor の設定も機能しません。これは、色が設定された時点で出力が発生しない場合、効果が発生しないためです。出力は最後まで行われないため、$host 設定は無効になります。
以下は、私の悩みを説明するための簡単なデモ スクリプトです。
#Runspacing output example. Fix interleaving
cls
#region Setup throttle, pool, iterations
$iterations = 5
$throttleLimit = 2
write-host ('Throttled to ' + $throttleLimit + ' concurrent threads')
$iss = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
$Pool = [runspacefactory]::CreateRunspacePool(1,$throttleLimit,$iss,$Host)
$Pool.Open()
#endregion
#This works because the console color is set at the time output occurs
$OrigfC = $host.UI.RawUI.ForegroundColor
$host.UI.RawUI.ForegroundColor = 'red'
write-host 'THIS IS RED TEXT!'
start-sleep 2
$host.UI.RawUI.ForegroundColor = $OrigfC
#define code to run off the main thread
$scriptBlock = {
$nl = ([Environment]::NewLine.Chars(0)+[Environment]::NewLine.Chars(1))
#This does not work because output won't occur until after color has been reset
$OrigfC = $host.UI.RawUI.ForegroundColor
$host.UI.RawUI.ForegroundColor = 'yellow'
write-output ($nl + ' TEST: ' + $args[0])
Write-Output (' Some write-output: ' + $args[0])
Start-Sleep 1
write-host (' Some write-host: ' + $args[0]) -ForegroundColor Cyan # notice write-host occurs immediately
Start-Sleep 1
$host.UI.RawUI.ForegroundColor = $OrigfC
}
#Start new runspaces
$threads = @()
$handles = @(for($x = 1; $x -le $iterations; $x++)
{
$powerShell = [PowerShell]::Create().AddScript($scriptBlock).AddParameters(@($x))
$powershell.RunspacePool = $Pool
$powerShell.BeginInvoke()
$threads += $powerShell
})
#Wait for threads to complete
$completedCount = 0
$completed = ($handles | where-object {$_.IsCompleted -eq $true}).count
while($handles.IsCompleted.Contains($false))
{
if($completedCount -ne ($handles | where-object {$_.IsCompleted -eq $true}).count)
{
$completedCount = ($handles | where-object {$_.IsCompleted -eq $true}).count
write-host ('Threads Completed: ' + $completedCount + ' of ' + $iterations)
}
write-host '.' -nonewline
Start-Sleep 1
}
write-host ('Threads Completed: ' + ($handles | where-object {$_.IsCompleted -eq $true}).count + ' of ' + $iterations)
#output from threads
for($z = 0; $z -lt $handles.Count; $z++)
{
$threads[$z].EndInvoke($handles[$z]) #causes output
$threads[$z].Dispose()
$handles[$z] = $null
}
$Pool.Dispose()
出力が灰色で指定されていない限り、出力は以下のとおりです。
私の目標は、「Some write-output: X」という行を 1 つの色に設定し、「TEST: X」を別の色に設定することです。 . アイデア?私はpowershellプロンプトから実行していることに注意してください。ISE で実行すると、異なる結果が得られます。
Throttled to 2 concurrent threads
THIS IS RED TEXT! #Outputs in Red
. Some write-host: 1 #Outputs in Cyan
Some write-host: 2 #Outputs in Cyan
.Threads Completed: 2 of 5 #Outputs in Yellow
. Some write-host: 3 #Outputs in Cyan
Some write-host: 4 #Outputs in Cyan
.Threads Completed: 4 of 5 #Outputs in Yellow
. Some write-host: 5 #Outputs in Cyan
.Threads Completed: 5 of 5
TEST: 1
Some write-output: 1
TEST: 2
Some write-output: 2
TEST: 3
Some write-output: 3
TEST: 4
Some write-output: 4
TEST: 5
Some write-output: 5
編集: Mjolinor's Answer に対処する別の例を追加します。M さん、その通りです。セッションをジョブに渡すことができます。上記の例を単純化しすぎました。関数をジョブに送信する以下の例を検討してください。この行 ( if(1 -ne 1){MyFunc -ses $args[0]} ) を以下でコメントアウトすると、実行されます。行がコメント化されていない場合、セッション (System.Management.Automation.Runspaces.PSSession 型) は Deserialized.System.Management.Automation.Runspaces.PSSession 型に変換されますが、MyFunc 呼び出しにヒットできません。私はこれを理解することができなかったので、実行空間に向かって動き始めました。ジョブ指向のソリューションがあると思いますか?
cls
$ses = New-PSSession -ComputerName XXXX
Write-Host ('Outside job: '+$ses.GetType())
$func = {
function MyFunc {
param([parameter(Mandatory=$true)][PSSession]$ses)
Write-Host ('Inside fcn: '+$ses.GetType())
}
}
$scriptBlock = {
Write-Host ('Inside job: '+$args[0].GetType())
if(1 -ne 1){MyFunc -ses $args[0]}
}
Start-Job -InitializationScript $func -ScriptBlock $scriptBlock -Args @($ses) | Out-Null
While (Get-Job -State "Running") { }
Get-Job | Receive-Job
Remove-Job *
Remove-PSSession -Session $ses