【问题标题】:Capturing output of PsExec using PowerShell changes the output使用 PowerShell 捕获 PsExec 的输出会更改输出
【发布时间】:2019-06-29 00:22:00
【问题描述】:

我想要实现的是从运行psexec -nobanner \\1.2.3.4 net localgroup Administrators 重定向标准输出和标准错误。当我重定向标准输出时,命令的结果会发生变化。以我尝试过的任何方式捕获标准输出似乎都会改变结果。我想知道为什么,我想让它工作。

在 PowerShell 中,如果我运行这个:

psexec -nobanner \\1.2.3.4 net localgroup Administrators

我看到了:

Couldn't access 1.2.3.4:
The trust relationship between this workstation and the primary domain failed.

Couldn't access 1.2.3.4: 结束的地方,我短暂地看到了 Connecting to 1.2.3.4... 并且其他东西闪过太快而看不到。)

如果我尝试捕获输出,请使用:

$output = psexec.exe -nobanner \\1.2.3.4 net localgroup Administrators

我明白了:

Couldn't access 1.2.3.4:
The handle is invalid.

(如上,Couldn't access 1.2.3.4: 结束的地方,我简要地看到了Connecting to 1.2.3.4...。)

我意识到我需要重定向错误流 - 这就是我开始的地方。但是我什至不能得到标准输出而不改变它。这个问题是关于当我尝试捕获它时输出会发生变化的原因。

更新

我刚刚注意到,如果我运行相同的命令(在上面的 PowerShell 主机中运行)

psexec -nobanner \\1.2.3.4 net localgroup Administrators

PowerShell ISE 中,我收到与以下相同的错误:

psexec : The handle is invalid.
At line:1 char:1
+ psexec -nobanner \\1.2.3.4 net localgroup Administrators
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (The handle is invalid.:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

Connecting to 1.2.3.4...Couldn't access 1.2.3.4:
Connecting to 1.2.3.4...

那么,为什么在 ISE 中运行它会得到与普通主机不同的输出?


我尝试过的其他事情:

1.启动进程

Start-Process -Wait -PSPath 'C:\Windows\PSTools\psexec.exe' -NoNewWindow `
    -ArgumentList "-nobanner \\$ip net localgroup Administrators" `
    -RedirectStandardError '.\tempError.log' -RedirectStandardOutput '.\tempOutput.log'
'O:'
Get-Content .\tempOutput.log
'E:'
Get-Content .\tempError.log

给出:

O:
E:
The handle is invalid.
Connecting to 1.2.3.4...


Couldn't access 1.2.3.4:
Connecting to 1.2.3.4...

2。仅重定向标准输出

psexec -nobanner \\1.2.3.4 net localgroup Administrators > psexec.log

给出:

Couldn't access 1.2.3.4:
The handle is invalid.

[psexec.log 为空,因为我只重定向标准输出,而 PsExec 将自己的消息写入标准错误。]

3.仅重定向标准错误

我注意到其他一些奇怪的地方:如果我只重定向标准错误,它可以工作(PsExec 工作,命令失败,输出被重定向):

psexec -nobanner \\1.2.3.4 net localgroup Administrators 2> psexec.log

文件psexec.log 包含:

psexec : The trust relationship between this workstation and the primary domain failed.
At line:1 char:1
+ psexec -nobanner \\1.2.3.4 net localgroup Administrators 2> ps ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (The trust relat... domain failed.:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

Connecting to 1.2.3.4...                                                                                        
                               Couldn't access 1.2.3.4:
Connecting to 1.2.3.4...                                                                                        

4.全部重定向

psexec.exe -nobanner \\1.2.3.4 net localgroup Administrators *>&1 | Set-Variable -Name Output

这给出了这个:

psexec : The handle is invalid.
At line:1 char:1
+ psexec -nobanner \\1.2.3.4 net localgroup Administrators *>&1  ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (The handle is invalid.:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

Connecting to 1.2.3.4...Couldn't access 1.2.3.4:
Connecting to 1.2.3.4...

我用cmd重复了上面的一些:

5.仅重定向标准输出,使用 cmd

cmd /c C:\Windows\PsTools\psexec -nobanner \\1.2.3.4 net localgroup Administrators --% 1> psexec.log

给予:

Couldn't access 1.2.3.4:
The handle is invalid.

(直接到控制台)。 6。仅重定向标准错误,使用 cmd

cmd /c C:\Windows\PsTools\psexec -nobanner \\1.2.3.4 net localgroup Administrators --% 2> psexec.log

给(psexec.log):

The trust relationship between this workstation and the primary domain failed.
Connecting to 1.2.3.4...


Couldn't access 1.2.3.4:
Connecting to 1.2.3.4...

【问题讨论】:

  • 如果你尝试psexec.exe -nobanner \\1.2.3.4 net localgroup Administrators *>&1| Set-Variable -Name Output会发生什么?
  • psexec :句柄无效。在 line:1 char:1 + psexec -nobanner \\1.2.3.4 net localgroup Administrators *>&1 ... + ~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (句柄无效。:String) [], RemoteException + FullyQualifiedErrorId : NativeCommandError Connecting to 1.2.3.4...Couldn't access 1.2.3.4: Connecting to 1.2.3.4...
  • 那是不可读的。请edit您的问题。

标签: powershell psexec


【解决方案1】:

psexec.exe 是一个简单的可执行文件,它将输出写入 stdout(标准输出)和 stderr(标准错误)。因此,要捕获输出,请使用:

  • psexec.exe > stdout.txt 捕获发送到标准输出。
  • psexec.exe 2> sterr.txt 捕获发送到 stderr 的输出。
  • psexec.exe > combined.txt 2>&1 在单个文件中捕获 stdout 和 stderr。

有趣的是,psexec 将默认消息写入标准错误 - 通常这将是标准输出。所以你在运行 psexec 时在 shell 中看到的实际上是错误输出,需要使用 2> 捕获。

【讨论】:

    【解决方案2】:

    这会将 myScript.ps1 的所有输出返回到 $result 变量,而不会出现 PsExec 周围的所有其他垃圾。这是假设您可以将 ps1 文件复制到目标机器。

    $result = & C:\tools\SysinternalsSuite\PsExec.exe \\$PC -nobanner C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -executionpolicy bypass -file c:\myScript.ps1 2> $null
    

    在线PS命令版本

    $result = & C:\tools\SysinternalsSuite\PsExec.exe \\$PC -nobanner C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -executionpolicy bypass -command "get-process" 2> $null
    

    命令行版本

    $result = & C:\tools\SysinternalsSuite\PsExec.exe \\$PC -nobanner C:\Windows\System32\cmd.exe /c "ipconfig" 2> $null
    

    【讨论】:

      【解决方案3】:

      这是一个使用 psexec 和运行空间从远程系统运行和收集报告数据的完整脚本。它比 start-job 快得多,而且使用的内存要少得多。

      ################################################################################################
      # PSEXEC_Command_Runspaces
      # Uses PSEXEC to run a command on multiple computers.  Useful when PS remoting is not enabled
      # but you have admin rights.  
      #
      # Requires RSAT tools for the get-adcomputer command.  You could import a csv or other method
      # to obtain a list of computers instead.  
      ################################################################################################
      # Parameters
      ################################################################################################
      #The list of computers to process
      $pclist = get-adcomputer -filter "OperatingSystem -eq 'Windows 10 Enterprise' -and Name -like 'RC-*'" -properties DNSHostName | select -ExpandProperty DNSHostName
      $Throttle = 500   #number of concurrent runspaces.  The higher this is, the more memory is needed for the runspaces.  500 takes less than 1GB for this script.  
      
      ################################################################################################
      # This is the script that will run in each runspace.  
      ################################################################################################
      $scriptblock = {
          Param (
            $nothing,  #this empty variable seems to be required because if you pass a single variable, it gets corrupted.  
            $PC
          )           
      
        if (test-connection $PC -Count 1 -ea SilentlyContinue) {  
      
          # Create script folders on remote computer and copy report script.  
          md \\$PC\c$\wsapps -ea SilentlyContinue
          md \\$PC\C$\wsapps\QA -ea SilentlyContinue
          copy 'C:\tools\Powershell\Review Center\PullBIOSandLoggedOnUser.ps1' "\\$pc\c$\wsapps\qa" -Force
      
          # Run ps exec and collect output
          # 2> $null gets rid of the "starting service and other junk from the PSexec output
          $result = & C:\tools\SysinternalsSuite\PsExec.exe \\$PC -nobanner C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -executionpolicy bypass -file c:\wsapps\qa\PullBIOSandLoggedOnUser.ps1 2> $null
      
          #remote script file from remote machine.  You could also remove folders if apropriate here. 
          remove-item \\$pc\C$\wsapps\QA\PullBIOSandLoggedOnUser.ps1 -ea SilentlyContinue
      
          #Parse results from single line of output.  PS does not return muliple lines of output from PSEXEC when wrapped in a job or runspace.  
          $parts = $result.split(",")
          $outobj = New-Object psobject
          $outobj | Add-Member ComputerName $PC
          $outobj | Add-Member WindowsVersion ($parts[1].split(":")[1])
          $outobj | Add-Member BiosVersion ($parts[2].split(":")[1])
          $outobj | Add-Member LoggedOnUser ($parts[3].split(":")[1])
          $outobj | Add-Member IPAddress ($parts[4].split(":")[1])
        }
        else {   #report object indicating offline status.
        $outobj = New-Object psobject
          $outobj | Add-Member ComputerName $PC
          $outobj | Add-Member WindowsVersion "Offline"
          $outobj | Add-Member BiosVersion "?"
          $outobj | Add-Member LoggedOnUser "?"
          $outobj | Add-Member IPAddress "?"
        }
        write-output $outobj
      }
      
      
      ################################################################################################
      # Main Logic
      # Runspaces are much, much faster than start-job and use far less memory
      # 260 computers took 4.5GB memory and > 20 minutes to process with start- job
      # 260 computers took 260MB memory and < 1 minute to process with runspaces.
      ################################################################################################
      $RunspacePool = [runspacefactory]::CreateRunspacePool(1,$Throttle)
      $RunspacePool.Open()
      
      #RSArrayList contains a link to each runspace.  Needed to track progress and obtain results later
      $RSArrayList = New-Object System.Collections.ArrayList   
      
      #Loop through each PC in the list, creating runspaces.  The runspace pool is used for multiple parallel spaces with rate control.  
      foreach ($PC in $PClist) {
        $PowerShell = [powershell]::Create()
        [void]$PowerShell.AddScript($scriptblock)
        [void]$powershell.AddArgument("").AddArgument($PC)  #extra argument to avoid single argument corruption bug.  
        $PowerShell.RunspacePool = $RunspacePool
      
        $ThisRS = New-Object psobject
        $ThisRS | Add-Member Computer $PC
        $ThisRS | Add-Member PSInstance $PowerShell
        $thisRS | Add-Member Space ($PowerShell.BeginInvoke())  #execution starts here.
        $RSArrayList += $thisRS
        write-host "Adding $PC"
      }
      
      ################################################################################################
      #Progress bar to track when jobs are finished.
      write-host "waiting for runspaces to finish"
      while (($RSArrayList.space.iscompleted -eq $false).count -gt 0) {
        $Done = $RSArrayList.count - ($RSArrayList.space.iscompleted -eq $false).count
        if ($Done -eq 0) {$percentComplete = 0}
        else {$percentComplete = $Done / $RSArrayList.count * 100}
        write-progress -Activity "Waiting for jobs to complete" -Status (($RSArrayList.count - $Done).ToString() + "Left") -PercentComplete $percentComplete
        sleep -Seconds 1
      }
      
      ################################################################################################
      #collecting results and creating report object
      write-host "Processing Results"
      
      $Report = New-Object System.Collections.ArrayList
      foreach ($RS in $RSArrayList) {
        $Report += $RS.PSInstance.EndInvoke($RS.Space)  #equivilant to "receive-job"
        $RS.PSInstance.Dispose()  # frees up memory.
      }
      
      $Report | ft
      

      这是在远程系统上运行的报告收集脚本

      ################################################################################################
      # Looks up the computer name, Windows Version, BIOS version, logged on user, and IP address of the computer.  
      # Designed to be called by start-job or runspaces (much faster).  
      ################################################################################################
      $computername = $env:COMPUTERNAME
      $WindowsVersion = (Get-WmiObject win32_OperatingSystem).BuildNumber.toString()
      
      $BiosVersion = (Get-WmiObject Win32_BIOS).Name
      $IPAddress = "No 10 range IP"
      $addr = (Get-NetIPAddress -AddressFamily IPv4).ipaddress | where {$_ -like '10.*'}
      if ($addr) {$IPAddress = $addr}
      $LoggedOnUser = "None"
      $quser = (quser.exe 2> $null | select-string "console").line
      if ($quser) {$LoggedOnUser = $quser.Substring(1,$quser.IndexOf(" ",1)-1)}
      
      # For whatever reason, PS will not return multiple lines of output from PSexec when run under start-job or runspaces.  This was the workaround.  
      "Computername:$computername,WindowsVersion:$WindowsVersion,BIOSVersion:$BIOSVersion,LoggedOnUser:$LoggedOnUser,IPAddress:$IPAddress"
      

      【讨论】: