是否可以从远程Invoke-Command调用发送“带外数据”?

时间:2015-10-15 16:45:51

标签: powershell powershell-remoting

我有一个系统,我一次只能连接到一台机器上并运行命令,脚本等。能够以“实时”方式有效地从远程脚本返回日志消息是很有用的。一些代码可以了解我正在尝试做什么。

请注意,本地Log- * Msg函数都会记录到数据库(并根据需要发送到标准输出/错误)。另请注意,我们在远程端(从模块加载)上有类似的Log- * Msg方法,这些方法用于在线路上回调并记录在DB中,就像调用本地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
}

在“长时间运行”完成之前,上述内容不会返回任何内容。

我的问题如下:

我有办法在Powershell中可靠地做到这一点吗?在某种形式下,如果我使用的方法非常糟糕,请随意骂我并解释原因。

注意:从远程环境连接到数据库并记录日志消息并不总是可行的,所以虽然这种方法可行,但对于我的特定需求还不够。

1 个答案:

答案 0 :(得分:0)

在PowerShell v5中,您可以使用新的信息流。您应该修改本地函数,如下所示:

WorkArea

远程日志记录功能应写入信息流:

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)
  }
}