Using Windows PowerShell to run old command line tools (and their weirdest parameters)
Introduction
For a while now, I have let go of the old CMD.EXE command line and moved over to POWERSHELL.EXE for good.
As you probably know, you can run any old command from the new Windows PowerShell.
However, there are times when the name or the syntax of old tools might create issues.
But it can all be solved.
Problem 1: Name conflicts
One common issue is when an alias for a PowerShell cmdlet conflicts with the name of an old tool.
Say, for instance, you like the old “Service Control” tool called SC.EXE.
SC.EXE is very flexible and I can understand why you might still like it (no excuses for using even older NET.EXE for managing services, though :-).
If you want to query the status of the SMB Server service, for instance, you can use this with CMD.EXE:
C:>SC QUERY LANMANSERVER
SERVICE_NAME: LANMANSERVER
TYPE : 20 WIN32_SHARE_PROCESS
STATE : 4 RUNNING
(STOPPABLE, PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
If you try the same thing in PowerShell, you get this:
PS C:> SC QUERY LANMANSERVER
Set-Content : Access to the path 'C:QUERY' is denied.
At line:1 char:1
+ SC QUERY LANMANSERVER
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : PermissionDenied: (C:QUERY:String) [Set-Content], UnauthorizedAccessException
+ FullyQualifiedErrorId : GetContentWriterUnauthorizedAccessError,Microsoft.PowerShell.Commands.SetContentCommand
That’s because SC is the alias for the Set-Content cmdlet. Which takes priority over the SC.EXE file.
Solution 1A: Use the .EXE extension
To overcome the issue, you can simply refer to the old tool including the .EXE extension.
This eliminates the ambiguity and will make the same command line work with both CMD.EXE and PowerShell unchanged.
This also makes it clear to whoever is using your scripts that this is an old .EXE tool, not a PowerShell alias.
PS C:> SC.EXE QUERY LANMANSERVER
SERVICE_NAME: LANMANSERVER
TYPE : 20 WIN32_SHARE_PROCESS
STATE : 4 RUNNING
(STOPPABLE, PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
Solution 1B: Use CMD /C
Another way to make it work is to run CMD.EXE itself and pass your command to it, in quotes.
This works, although it’s not as efficient, since you essentially run an instance of CMD.EXE just to execute your command.
PS C:> CMD /C "SC QUERY LANMANSERVER"
SERVICE_NAME: LANMANSERVER
TYPE : 20 WIN32_SHARE_PROCESS
STATE : 4 RUNNING
(STOPPABLE, PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
Solution 1C: Use a PowerShell equivalent
In many cases, there is a PowerShell cmdlet to do exactly what your old tool does.
In this specific case, you can use the Get-Service cmdlet:
PS C:> Get-Service LANMANSERVER | FL
Name : LANMANSERVER
DisplayName : Server
Status : Running
DependentServices : {Browser}
ServicesDependedOn : {SamSS, Srv}
CanPauseAndContinue : True
CanShutdown : False
CanStop : True
ServiceType : Win32ShareProcess
Problem 2: Special PowerShell characters
Sometimes, the parameters of an old tool use characters that have special meaning to PowerShell.
Let’s say you want to change the permissions on a folder to allow all users to have "Full" access. With CMD.EXE, you could use:
C:>ICACLS.EXE C:TEST /GRANT USERS:(F)
processed file: C:TEST
Successfully processed 1 files; Failed processing 0 files
The command above works fine from CMD.EXE, but when you try running it in PowerShell, you get an error:
PS C:> ICACLS.EXE C:TEST /GRANT USERS:(F)
The term 'F' 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.
At line:1 char:34
+ ICACLS.EXE C:TEST /GRANT USERS:(F)
+ ~
+ CategoryInfo : ObjectNotFound: (F:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundExceptionn
A related issue might happen while trying to grant permission to a computer object, which has a name ending with a $.
PS C:> ICACLS.EXE C:TEST /GRANT COMPUTERNAME$:(F)
At line:1 char:39
+ ICACLS.EXE C:TEST /GRANT COMPUTERNAME$:(F)
+ ~~
Invalid variable reference. '$' was not followed by a valid variable name character. Consider using ${} to delimit the
name.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : InvalidVariableReference
The problem is that the parenthesis and dollar sign have a special meaning to PowerShell.
Similar conflicts occur with other common characters like the curly brackets.
Again, there are at least a few different ways to overcome the issue.
Solution 2A: Use CMD /C
As with the first problem, you can run CMD.EXE itself and pass your command and its parameters in quotes.
Efficiency aside, this will work fine, since PowerShell will not try to parse the string in quotes.
PS C:> CMD.EXE /C "ICACLS.EXE C:TEST /GRANT USERS:(F)"
processed file: C:TEST
Successfully processed 1 files; Failed processing 0 files
Solution 2B: Use the PowerShell escape character
For this solution, you first need to identify exactly what characters you are using have special meaning to PowerShell.
Then you need to precede each one with a backtick or grave accent (`), which is the PowerShell “escape character”.
The main issue with this solution is that it makes your scripts harder to write and read. And you must know exactly what characters to escape.
In our example, we need to do that for the “(“ and the “)” characters:
PS C:> ICACLS.EXE C:TEST /GRANT USERS:`(F`)
processed file: C:TEST
Successfully processed 1 files; Failed processing 0 files
Solution 2C: Use the new PowerShell v3 “--%” syntax
PowerShell v3 (included in Windows Server “8” Beta), has another option to solve this issue.
You simply add a the --% sequence (two dashes and a percent sign) anywhere in the command line and PowerShell will not try to parse the remainder of that line.
For our example, you could use:
PS C:> ICACLS.EXE --% C:TEST /GRANT USERS:(F)
processed file: C:TEST
Successfully processed 1 files; Failed processing 0 files
Or you could use:
PS C:> ICACLS.EXE C:TEST --% /GRANT USERS:(F)
processed file: C:TEST
Successfully processed 1 files; Failed processing 0 files
Solution 2D: Use a PowerShell equivalent
Using a PowerShell equivalent is obviously also an option for problem 2.
In the case of ICACLS.EXE, you can use the Set-ACL cmdlet.
You can find an example of how to use Set-ACL in this other blog post.
Mixing it up
With the tips shown here, you can safely enjoy the flexibility of PowerShell combined with your old command line tools.
You might learn a few tricks and start combining the old and the new in entirely new ways.
For instance, you can use the flexibility of the wildcards in Get-Service with some obscure option in SC.EXE:
Get-Service LAN* | % { $_.Name; SC.EXE SDSHOW $_.Name }
Or you can use PowerShell’s a filter with Get-Item (alias Dir) to pass a subset of files for processing by ICACLS.EXE:
DIR C:TEST -Recurse | ? {$_.Length -ge 1MB} | % { ICACLS.EXE $_.FullName /Grant Administrator:`(F`) }
You could even loop through a few numbers and use the good old FSUTIL.EXE tool to create lots of files of different sizes for a test project:
1..100 | % { FSUTIL.EXE FILE CREATENEW C:TESTFILE$_.TXT ($_*10KB) }
Conclusion
At this stage, you are probably already convinced that Windows PowerShell an Administrator’s best friend.
However, you might be holding out because you have some old tool with an odd name or parameters that you though you couldn’t use with POWERSHELL.EXE.
I would highly encourage you to use these tips to stop using CMD.EXE altogether and move permanently to PowerShell as you primary shell.
Comments
- Anonymous
January 01, 2003
This blog posts lists a few of the common test cases for the new "Hyper-V over SMB" scenario in Windows - Anonymous
March 27, 2012
Thanks for this, I was experimenting with the powershell yesterday and I really like it.thanks again. - Anonymous
January 27, 2017
The comment has been removed