简短的问题描述:
我需要来自我的脚本生成的其他 powershell 运行空间的非散布输出。我需要能够设置输出中各行的颜色。
长问题描述:
我编写了一个脚本来将更新部署到多个远程服务器上的供应商应用程序。最初该脚本旨在一次仅部署一个更新,但我有一个新要求,即我现在以交互方式处理多个更新。所以我重写了我的脚本来使用运行空间,这样我就可以在所有服务器上打开会话,初始化它们,然后对于用户输入的每次更新,都会为每个初始化的会话创建一个运行空间,部署工作就完成了。我以前使用过作业,但必须去运行空间,因为您不能将 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”设置为一种颜色,并将“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 的答案。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