是否可以从远程 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
}