是否可以从远程 Invoke-Command 调用发送 'out of band data'?
Is it possible to send 'out of band data' from a remote Invoke-Command call?
我有一个系统,在这个系统中我一次远程连接到一台机器和 运行 命令、脚本等。能够有效地 return 记录消息会很有用来自 "realtime" 中的远程脚本。一些代码来了解我正在尝试做什么。
请注意,本地 Log-*Msg 函数都记录到数据库(并视情况符合标准 out/err)。另请注意,我们在远程端(从模块加载)有类似的 Log-*Msg 方法,旨在通过线路向后倾斜并记录在数据库中,就像调用本地 Log-*Msg 函数一样。
局部方法
function Exec-Remote {
param(
[ValidateNotNull()]
[System.Management.Automation.Runspaces.PSSession]
$Session=$(throw "Session is mandatory ($($MyInvocation.MyCommand))"),
$argumentList,
$scriptBlock
)
if($argumentList -is [scriptblock]) {$scriptBlock = $argumentList}
if($scriptBlock -eq $null) { throw 'Scriptblock is required'}
Invoke-Command -Session $Session -ArgumentList $argumentList -scriptBlock $scriptBlock | Filter-RemoteLogs
}
Filter Filter-RemoteLogs {
if($_ -isnot [string]) { return $_ }
if($_.StartsWith('Log-VerboseMsg:')) {
Log-VerboseMsg $_.Replace("Log-VerboseMsg:", "") | Out-Null
return
}
if($_.StartsWith('Log-WarningMsg:')) {
Log-WarningMsg $_.Replace("Log-WarningMsg:", "") | Out-Null
return
}
if($_.StartsWith('Log-UserMsg:')) {
Log-UserMsg $_.Replace("Log-UserMsg:", "") | Out-Null
return
}
else { return $_ }
}
远程方法示例
在远程端,我有一个加载了一些日志记录功能的模块,这是一个这样的功能:
function Log-VerboseMsg {
param([ValidateNotNullOrEmpty()] $msg)
"Log-VerboseMsg:$msg"
}
在大多数情况下,我可以执行以下操作
$val = Exec-Remote -Session $PSSession {
Log-VerboseMsg 'A test log message!'
return $true
}
让它透明地做正确的事。
但是,在以下情况下它会失败。
$val = Exec-Remote -Session $PSSession {
function Test-Logging {
Log-VerboseMsg 'A test log message!'
return $true
}
$aVariable = Test-Logging
Do-ALongRunningOperation
return $aVariable
}
在 'long running operation' 完成之前,以上不会 return 任何内容。
我的问题如下。
有没有办法让我在 Powershell 中可靠地执行此操作?在某种形式上,如果我使用的方法真的那么糟糕,请随意抨击我并解释原因。
注意:从远程环境连接到数据库并记录日志消息并不总是可行的,因此虽然这种方法可行,但对于我的特定需求来说它是不够的。
在 PowerShell v5 中,您可以为此使用新的信息流。您应该按如下方式修改局部函数:
function Exec-Remote {
param(
[ValidateNotNull()]
[System.Management.Automation.Runspaces.PSSession]
$Session=$(throw "Session is mandatory ($($MyInvocation.MyCommand))"),
$argumentList,
$scriptBlock
)
if($argumentList -is [scriptblock]) {$scriptBlock = $argumentList}
if($scriptBlock -eq $null) { throw 'Scriptblock is required'}
# 6>&1 will redirect information stream to output, so Filter-RemoteLogs can process it.
Invoke-Command -Session $Session -ArgumentList $argumentList -scriptBlock $scriptBlock 6>&1 | Filter-RemoteLogs
}
Filter Filter-RemoteLogs {
# Function should be advanced, so we can call $PSCmdlet.WriteInformation.
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
[PSObject]$InputObject
)
if(
# If it is InformationRecord.
($InputObject -is [Management.Automation.InformationRecord]) -and
# And if it come from informational steam.
($WriteInformationStream=$InputObject.PSObject.Properties['WriteInformationStream']) -and
($WriteInformationStream.Value)
) {
# If it is our InformationRecord.
if($InputObject.Tags-contains'MyLoggingInfomation') {
# Write it to log.
&"Log-$($InputObject.MessageData.LogType)Msg" $InputObject.MessageData.Message | Out-Null
} else {
# Return not our InformationRecord to informational stream.
$PSCmdlet.WriteInformation($InputObject)
}
} else {
# Return other objects to output stream.
$PSCmdlet.WriteObject($InputObject)
}
}
并且远程日志记录函数应该写入信息流:
function Log-VerboseMsg {
param([ValidateNotNullOrEmpty()] $msg)
Write-Information ([PSCustomObject]@{Message=$msg;LogType='Verbose'}) MyLoggingInfomation
}
function Log-WarningMsg {
param([ValidateNotNullOrEmpty()] $msg)
Write-Information ([PSCustomObject]@{Message=$msg;LogType='Warning'}) MyLoggingInfomation
}
function Log-UserMsg {
param([ValidateNotNullOrEmpty()] $msg)
Write-Information ([PSCustomObject]@{Message=$msg;LogType='User'}) MyLoggingInfomation
}
我有一个系统,在这个系统中我一次远程连接到一台机器和 运行 命令、脚本等。能够有效地 return 记录消息会很有用来自 "realtime" 中的远程脚本。一些代码来了解我正在尝试做什么。
请注意,本地 Log-*Msg 函数都记录到数据库(并视情况符合标准 out/err)。另请注意,我们在远程端(从模块加载)有类似的 Log-*Msg 方法,旨在通过线路向后倾斜并记录在数据库中,就像调用本地 Log-*Msg 函数一样。
局部方法
function Exec-Remote {
param(
[ValidateNotNull()]
[System.Management.Automation.Runspaces.PSSession]
$Session=$(throw "Session is mandatory ($($MyInvocation.MyCommand))"),
$argumentList,
$scriptBlock
)
if($argumentList -is [scriptblock]) {$scriptBlock = $argumentList}
if($scriptBlock -eq $null) { throw 'Scriptblock is required'}
Invoke-Command -Session $Session -ArgumentList $argumentList -scriptBlock $scriptBlock | Filter-RemoteLogs
}
Filter Filter-RemoteLogs {
if($_ -isnot [string]) { return $_ }
if($_.StartsWith('Log-VerboseMsg:')) {
Log-VerboseMsg $_.Replace("Log-VerboseMsg:", "") | Out-Null
return
}
if($_.StartsWith('Log-WarningMsg:')) {
Log-WarningMsg $_.Replace("Log-WarningMsg:", "") | Out-Null
return
}
if($_.StartsWith('Log-UserMsg:')) {
Log-UserMsg $_.Replace("Log-UserMsg:", "") | Out-Null
return
}
else { return $_ }
}
远程方法示例
在远程端,我有一个加载了一些日志记录功能的模块,这是一个这样的功能:
function Log-VerboseMsg {
param([ValidateNotNullOrEmpty()] $msg)
"Log-VerboseMsg:$msg"
}
在大多数情况下,我可以执行以下操作
$val = Exec-Remote -Session $PSSession {
Log-VerboseMsg 'A test log message!'
return $true
}
让它透明地做正确的事。
但是,在以下情况下它会失败。
$val = Exec-Remote -Session $PSSession {
function Test-Logging {
Log-VerboseMsg 'A test log message!'
return $true
}
$aVariable = Test-Logging
Do-ALongRunningOperation
return $aVariable
}
在 'long running operation' 完成之前,以上不会 return 任何内容。
我的问题如下。
有没有办法让我在 Powershell 中可靠地执行此操作?在某种形式上,如果我使用的方法真的那么糟糕,请随意抨击我并解释原因。
注意:从远程环境连接到数据库并记录日志消息并不总是可行的,因此虽然这种方法可行,但对于我的特定需求来说它是不够的。
在 PowerShell v5 中,您可以为此使用新的信息流。您应该按如下方式修改局部函数:
function Exec-Remote {
param(
[ValidateNotNull()]
[System.Management.Automation.Runspaces.PSSession]
$Session=$(throw "Session is mandatory ($($MyInvocation.MyCommand))"),
$argumentList,
$scriptBlock
)
if($argumentList -is [scriptblock]) {$scriptBlock = $argumentList}
if($scriptBlock -eq $null) { throw 'Scriptblock is required'}
# 6>&1 will redirect information stream to output, so Filter-RemoteLogs can process it.
Invoke-Command -Session $Session -ArgumentList $argumentList -scriptBlock $scriptBlock 6>&1 | Filter-RemoteLogs
}
Filter Filter-RemoteLogs {
# Function should be advanced, so we can call $PSCmdlet.WriteInformation.
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
[PSObject]$InputObject
)
if(
# If it is InformationRecord.
($InputObject -is [Management.Automation.InformationRecord]) -and
# And if it come from informational steam.
($WriteInformationStream=$InputObject.PSObject.Properties['WriteInformationStream']) -and
($WriteInformationStream.Value)
) {
# If it is our InformationRecord.
if($InputObject.Tags-contains'MyLoggingInfomation') {
# Write it to log.
&"Log-$($InputObject.MessageData.LogType)Msg" $InputObject.MessageData.Message | Out-Null
} else {
# Return not our InformationRecord to informational stream.
$PSCmdlet.WriteInformation($InputObject)
}
} else {
# Return other objects to output stream.
$PSCmdlet.WriteObject($InputObject)
}
}
并且远程日志记录函数应该写入信息流:
function Log-VerboseMsg {
param([ValidateNotNullOrEmpty()] $msg)
Write-Information ([PSCustomObject]@{Message=$msg;LogType='Verbose'}) MyLoggingInfomation
}
function Log-WarningMsg {
param([ValidateNotNullOrEmpty()] $msg)
Write-Information ([PSCustomObject]@{Message=$msg;LogType='Warning'}) MyLoggingInfomation
}
function Log-UserMsg {
param([ValidateNotNullOrEmpty()] $msg)
Write-Information ([PSCustomObject]@{Message=$msg;LogType='User'}) MyLoggingInfomation
}