I try to answer questions on the PowerShellCommunity forums as often as I can. One fairly common question is how do we execute native commands. Of course, posters don't ask the question in that fashion. Typically, they are trying to execute a command and they're trying to use Invoke-Item, Invoke-Expression, Invoke-Command, or some other facility to execute the command. What they are doing doesn't work and they need help. I almost always give the same advice, call the command directly.
Usually the root cause of the problem is that the command they are trying to execute contains spaces. To execute a command in a BATCH script with spaces you simply wrap the command in double quotes.
"C:\Program Files\7-Zip\7z.exe" l C:\temp\7za920.zip
This doesn't work in PowerShell causing many people to try using Invoke-Command, Invoke-Item, Invoke-Expression, start, cmd, and even [System.Diagnostics.Process] to try to execute their command. All these methods can be used to accomplish the goal but they add additional complexity that we don't need.
PowerShell knows how to execute native commands directly. We can see this when we attempt to execute the tasklist native command.
PS C:\> tasklist /fi "imagename eq explorer.exe" /fo list Image Name: explorer.exe PID: 4012 Session Name: Console Session#: 0 Mem Usage: 28,248 K PS C:\>
So what happens when the command to execute is wrapped in quotes? PowerShell interprets the command as a string literal. A string literal is an expression in PowerShell. String literals can be combined in to larger expressions with the use of operators.
PS C:\> "red brick" -match "red" True PS C:\>
In that expression -match is the operator linking two separate string literals into a larger expression. The native command example above has a string literal followed by another, implicit, string literal resulting in a parse error.
PS C:\> "C:\Program Files\7-Zip\7z.exe" l C:\temp\7za920.zip
Unexpected token 'l' in expression or statement.
At line:1 char:34
+ "C:\Program Files\7-Zip\7z.exe" l <<<< C:\temp\7za920.zip
+ CategoryInfo : ParserError: (l:String) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : UnexpectedToken
PS C:\>
The PowerShell designers knew that users would need to execute commands like this so they added a special operator to interpret string literals as commands, the call operator (&). See Get-Help about_operators for details. Executing our command using the call operator is simple.
PS C:\> & "C:\Program Files\7-Zip\7z.exe" l C:\temp\7za920.zip 7-Zip 4.65 Copyright (c) 1999-2009 Igor Pavlov 2009-02-03 Listing archive: C:\temp\7za920.zip Date Time Attr Size Compressed Name ------------------- ----- ------------ ------------ ------------------------ 2010-11-18 08:08:04 ....A 91020 83925 7-zip.chm 2010-11-18 08:27:33 ....A 587776 299189 7za.exe 2010-03-13 00:06:33 ....A 1162 544 license.txt 2010-11-18 08:09:09 ....A 1254 644 readme.txt ------------------- ----- ------------ ------------ ------------------------ 681212 384302 4 files, 0 folders PS C:\>
The call operator is needed whenever the command to execute, and only the command, is represented by an expression. We could calculate the path to the 7z.exe native command using the ProgramFiles environment variable and execute the command in a single command.
PS C:\> & $(Join-Path -Path $env:ProgramFiles -ChildPath 7-Zip\7z.exe) l C:\temp\7za920.zip
We could have assigned the result of Join-Path to a variable and used the call operator to execute the command represented by the variable too.
PS C:\> $7z = Join-Path -Path $env:ProgramFiles -ChildPath 7-Zip\7z.exe PS C:\> & $7z l C:\temp\7za920.zip
Always remember KISS, even with PowerShell.
I love you. I would totally buy you a beer.
ReplyDelete