The PowerShell call operator (&) can be cumbersome to use, but following some simple rules or using a general template can do the trick. This operator is also called the invoke operator.
Parameters
When using the call operator you have to remember it executes the string to the first space, and because of this it can not take a command-line string with parameters like „ping www.sqladmin.info -n 5“.A solution to this is given by Devlin Bentley in the blog entry „PowerShell Call Operator (&): Using an array of parameters to solve all your quoting problems“.
In general the trick is to call the command with the operator and put parameters in an additional array.
[String]$cmd = 'ping'
$cmd += '.exe'
[String[]]$param = @('www.sqladmin.info','-n','5')
& $cmd $param
A output of this could be
Pinging www.sqladmin.info [212.97.133.23] with 32 bytes of data:
Reply from 212.97.133.23: bytes=32 time=16ms TTL=120
Reply from 212.97.133.23: bytes=32 time=24ms TTL=120
Reply from 212.97.133.23: bytes=32 time=23ms TTL=120
Reply from 212.97.133.23: bytes=32 time=20ms TTL=120
Reply from 212.97.133.23: bytes=32 time=19ms TTL=120
Ping statistics for 212.97.133.23:
Packets: Sent = 5, Received = 5, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 16ms, Maximum = 24ms, Average = 20ms
By putting the command in a string by itself, it can be build in script with path and so on. I have tried to illustrate this by adding the file extension in a seperate line of the script above.
The parameter values can be defined element by element into a parameter array.
[String]$cmd = 'ping'
$cmd += '.exe'
[String[]]$param = @()
$param += 'www.sqladmin.info'
$param += '-n'
$param += '5'
& $cmd $param
Catch output
To catch the output line by line it can be taken from the output stream.[String]$cmd = 'ping'
$cmd += '.exe'
[String[]]$param = @('www.sqladmin.info','-n','5')
& $cmd $param |
ForEach-Object { "{0:s}Z $($_)" -f $([System.DateTime]::UtcNow) }
A output of this could be
2013-03-26T13:10:39Z
2013-03-26T13:10:39Z Pinging www.sqladmin.info [212.97.133.23] med 32 byte data:
2013-03-26T13:10:39Z Reply from 212.97.133.23: byte=32 time=3ms TTL=118
2013-03-26T13:10:40Z Reply from 212.97.133.23: byte=32 time=2ms TTL=118
2013-03-26T13:10:41Z Reply from 212.97.133.23: byte=32 time=3ms TTL=118
2013-03-26T13:10:42Z Reply from 212.97.133.23: byte=32 time=2ms TTL=118
2013-03-26T13:10:43Z Reply from 212.97.133.23: byte=32 time=2ms TTL=118
2013-03-26T13:10:43Z
2013-03-26T13:10:43Z Ping statistics for 212.97.133.23:
2013-03-26T13:10:43Z Packets: Sent = 5, Recieved = 5, Lost = 0 (0% loss),
2013-03-26T13:10:43Z Approximate round trip times in milli-seconds:
2013-03-26T13:10:43Z Minimum = 2ms, Maximum = 3ms, Average = 2ms
Be aware of large amounts of output. I have experienced that several thousand lines of output will make the script execution unstable in a unpredictable way.
Exception
A simple exception handling withTry-Catch-Finally
blocks could be like[String]$cmd = 'ping'
$cmd += '.exe'
[String[]]$param = @('www.sqladmin.info','-n','5')
try {
& $cmd $param |
ForEach-Object { "{0:s}Z $($_)" -f $([System.DateTime]::UtcNow) }
}
catch {
"{0:s}Z ERROR: $($_.Exception.Message)" -f $([System.DateTime]::UtcNow)
}
If the command in the variable
$cmd
is change to „noping.exe
“, the output will like2013-03-29T14:42:31Z ERROR: The term 'noping.exe' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
Trap
In the beginning PowerShell only had theTrap
command, but it had some limitations. Passing on a error from a Trap
can be done with the Throw
.It is generally recommended to use
Try-Catch-Finally
that we got with PowerShell v2.Jeffrey Snover shared a very short but precise comparison in „Traps vs Try/Catch“.
Error
The automatic variable$LastExitCode
contains the exit code of the last program that was executed.This is nice in a simple setup, but sometimes there are several errors from one program. In such case I would like to know all errors and their details.
The automatic variable
$Error
is an array of all errors (System.Management.Automation.ErrorRecord
) in the current PowerShell session. The latest error is the first element in the array ($Error[0]
).This is a simple demonstration how
$Error
can be used:$Error.Clear()
try {
throw [System.DivideByZeroException]
}
catch {
$PSItem.CategoryInfo
}
finally {
":: Final"
}
if ($Error) {
"{0:s} Error in script execution. See log for details." -f $([System.DateTime]::Now)
if ($Error[0].Exception -match 'System.DivideByZeroException') {
"-- Divide-By-Zero Exception"
}
}
"$($Error.Count) Error(-s)."
The result will be something like this:
Category : OperationStopped
Activity :
Reason : RuntimeException
TargetName : System.DivideByZeroException
TargetType : RuntimeType
:: Final
2014-01-27T20:45:29 Error in script execution. See log for details.
-- Divide-By-Zero Exception
1 Error(-s).
When building/developing a script
$Error
is a great tool to look into an error.Details on the exception itself can be very usefull. This you can get by piping the Exception to the CmdLet
Get-Member
. The professional lazy scripting guy can use the alias:$error[0].Exception | gm
Injection attack
Several blog posts and forum answers uses the cmdlet Invoke-Expression, but please notice that this could make the script open to injection attacs. This is described by the Windows PowerShell Team in the blog post „Invoke-Expression considered harmful“.Running Executables
This subject is already described in a TechNet Wiki article: „PowerShell: Running Executables“.The article covers several other methods to call a command-line in PowerShell, but I will for now stay with the call operator.
History
2014-01-27 : Exception handling added note onTrap
. Error section added.2013-03-29 : Exception handling example added.
2013-03-26 : The parts on piping the output and defining the parameter values element by element are added.