简体   繁体   中英

Capturing output of PsExec using PowerShell changes the output

What I'm trying to achieve is to redirect standard output and standard error from running psexec -nobanner \\\\1.2.3.4 net localgroup Administrators . When I redirect standard output, the result of the command changes. Something about capturing standard output, in any of the ways I've tried, seems to change the result. I'd like to know why and I'd like to get it working.

In PowerShell, if I run this:

psexec -nobanner \\1.2.3.4 net localgroup Administrators

I see this:

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

(Where Couldn't access 1.2.3.4: ends up, I briefly see Connecting to 1.2.3.4... and something else flashes past too quickly to see.)

If I try to capture the output, using this:

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

I see:

Couldn't access 1.2.3.4:
The handle is invalid.

(As above, where Couldn't access 1.2.3.4: ends up, I briefly see Connecting to 1.2.3.4... .)

I realise that I need to redirect the error stream - that's where I started. But I can't even get the standard output without it changing. This question is about whey the output changes as soon as I try to capture it.

UPDATE

I've just noticed that if I run the same command (that works above in the PowerShell host)

psexec -nobanner \\1.2.3.4 net localgroup Administrators

in PowerShell ISE , I get the same error as below:

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...

So, why does running it in ISE give different output from the normal host?


Other things I've tried:

1. Start-Process

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

which gives:

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. Redirect Standard Output only

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

which gives:

Couldn't access 1.2.3.4:
The handle is invalid.

[psexec.log is empty because I'm only redirecting Standard Output and PsExec writes its own messages to Standard Error.]

3. Redirect Standard Error only

I've noticed something else odd about this: if I only redirect Standard Error, it works (PsExec works, the command fails, the output is redirected):

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

The file psexec.log contains:

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. Redirect all

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

which gives this:

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...

I repeated some of the above using cmd :

5. Redirect Standard Output only, with cmd

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

gives:

Couldn't access 1.2.3.4:
The handle is invalid.

(directly to the console). 6. Redirecting Standard Error only, with cmd

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

gives (in 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 is a simple executable which writes output to stdout (standard output) and stderr (standard error). So, to capture the output use:

  • psexec.exe > stdout.txt to capture sent to stdout.
  • psexec.exe 2> sterr.txt to capture output sent to stderr.
  • psexec.exe > combined.txt 2>&1 to capture both stdout and stderr in a single file.

Interestingly, psexec writes the default message to stderr - usually this would be stdout. So what you see in your shell when running psexec is actually the error output and would need to be captured using 2> .

This returns all the output from myScript.ps1 to the $result variable without all the other junk surrounding PsExec. This is assuming you can get the ps1 file copied to the target machine.

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

In line PS command version

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

Command line version

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

This is a full script to run and collect report data from remote systems using psexec and runspaces. It is much, much faster than start-job and uses far less memory.

################################################################################################
# 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

This is the report collection script that is run on the remote system

################################################################################################
# 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"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM