I am writing a rather lengthy PowerShell script to perform a lot of functions. For the most part, everything is going very well.
However, another function I am running at all levels of the script is a very detailed log. I'll post the command line portion as an example (simplified):
$Batch = $RunMe[1]
$ResultCode = (Invoke-Expression $Batch -ErrorAction Stop)
$ResultCode
My expected result is:
Return Code 0
Then I would log it. Instead I am getting something like:
C:\batchfiles\batchfile.bat argument
Which is the command I am using Invoke-Expression to execute.
Some quick explanation of the variables:
$RunMe is an array that stores the commands (0 is either "Batch" or "Proc" and 1 is the command to run)
$Batch is created in the code as written (this is the first reference to it)
$CurrFileToExec is the current file being processed (the script runs on a folder at a time). I'm using it for a string replace for the command line I got from SQL.
I can't get $ResultCode to pass the correct output of the command. It just passes the command line function ($Batch) again.
As I said, the actual functionality part works great, but I can't get that return code to my logfile.
If I can presume $batch contains external executables, the return code will be in the automatic variable $LASTEXITCODE - invoke-expression's return consists of things written to STDOUT and STDERR by external applications or things written to powershell's output or error streams by script. It will not contain the executable's dos-style return code, if you called one.
The solution was to modify my $Batch variable to add a cmd /c to the beginning. I think there was an issue with just using Invoke-Expression and scoping of the output parameter. I added a line for:
$Batch = "cmd /c " + $Batch
Then ran again, and $ResultCode had the expected output.
Related
I have the following line of code that I use to download and combine dns text records from my website and execute them in powershell. I can run this command just fine from the console but can not figure out how to effectively run it from the run box (win+r)
1..3|%{$p+=Resolve-DnsName "$_.website.com." -Ty TXT -EA 0|% S*s};& ([scriptblock]::Create($p))
Below is a simplified version I use to pull down and execute a single dns text record using a single word instead of iterating through through numbers and combining them.
powershell . (nslookup -q=txt example.website.com)[-1]
The above simplified version works fine from the console but in order for it to work in the run box it has to be modified as seen below:
powershell "powershell . (nslookup -q=txt sub.website.com)[-1]"
I can not seem to find a way to modify that first example in a way that allows me to execute it from the run box. Doing something like the example below errors out. I have tried about 20 variations of the code below with no success
powershell .(1..3|%{$p+=Resolve-DnsName "$_.website.com." -Ty TXT -EA 0|% S*s};& ([scriptblock]::Create($p)))
Try the following:
powershell 1..3|%{$p+=Resolve-DnsName \"$_.website.com.\" -Ty TXT -EA 0|% S*s};& ([scriptblock]::Create($p)))
The crucial change is to escape the " chars. as \", so that PowerShell considers them part of the command to execute.
See this answer for more information.
There's no reason to use . (...) for execution - just use ... directly, as shown.
Note that the above command only works from no-shell environments such as the Windows Run dialog (WinKey-R) - to execute it from cmd.exe, you'd need additional quoting or escaping.
It seems that I am in the powershell with exit codes.
There are many topics about why powershell behaves weird with its exit codes (for syntax errors, etc).
There is furthermore a big issue with exit codes and the Parameter attribute. e.g.
calling a script with the following code
Param(
[Parameter()]$a
)
exit 9999
returns an exit code of 0 in cmd.exe (e.g. echo %ERRORLEVEL%) making it impossible to properly deal with return codes.
The very last workaround for that is using Environment::Exit:
Param(
[Parameter()]$a
)
[Environment]::Exit(9999)
Now the problem: I've written a script that has extensive Param definitions and therefore needs Environment::Exit to work.
This utility returns a value via echo, here is a working script for that
# dummy.ps1
Param(
[Parameter()]$a
)
echo "foo 42"
[Environment]::Exit(10)
Of course I don't need the exit here, but the real-world script requires this to properly exit after the echo indicating exit code 10 in both ps and in cmd
Calling it via powershell -f dummy.ps1 returns 42, everything is fine.
But here is the problem: I want to reuse this script in another script so I can use the echoed value properly.
When I am in the powershell prompt I want to do something like that:
$res = Invoke-Expression "& d:\dummy.ps1"
echo $res
But Environment::Exit closes my prompt after the Invoke-Expression and everything is gone.
(Even if I put this code in a separate ps1 file)
Is there a scenario in which this entire thing works as expected?
Again: I need proper exit codes for ps and cmd so exit 10 or exit(10) fail here. And due to the behaviour with [Parameter()] no other exit code than Environment::Exit does work.
I'm experiencing the same thing - in order to recreate the issue, execute with powershell.exe -version 2.0 -file blah.ps1
It's version 2.0... and parameter. once you drop the [Parameter()] you get exit codes as expected.
Param(
#[Parameter(ParameterSetName="inst")][string]$inst
[string]$inst
)
so, this works - swap out the commented lines, then you always get an exit code of 0... And for someone to post, you should never use EXIT? dude, we all have different requirements..
I guess my work around is to not use parameter and just do it this way - I have to get this to work consistently on various OS versions which means I have a minimum requirement that it must work on 2.0.
Here is a piece of function I'm trying to create to make my testing faster:
function t++ {
param($Source, $Input, $Output)
...
g++ $Source -o test;
.\test.exe
Write-Output "===End-Of-Output==="
}
But the output in my Windows PowerShell ISE comes as:
Hello, World!
===End-Of-Output===
\n
(the last line is blank)
I can't understand why is it outputting that extra newline after the End-Of-Output.
When output is written to the standard output stream in PowerShell, it's picked up by the Host application - and the host application may format and manipulate the output.
In case of powershell.exe that would be System.Console, rendering string output as is, but a PowerShell Host application is not necessarily Console-based.
PowerShell ISE for example, doesn't use System.Console, since the output pane is also your debugger - I assume the PowerShell development team found it easier to implement a new output mechanism rather than extending Console.
I can't seem to call this executable correctly in my psake deploy script.
If I do this:
exec { "$ArchiverOutputDir\NServiceBus.Host.exe /install" }
It simply outputs this (and is clearly not calling the executable - just outputting the value of that expression):
c:\ReloDotNet2_ServiceEndpoints\Archiver\NServiceBus.Host.exe /install
But if I do this:
exec { c:\ReloDotNet2_ServiceEndpoints\Archiver\NServiceBus.Host.exe /install }
I get the expected output from the executable.
How do I correctly call an executable with a variable in the path to the executable in psake? If this is actually a PowerShell issue, please feel free to correct the question to reflect that insight.
I
Classic PowerShell issue. Try this instead:
exec { & "$ArchiverOutputDir\NServiceBus.Host.exe" /install }
PowerShell not only executes commands, it also evaluates expressions e.g.:
C:\PS> 2 + 2
4
C:\PS> "hello world"
hello world
What you have given to PowerShell at the beginning of a pipeline is a string expression which it faithfully evaluates and prints to the console. By using the call operator &, you're telling PowerShell that the following thing is either the name of a command (in a string) to be executed or a scriptblock to be executed. Technically you could also use . "some-command-name-or-path". The only difference is that for PowerShell commands, & creates a new scope to execute the command in and . doesn't. For external exes it makes no difference as far as I can tell which one you use but & is typically used.
Generally one can use $ErrorActionPreference to stop the execution of a PS script on error as in the example below that uses an invalid subversion command. The script exits after the first command.
$ErrorActionPreference='Stop'
svn foo
svn foo
Trying the same thing with the maven command (mvn.bat) executes both commands.
$ErrorActionPreference='Stop'
mvn foo
mvn foo
Initially I suspected that mvn.bat does not set an error code and PS just doesn't see the error. However, $? is set properly as demonstrated by the following, when PS exits after the first error:
mvn foo
if (-not $?) {exit 1}
mvn foo
So here's my question: Given that both svn.exe and mvn.bat set an error code on failure, why does the PS script not stop after the mvn error. I find it a lot more convenient to set "$ErrorActionPreference=Stop" globally rather than doing "if (-not $?) {exit 1}" after each command to terminate on error.
Not all command line programs provide errors in the same way. Some set an exit code. Some don't. Some use the error stream, some don't. I've seen some command line programs actually output everything to error, and always output non-zero return codes.
So there's not a real safe guess one could ever make as to it having run successfully, and therefore it's next to impossible to codify that behavior into PowerShell.
$errorActionPreference will actually stop a script whenever a .exe writes to the error stream, but many .exes will write regular output to the console and never populate the error stream.
But it will not reliably work. Neither, for that matter, will $?. If an exe returns 0 and doesn't write to error $? will be true.
While it might be a pain in the butt to deal with each of these individual .exes and their errors in PowerShell, it's a great example of why PowerShell's highly structured Output/Error/Verbose/Warning/Debug/Progress streams and consistent error behavior in Cmdlets beats out plain old .EXE tools.
Hope this Helps
$ErrorActionPreference controls PowerShell's behavior with regard to commandlets and PowerShell functions -- it is not sensitive to exit codes of "legacy" commands. I'm still looking for a work-around for this design decision myself -- for this issue, refer to this thread: How to stop a PowerShell script on the first error?. You'll probably conclude that this custom "exec" function is the best solution: http://jameskovacs.com/2010/02/25/the-exec-problem/
With regard to the stop-on-stderr behavior, I can't reproduce this behavior in PowerShell 3:
Invoke-Command
{
$ErrorActionPreference='Stop'
cmd /c "echo hi >&2" # Output to stderr (this does not cause termination)
.\DoesNotExist.exe # Try to run a program that doesn't exist (this throws)
echo bye # This never executes
}
Update: The stop-on-stderr behavior appears to take effect when using PS remoting.