PsExec Throws Error Messages, but works without any problems - windows

So we are using PsExec a lot in our automations to install virtual machines, as we can't use ps remote sessions with our windows 2003 machines. Everything works great and there are no Problems, but PsExec keeps throwing errors, even every command is being carried out without correctly.
For example:
D:\tools\pstools\psexec.exe $guestIP -u $global:default_user -p $global:default_pwd -d -i C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -command "Enable-PSRemoting -Force"
Enables the PsRemoting on the guest, but also throws this error message:
psexec.exe :
Bei D:\Scripts\VMware\VMware_Module5.ps1:489 Zeichen:29
+ D:\tools\pstools\psexec.exe <<<< $guestIP -u $global:default_user -p $global:default_pwd -d -i C:\Windows\System32\WindowsPowerShell\
v1.0\powershell.exe -command "Enable-PSRemoting -Force"
+ CategoryInfo : NotSpecified: (:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
PsExec v1.98 - Execute processes remotely
Copyright (C) 2001-2010 Mark Russinovich
Sysinternals - www.sysinternals.com
Connecting to 172.17.23.95...Starting PsExec service on 172.17.23.95...Connecting with PsExec service on 172.17.23.95...Starting C:\Windows\
System32\WindowsPowerShell\v1.0\powershell.exe on 172.17.23.95...
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe started on 172.17.23.95 with process ID 2600.
These kinds of error messages apear ALWAYS no matter how i use psexec, like with quotes, with vriables/fixed values, other flags, etc. Does anybody has an idea how i could fix this? It is not a real problem, but it makes finding errors a pain in the ass, because the "errors" are everywhere. Disabling the error messages of psexec at all would also help...

This is because PowerShell sometimes reports a NativeCommandError when a process writes to STDERR. PsExec writes the infoline
PsExec v1.98 - Execute processes remotely
Copyright (C) 2001-2010 Mark Russinovich
Sysinternals - www.sysinternals.com
to STDERR which means it can cause this.
For more information, see these questions / answers:
https://stackoverflow.com/a/1416933/478656
https://stackoverflow.com/a/11826589/478656
https://stackoverflow.com/a/10666208/478656

redirect stderr to null worked best for me. see below link
Error when calling 3rd party executable from Powershell when using an IDE
Here's the relevant section from that link:
To avoid this you can redirect stderr to null e.g.:
du 2> $null
Essentially the console host and ISE (as well as remoting) treat the stderr stream differently. On the console host it was important for PowerShell to support applications like edit.com to work along with other applications that write colored output and errors to the screen. If the I/O stream is not redirected on console host, PowerShell gives the native EXE a console handle to write to directly. This bypasses PowerShell so PowerShell can't see that there are errors written so it can't report the error via $error or by writing to PowerShell's stderr stream.
ISE and remoting don't need to support this scenario so they do see the errors on stderr and subsequently write the error and update $error.
.\PsExec.exe \$hostname -u $script:userName -p $script:password /accepteula -h cmd /c $powerShellArgs 2> $null

I have created a psexec wrapper for powershell, which may be helpful to people browsing this question:
function Return-CommandResultsUsingPsexec {
param(
[Parameter(Mandatory=$true)] [string] $command_str,
[Parameter(Mandatory=$true)] [string] $remote_computer,
[Parameter(Mandatory=$true)] [string] $psexec_path,
[switch] $include_blank_lines
)
begin {
$remote_computer_regex_escaped = [regex]::Escape($remote_computer)
# $ps_exec_header = "`r`nPsExec v2.2 - Execute processes remotely`r`nCopyright (C) 2001-2016 Mark Russinovich`r`nSysinternals - www.sysinternals.com`r`n"
$ps_exec_regex_headers_array = #(
'^\s*PsExec v\d+(?:\.\d+)? - Execute processes remotely\s*$',
'^\s*Copyright \(C\) \d{4}(?:-\d{4})? Mark Russinovich\s*$',
'^\s*Sysinternals - www\.sysinternals\.com\s*$'
)
$ps_exec_regex_info_array = #(
('^\s*Connecting to ' + $remote_computer_regex_escaped + '\.{3}\s*$'),
('^\s*Starting PSEXESVC service on ' + $remote_computer_regex_escaped + '\.{3}\s*$'),
('^\s*Connecting with PsExec service on ' + $remote_computer_regex_escaped + '\.{3}\s*$'),
('^\s*Starting .+ on ' + $remote_computer_regex_escaped + '\.{3}\s*$')
)
$bypass_regex_array = $ps_exec_regex_headers_array + $ps_exec_regex_info_array
$exit_code_regex_str = ('^.+ exited on ' + $remote_computer_regex_escaped + ' with error code (\d+)\.\s*$')
$ps_exec_args_str = ('"\\' + $remote_computer + '" ' + $command_str)
}
process {
$return_dict = #{
'std_out' = (New-Object 'system.collections.generic.list[string]');
'std_err' = (New-Object 'system.collections.generic.list[string]');
'exit_code' = $null;
'bypassed_std' = (New-Object 'system.collections.generic.list[string]');
}
$process_info = New-Object System.Diagnostics.ProcessStartInfo
$process_info.RedirectStandardError = $true
$process_info.RedirectStandardOutput = $true
$process_info.UseShellExecute = $false
$process_info.FileName = $psexec_path
$process_info.Arguments = $ps_exec_args_str
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $process_info
$process.Start() | Out-Null
$std_dict = [ordered] #{
'std_out' = New-Object 'system.collections.generic.list[string]';
'std_err' = New-Object 'system.collections.generic.list[string]';
}
# $stdout_str = $process.StandardOutput.ReadToEnd()
while ($true) {
$line = $process.StandardOutput.ReadLine()
if ($line -eq $null) {
break
}
$std_dict['std_out'].Add($line)
}
# $stderr_str = $process.StandardError.ReadToEnd()
while ($true) {
$line = $process.StandardError.ReadLine()
if ($line -eq $null) {
break
}
$std_dict['std_err'].Add($line)
}
$process.WaitForExit()
ForEach ($std_type in $std_dict.Keys) {
ForEach ($line in $std_dict[$std_type]) {
if ((-not $include_blank_lines) -and ($line -match '^\s*$')) {
continue
}
$do_continue = $false
ForEach ($regex_str in $bypass_regex_array) {
if ($line -match $regex_str) {
$return_dict['bypassed_std'].Add($line)
$do_continue = $true
break
}
}
if ($do_continue) {
continue
}
$exit_code_regex_match = [regex]::Match($line, $exit_code_regex_str)
if ($exit_code_regex_match.Success) {
$return_dict['exit_code'] = [int] $exit_code_regex_match.Groups[1].Value
} elseif ($std_type -eq 'std_out') {
$return_dict['std_out'].Add($line)
} elseif ($std_type -eq 'std_err') {
$return_dict['std_err'].Add($line)
} else {
throw 'this conditional should never be true; if so, something was coded incorrectly'
}
}
}
return $return_dict
}
}

Related

How to get the List of Installed software

I am trying to get the availability of the software on pc. My condition is that I need to fetch whether the application is installed or not on my laptop if it is installed is it in working condition?
# Setting Execution policy for the Current User
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
$currentExecutionPolicy = Get-ExecutionPolicy
Write-Output "The Execution Ploicy is set to $currentExecutionPolicy"
$programFilePath = #(
'Google Chrome-C:\Program Files\Google\Chrome\Application\chrome.exe'
'Brackets Text Editor-C:\Program Files (x86)\Brackets\Brackets.exe'
'Microsoft Edge-C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
'Microsoft Excel-C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE'
#'Microsoft Outlook-C:\Program Files\Microsoft Office\root\Office16\OUTLOOK.EXE'
)
$result = foreach($program in $programFilePath) {
$splitString = $program -split ('-')
$program_name = $splitString[0]
$program_path = $splitString[1]
foreach($program in $program_path) {
if (Test-Path -Path $program) {
# Write-Output "Program Path Exists"
$programProcess = Start-Process -FilePath $program -PassThru -ErrorAction SilentlyContinue
timeout 5
try{
$myshell = New-Object -com "Wscript.Shell"
$myshell.sendkeys("{ENTER}")
timeout 1
$myshell = New-Object -com "Wscript.Shell"
$myshell.sendkeys("{ENTER}")
}
catch{
$runningProcess = Get-Process -Name $programProcess.ProcessName
}
if($runningProcess -eq $true) {
[pscustomobject]#{
Application_Name = $program_name
Application_Availability = 'Installed'
Application_Status = 'Working'
}
}
else {
[pscustomobject]#{
Application_Name = $program_name
Application_Availability = 'Installed'
Application_Status = 'Not Working. Error Log is generated as Application_Error_Log text file.'
}
Get-EventLog -LogName Application | Where-Object {$_.InstanceID -eq '1000'} | Tee-Object -FilePath .\Application_Error_Log.txt
}
<# Action to perform if the condition is true #>
} else {
[pscustomobject]#{
Application_Name = $program_name
Application_Availability = 'Not Installed'
Application_Status = $null
}
}
}
}
" :: System Software Audit Report :: " | Out-File .\System_Software_Details.txt
$result | Tee-Object -FilePath ".\System_Software_Details.txt" -Append
timeout 60
Although I am getting the application active which are working and functional but in my output in Text file application status shows : Application_Status = 'Not Working. Error Log is generated as although my application is working fine
My second concern is I am unable to handle the application which is giving me an error
$myshell = New-Object -com "Wscript.Shell"
$myshell.sendkeys("{ENTER}")
timeout 1
$myshell = New-Object -com "Wscript.Shell"
$myshell.sendkeys("{ENTER}")
I think checking filesystem paths is an option but a bad one - you cannot ensure in any case that the expected path is used. Checking the filesystem is only necessary for portable applications.
A better approach is to check the following registry keys, by doing so you get the same result as it is displayed under add/remove programs:
HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall
Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" -Name DisplayName,DisplayVersion,InstallSource,Publisher,UninstallString
Another way is to query wmi/cim:
Get-CimInstance -query "select * from win32_product"
But by doing so you generate foreach discovered software product an event in the windows event log (Log: Application, Source: MSIInstaller).
To verify if you can start successfully an application by using start-process you need also to specify the parameter -wait and then check if the return code is 0.
$runningProcess -eq $true doesn't necessarily work as $runningProcess is not a boolean but an object. Alas it always returns false.
TL;DR
If you look at your code you see that to get to "...Not Working..." you have to evaluate ($runningProcess -eq $true). Ergo it returns false.
There's always get-package.
$list = '*chrome*','*firefox*','*notepad++*'
$list | % { get-package $_ }
Name Version Source ProviderName
---- ------- ------ ------------
Google Chrome 104.0.5112.102 msi
Mozilla Firefox (x64 en-US) 104.0.1 Programs
Notepad++ (64-bit x64) 7.8.9 msi
Faster as an argument list and no wildcards:
get-package 'google chrome','mozilla firefox (x64 en-us)',
'notepad++ (64-bit x64)'
Or with the threadjob module:
$list = '*chrome*','*firefox*','*notepad++*'
$list | % { start-threadjob { get-package $using:_ } } |
receive-job -wait -auto

Powershell send commans to some putty windows

I am looking for powershell code to send text to some putty windows (same as what putty command sender does).
I found a similar example below which send text to some notepad windows - it works.
However, I made 1 word change from notepad to putty, it does not work for putty.
How can I update the code to make it work for putty? Thank you.
Below is code for notepad (the line for putty is commented):
#requires -Version 2
function Out-Notepad
{
param
(
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[String]
[AllowEmptyString()]
$Text
)
begin
{
$sb = New-Object System.Text.StringBuilder
}
process
{
$null = $sb.AppendLine($Text)
}
end
{
$text = $sb.ToString()
$processes = Get-Process notepad
# $processes = Get-Process putty
$sig = '
[DllImport("user32.dll", EntryPoint = "FindWindowEx")]public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("User32.dll")]public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
'
foreach ($process in $processes) {
$null = $process.WaitForInputIdle()
$type = Add-Type -MemberDefinition $sig -Name APISendMessage -PassThru
Write-Output type: $type
$hwnd = $process.MainWindowHandle
Write-Output hwnd: $hwnd
[IntPtr]$child = $type::FindWindowEx($hwnd, [IntPtr]::Zero, "Edit", $null)
Write-Output child: $child
$null = $type::SendMessage($child, 0x000C, 0, $text)
Write-Output "---------------------"
}
}
}
Continuing from my comment.
There are many examples of automating putty command via command-line plink via PowerShell. Plink is just a putty command-line executable, and thus you'd run it via PowerShell like any other executable. As documented here:
• PowerShell: Running Executables
https://social.technet.microsoft.com/wiki/contents/articles/7703.powershell-running-executables.aspx
A quick web search using your favorite search engine will list them for you.
PowerShell using Plink
Example hit(s)
Powershell + Plink.exe (Putty) – Execute a command on a remote Linux Device
$plinkPath = "c:\Users\public\Downloads\plink.exe"
$OSun = "root" # User name required by PLINK (Putty) to SSH to Linux Primary Server
$OSpw = "rOoTpAsSwOrD" # Password required by PLINK (Putty) to SSH to Linux Primary Server
$LinuxServer = "myLinuxServer" # Server name, must resolve on your desktop machine
$remoteCommand = """ls"""
$localCommand = "$plinkPath $LinuxServer -l $OSun -pw $OSpw $remoteCommand"
$result = Invoke-Expression($localCommand)
Calling Putty via Powershell using Plink - how to auto answer the security key question
Write-Output "yes" |
PLINK.EXE -ssh $remoteserver -P 22 -pw $password -m $command
Powershell and Plink
$Params = #(
"-l $SwitchLogon"
"-pw $SwitchPsswd"
"-m . $SwitchCliCommand"
"$_"
)
& C:\Scripts\Brocadeswitch\plink.exe $Params
Even the script you say you found is overkill for your use case. Again SendKeys has its issues, but you could do this. The delay is a requirement to allow the app to load. This is the issue. Different machines require different times.
Add-Type -AssemblyName System.Windows.Forms
$SendKeys = [System.Windows.Forms.SendKeys]
# Sending ot notepad
Start-Process -FilePath 'notepad.exe'
Start-Sleep -Seconds 3
$SendKeys::SendWait("~{TAB}$env:USERNAME~")
# Sending ot wordpad
Start-Process -FilePath 'C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Accessories\Wordpad.lnk'
Start-Sleep -Seconds 3
$SendKeys::SendWait("~{TAB}$env:USERNAME~")
# Sending powershell instance
Start-Process -FilePath 'powershell.exe' '-NoProfile', '-NoLogo'
Start-Sleep -Seconds 3
$SendKeys::SendWait("~{TAB}Get-Date~")
You could do this as a delay tactic as well.
# Using Do/Unitl
$MainWindowTitle = $null
Start-Process -FilePath 'notepad.exe'
do
{
$MainWindowTitle = (Get-Process -Name notepad).MainWindowTitle
Start-Sleep -Milliseconds 250
}
until ($MainWindowTitle -ne '')
$SendKeys::SendWait("~{TAB}$env:USERNAME~")
So, I hope you are getting that use Powershell for UI automation is not really a thing to do, since, though it can, it is not its strong suit. So, stick with real automation, or use purpose-built GUI automation tools like AutoIT, `Selenium, etc.

Powershell expression that throws an error as as job, but executes otherwise

I am new to Powershell scripting. I am trying to run a script to convert an Excel spreadsheet into a PDF file. This is the script I am using:
$excelInputPath = <Path1>;
$pdfOutputPath = <Path2>;
$xlFixedFormat = "Microsoft.Office.Interop.Excel.xlFixedFormatType" -As [type];
$objExcel = New-Object -ComObject excel.application;
$objExcel.visible = $False;
$workbook = $objExcel.workbooks.open($excelInputPath, 3);
$workbook.ExportAsFixedFormat($xlFixedFormat::xlTypePDF, $pdfOutputPath);
$objExcel.Workbooks.close();
$objExcel.Quit()
On its own, it executes perfectly. I now wish to add a timeout and so I am trying to run it as a job. However, this job throws an execution error which I catch via the Recieve-Job command:
$excelInputPath = <Path1>;
$pdfOutputPath = <Path2>;
$xlFixedFormat = "Microsoft.Office.Interop.Excel.xlFixedFormatType" -As [type];
$objExcel = New-Object -ComObject excel.application;
$objExcel.visible = $False;
$workbook = $objExcel.workbooks.open($excelInputPath, 3);
$job = Start-Job -ScriptBlock {$workbook.ExportAsFixedFormat($xlFixedFormat::xlTypePDF, $pdfOutputPath);}
$job | Wait-Job -Timeout 10;
If ($job.State -eq 'Running')
{$job.StopJob();
$objExcel.Workbooks.close();
$objExcel.Quit()
throw "Error encountered: Operation timed out"}
else
{if($job.Childjobs[0].Error)
{$objExcel.Workbooks.close();
$objExcel.Quit()
$job | Receive-Job}
else
{$job | Receive-Job;
$objExcel.Workbooks.close();
$objExcel.Quit()}
}
The output I receive is quoted below. The message is in German and roughly translates to: "It is not possible to execute a method for an expression that has a NULL".
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
57 Job57 BackgroundJob Completed True localhost $workbook.ExportAsFixe...
Es ist nicht möglich, eine Methode für einen Ausdruck aufzurufen, der den NULL hat.
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
+ PSComputerName : localhost
Another confusing thing here is that the Job -State is reflected as complete even when it doesn't perform the conversion and throws this error message.
Thank you! And apart from an explanation on why this is happening, I would appreciate any input on how I can perform this task better.
The scriptblock has no knowledge of the variables $workbook, $xlFixedFormat::xlTypePDF and $pdfOutputPath.
You need to send these as parameters using the -ArgumentList parameter.
Try:
$scriptBlock = {
param($workbook, $format, $outPath)
$workbook.ExportAsFixedFormat($format, $outPath)
}
$job = Start-Job -ScriptBlock $scriptBlock -ArgumentList $workbook, $xlFixedFormat::xlTypePDF, $pdfOutputPath

Different CMDs Different behaviour

Update2:
Now, when I know, that x32 is the problem I debugged into the script using powershell_ise_x32 and found out, that $Word.Documents is null.
So Powershell-API for Word has a different behaviour in x32 PowerShell, then in 64bit.
Update:
The error occurs, when using PowerShell x32 and occurs NOT on PowerShell 64bit. That was really it. Powershell x32 was executed because I started it from the Total Commander 32bit.
The question is now - why 32bit and 64bit PowerShell have different behaviour?
Initial Question:
I wrote a powershell script, to convert my WordDocuments and merge them to one.
I wrote a Batch script, to start this powershell script.
When I execute the script directly in "Powershell ISE" the script works fine.
When I execute the batch script as Administrator via context menu, the script reports errors. In this case the C:\WINDOWS\SysWOW64\cmd.exe is executed.
When I execute another cmd.exe found on my system as Administrator - everything works fine:
"C:\Windows\WinSxS\amd64_microsoft-windows-commandprompt_31bf3856ad364e35_10.0.15063.0_none_9c209ff6532b42d7\cmd.exe"
Why do I have different behaviour in different cmd.exe? What are those different cmd.exe?
Batch Script:
cd /d "%~dp0"
powershell.exe -noprofile -executionpolicy bypass -file "%~dp0%DocxToPdf.ps1"
pause
Powershell Script
$FilePath = $PSScriptRoot
$Pdfsam = "D:\Programme\PDFsam\bin\run-console.bat"
$Files = Get-ChildItem "$FilePath\*.docx"
$Word = New-Object -ComObject Word.Application
if(-not $?){
throw "Failed to open Word"
}
# Convert all docx files to pdf
Foreach ($File in $Files) {
Write-Host "Word Object: " $Word
Write-Host "File Object: " $Word $File
Write-Host "FullName prop:" $File.FullName
# open a Word document, filename from the directory
$Doc = $Word.Documents.Open($File.FullName)
# Swap out DOCX with PDF in the Filename
$Name=($Doc.FullName).Replace("docx","pdf")
# Save this File as a PDF in Word 2010/2013
$Doc.SaveAs([ref] $Name, [ref] 17)
$Doc.Close()
}
# check errors
if(-not $?){
Write-Host("Stop because an error occurred")
pause
exit 0
}
# wait until the conversion is done
Start-Sleep -s 15
# Now concat all pdfs to one single pdf
$Files = Get-ChildItem "$FilePath\*.pdf" | Sort-Object
Write-Host $Files.Count
if ($Files.Count -gt 0) {
$command = ""
Foreach ($File in $Files) {
$command += " -f "
$command += "`"" + $File.FullName + "`""
}
$command += " -o `"$FilePath\Letter of application.pdf`" -overwrite concat"
$command = $Pdfsam + $command
echo $command
$path = Split-Path -Path $Pdfsam -Parent
cd $path
cmd /c $command
}else{
Write-Host "No PDFs found for concatenation"
}
Write-Host -NoNewLine "Press any key to continue...";
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown");
I've found $PSScriptRoot to be unreliable.
$FilePath = $PSScriptRoot;
$CurLocation = Get-Location;
$ScriptLocation = Split-Path $MyInvocation.MyCommand.Path
Write-Host "FilePath = [$FilePath]";
Write-Host "CurLocation = [$CurLocation]";
Write-Host "ScriptLocation = [$ScriptLocation]";
Results:
O:\Data>powershell ..\Script\t.ps1
FilePath = []
CurLocation = [O:\Data]
ScriptLocation = [O:\Script]
As to the differences between the various cmd.exe implementations, I can't really answer that. I should have thought they'd be functionally identical, but maybe there's 32/64-bit differences that matter.
The error occurs, when using PowerShell x32 and occurs NOT on PowerShell 64bit.
I debugged into the script using powershell_ise_x32 and found out, that $Word.Documents is null.
This is because on my system Word 64bit is installed.

Capture program stdout and stderr to separate variables

Is it possible to redirect stdout from an external program to a variable and stderr from external programs to another variable in one run?
For example:
$global:ERRORS = #();
$global:PROGERR = #();
function test() {
# Can we redirect errors to $PROGERR here, leaving stdout for $OUTPUT?
$OUTPUT = (& myprogram.exe 'argv[0]', 'argv[1]');
if ( $OUTPUT | select-string -Pattern "foo" ) {
# do stuff
} else {
$global:ERRORS += "test(): oh noes! 'foo' missing!";
}
}
test;
if ( #($global:ERRORS).length -gt 0 ) {
Write-Host "Script specific error occurred";
foreach ( $err in $global:ERRORS ) {
$host.ui.WriteErrorLine("err: $err");
}
} else {
Write-Host "Script ran fine!";
}
if ( #($global:PROGERR).length -gt 0 ) {
# do stuff
} else {
Write-Host "External program ran fine!";
}
A dull example however I am wondering if that is possible?
One option is to combine the output of stdout and stderr into a single stream, then filter.
Data from stdout will be strings, while stderr produces System.Management.Automation.ErrorRecord objects.
$allOutput = & myprogram.exe 2>&1
$stderr = $allOutput | ?{ $_ -is [System.Management.Automation.ErrorRecord] }
$stdout = $allOutput | ?{ $_ -isnot [System.Management.Automation.ErrorRecord] }
The easiest way to do this is to use a file for the stderr output, e.g.:
$output = & myprogram.exe 'argv[0]', 'argv[1]' 2>stderr.txt
$err = get-content stderr.txt
if ($LastExitCode -ne 0) { ... handle error ... }
I would also use $LastExitCode to check for errors from native console EXE files.
You should be using Start-Process with -RedirectStandardError -RedirectStandardOutput options. This other post has a great example of how to do this (sampled from that post below):
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "ping.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = "localhost"
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$p.WaitForExit()
$stdout = $p.StandardOutput.ReadToEnd()
$stderr = $p.StandardError.ReadToEnd()
Write-Host "stdout: $stdout"
Write-Host "stderr: $stderr"
Write-Host "exit code: " + $p.ExitCode
This is also an alternative that I have used to redirect stdout and stderr of a command line while still showing the output during PowerShell execution:
$command = "myexecutable.exe my command line params"
Invoke-Expression $command -OutVariable output -ErrorVariable errors
Write-Host "STDOUT"
Write-Host $output
Write-Host "STDERR"
Write-Host $errors
It is just another possibility to supplement what was already given.
Keep in mind this may not always work depending upon how the script is invoked. I have had problems with -OutVariable and -ErrorVariable when invoked from a standard command line rather than a PowerShell command line like this:
PowerShell -File ".\FileName.ps1"
An alternative that seems to work under most circumstances is this:
$stdOutAndError = Invoke-Expression "$command 2>&1"
Unfortunately, you will lose output to the command line during execution of the script and would have to Write-Host $stdOutAndError after the command returns to make it "a part of the record" (like a part of a Jenkins batch file run). And unfortunately it doesn't separate stdout and stderr.
In case you want to get any from a PowerShell script and to pass a function name followed by any arguments you can use dot sourcing to call the function name and its parameters.
Then using part of James answer to get the $output or the $errors.
The .ps1 file is called W:\Path With Spaces\Get-Something.ps1 with a function inside named Get-It and a parameter FilePath.
Both the paths are wrapped in quotes to prevent spaces in the paths breaking the command.
$command = '. "C:\Path Spaces\Get-Something.ps1"; Get-It -FilePath "W:\Apps\settings.json"'
Invoke-Expression $command -OutVariable output -ErrorVariable errors | Out-Null
# This will get its output.
$output
# This will output the errors.
$errors
Copied from my answer on how to capture both output and verbose information in different variables.
Using Where-Object(The alias is symbol ?) is an obvious method, but it's a bit too cumbersome. It needs a lot of code.
In this way, it will not only take longer time, but also increase the probability of error.
In fact, there is a more concise method that separate different streams to different variable in PowerShell(it came to me by accident).
# First, declare a method that outputs both streams at the same time.
function thisFunc {
[cmdletbinding()]
param()
Write-Output 'Output'
Write-Verbose 'Verbose'
}
# The separation is done in a single statement.Our goal has been achieved.
$VerboseStream = (thisFunc -Verbose | Tee-Object -Variable 'String' | Out-Null) 4>&1
Then we verify the contents of these two variables
$VerboseStream.getType().FullName
$String.getType().FullName
The following information should appear on the console:
PS> System.Management.Automation.VerboseRecord
System.String
'4>&1' means to redirect the verboseStream to the success stream, which can then be saved to a variable, of course you can change this number to any number between 2 and 5.
Separately, preserving formatting
cls
function GetAnsVal {
param([Parameter(Mandatory=$true, ValueFromPipeline=$true)][System.Object[]][AllowEmptyString()]$Output,
[Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$firstEncNew="UTF-8",
[Parameter(Mandatory=$false, ValueFromPipeline=$true)][System.String]$secondEncNew="CP866"
)
function ConvertTo-Encoding ([string]$From, [string]$To){#"UTF-8" "CP866" "ASCII" "windows-1251"
Begin{
$encFrom = [System.Text.Encoding]::GetEncoding($from)
$encTo = [System.Text.Encoding]::GetEncoding($to)
}
Process{
$Text=($_).ToString()
$bytes = $encTo.GetBytes($Text)
$bytes = [System.Text.Encoding]::Convert($encFrom, $encTo, $bytes)
$encTo.GetString($bytes)
}
}
$all = New-Object System.Collections.Generic.List[System.Object];
$exception = New-Object System.Collections.Generic.List[System.Object];
$stderr = New-Object System.Collections.Generic.List[System.Object];
$stdout = New-Object System.Collections.Generic.List[System.Object]
$i = 0;$Output | % {
if ($_ -ne $null){
if ($_.GetType().FullName -ne 'System.Management.Automation.ErrorRecord'){
if ($_.Exception.message -ne $null){$Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$exception.Add($Temp)}
elseif ($_ -ne $null){$Temp=$_ | ConvertTo-Encoding $firstEncNew $secondEncNew;$all.Add($Temp);$stdout.Add($Temp)}
} else {
#if (MyNonTerminatingError.Exception is AccessDeniedException)
$Temp=$_.Exception.message | ConvertTo-Encoding $firstEncNew $secondEncNew;
$all.Add($Temp);$stderr.Add($Temp)
}
}
$i++
}
[hashtable]$return = #{}
$return.Meta0=$all;$return.Meta1=$exception;$return.Meta2=$stderr;$return.Meta3=$stdout;
return $return
}
Add-Type -AssemblyName System.Windows.Forms;
& C:\Windows\System32\curl.exe 'api.ipify.org/?format=plain' 2>&1 | set-variable Output;
$r = & GetAnsVal $Output
$Meta2=""
foreach ($el in $r.Meta2){
$Meta2+=$el
}
$Meta2=($Meta2 -split "[`r`n]") -join "`n"
$Meta2=($Meta2 -split "[`n]{2,}") -join "`n"
[Console]::Write("stderr:`n");
[Console]::Write($Meta2);
[Console]::Write("`n");
$Meta3=""
foreach ($el in $r.Meta3){
$Meta3+=$el
}
$Meta3=($Meta3 -split "[`r`n]") -join "`n"
$Meta3=($Meta3 -split "[`n]{2,}") -join "`n"
[Console]::Write("stdout:`n");
[Console]::Write($Meta3);
[Console]::Write("`n");

Resources