Running legacy command line commands in PowerShell

Last week I was asked by two different colleagues to help them sort out some issues they were having running old command prompt (cmd.exe) commands in a PowerShell script or shell environment. On its face it seems like an odd question, since almost all command prompt commands and EXEs run without issue in PowerShell. But, there are a few subtle gotchas that do come up, so I thought I’d write a quick post to point out these lesser-known issues and some ways to deal with them.

For the few edge cases where PowerShell does indeed trip up on older commands, most, if not all, of these issues stem from characters in the legacy command (parameters and/or switches) colliding with or having a different meaning in PowerShell than they do in the command prompt. A few examples are:

  • Passing in computer objects or machine account names like
     COMPUTERNAME$as parameters (computer objects end in $, but in PowerShell, $ has a special meaning)
  • Collisions where a legacy executable has the same name as a PowerShell cmdlet or alias, but are not the same command (think
  • sc.exe
  • and the alias
  • sc
  • for
  • Set-Content
  • in PowerShell)
  • Commands that use parentheses as part of their parameters which, again, have a different meaning in PowerShell (
  • icacls.exe
  • is the only one coming to mind at the moment, but there may be more)

So, how do we deal with these commands in PowerShell? Well, you have a number of options:

  • Use a PowerShell equivalent to the command you are trying to run
  • Use PowerShell’s escape character to escape out the characters being misinterpreted
  • Use PowerShell’s
  •  — %
  • sequence in the legacy command to stop parsing the rest of the command as PowerShell syntax (requires PowerShell v3 or later)
  • Explicitly pass your command to the cmd.exe shell itself for execution

Use the PowerShell equivalent

I’ll first mention the most obvious (and preferred) way to resolve this issue: use the PowerShell equivalent of the command prompt command you are trying to run. Under the hood, many of the old commands are just aliases of new PowerShell cmdlets anyway (

dir

is an alias for

Get-ChildItem

, for example). And yes, even though cmdlets usually require more typing (

Get-ChildItem

instead of

dir

, or

Test-Connection

instead of

ping

), it’s not a bad habit to get into. Also, tab completion is your friend. And who knows, one day your brilliant code may be distributed to others in your company or online, so avoiding legacy commands as much as possible keeps things as modern and portable as possible, and is technically faster execution-wise (see below).

Use PowerShell’s escape character

If you cannot, for whatever reason, use a PowerShell equivalent (or there just isn’t one), a second option is to escape out the offending characters that are breaking the command execution. If you know which character(s) are causing PowerShell to misinterpret the command prompt command, you can use PowerShell’s escape character (the backtick, or

`

) to escape out that character. For example:

PS C:\> icacls.exe C:\inetpub /grant Users:`(R`)

… will allow this command to run correctly in PowerShell because the backticks are telling to PowerShell to interpret the parentheses literally, in the same way you would escape out special characters in a string.

Use PowerShell’s “- -%” sequence

Similar to using the escape character above, PowerShell v3 and later support using

--%

(“dash dash percent”) as a way of telling PowerShell to stop interpreting whatever follows that sequence as PowerShell syntax. Using the example command from above, either one of the following would work:

PS C:\> icacls.exe --% C:\inetpub /grant Users:(R)
PS C:\> icacls.exe C:\inetpub --% /grant Users:(R)

Just think of the

--%

as another parameter in your command prompt command. The 2 things to remember when using this method are:

  • Do not start the command line with the sequence (
  • PS C:\> — % icacls.exe C:\inetpub /grant Users:(R)
  • will not work)
  • PowerShell will stop interpreting as PowerShell syntax anything after the
  •  — %

Explicitly pass the legacy command to cmd.exe for execution

I list this one last because, in my opinion, it is the least desirable, and is less efficient resource-wise than the other methods listed above. With that said, one other solution is to explicitly pass the command you want to run (in quotes) to the

cmd.exe

shell for execution using the

/c

switch. This was also a trick back in the day when you wanted to execute batch or shell commands in VBS scripts. So, for example, doing this:

cmd.exe /c "sc query eventlog"

… will execute the

sc.exe,/pre> command as you would expect.

Small side-note: for the case of very specific collisions like the
sc.exe

command and

sc,/pre> alias, typing the full executable command with it's extension (
sc.exe

) will often resolve the problem. As a PowerShell command,

sc

can mean the executable

sc.exe

or the alias

sc

. By typing

sc.exe

as the command, the ambiguity is removed and the shell knows how to proceed.

As I mentioned above, one minor thing to note about using this

cmd.exe /c

method is that it will be slower than the other ways mentioned in this post. What’s really happening behind the scenes is that our PowerShell process is spawning a separate

cmd.exe

process, and that

cmd.exe

process will then execute your command. The spawning of these additional processes has computational overhead and cost in terms of performance and resources. If only done once or a few times, this difference is almost imperceptibly negligible. However, if this is part of a script or process that runs often, loops in any way, or acts on many objects, those costs in resources and execution time may become a factor and lead you to choose one of the other methods mentioned here.


Originally published at dustin.io.