Nothing solves everything – PowerShell and other technologies
Raymond recently wrote a post titled, “A new scripting language doesn’t solve everything.” In that post (and in a previous one,) he points out that backwards compatibility is extremely important for cmd.exe (with itself,) and for PowerShell (with existing cmd.exe scripts.) More importantly, the point is that PowerShell doesn’t solve everything:
Shipping a new command shell doesn’t solve everything either. For one thing, you have to decide if you are going to support classic batch files or not. Maybe you decide that you won’t and prefer to force people to rewrite all their batch files into your new language. Good luck on that.
On the other hand, if you decide that you will support batch files after all, then presumably your new command shell will not execute old batch files natively, but rather will defer to CMD.EXE. And there’s your problem: You see, batch files have the ability to modify environment variables and have the changes persist beyond the end of the batch file.
Similar threads have come up in the newsgroups in relation to VBScript.
Raymond is absolutely correct, and you won’t find a single person on the PowerShell team that says otherwise. Or has ever said otherwise, for that matter. PowerShell is not a swap-in replacement for cmd.exe, it is not a swap-in replacement for the Windows Scripting Host, does not need to re-implement every command-line tool that ships in Windows, and does not solve world hunger. Tensions that suggest differently are misguided.
Many people – and many systems – depend on the subtle details of the tool with which they’ve chosen to solve their problem. If you want to write an application that understands the source files for all related technologies (ie: cmd.exe scripts, or .vbs files,) then you’d better make sure that you re-implement every nuance and bug as well. That includes the bugs that even the owners of those related technologies don’t know about, because I guarantee that a script somewhere depends on it.
The solution isn’t to burn your time by rewriting your systems, either. If you have code that works – keep it! I currently have files from 41 different technologies in my “tools” directory:
[D:\lee\tools]
PS:170 > dir | group Extension | sort -desc Count
Count Name Group
----- ---- -----
275 .exe {adlb.exe, ansi2knr.exe, atmar
59 .ps1 {#share-file.ps1, backup-chang
39 .bat {articlecounter.bat, c.bat, ch
25 .dll {acctinfo.dll, AutoItX3.dll, B
20 .cmd {d.cmd, devdiv.cmd, dumpfsmos.
16 .vbs {checkrepl.vbs, clean.vbs, clo
8 .js {calc.js, cio.js, d2.js, evt2c
7 {%backup%~, 0, _viminfo, autom
6 .config {cordbg.exe.config, perfcompar
5 .hlp {depends.hlp, srvmgr.hlp, usrm
5 .ini {KeePass.ini, memtriage.ini, r
5 .txt {cmgetcer.txt, getcm.txt, inst
4 .doc {kernrate.doc, mqcast.doc, prn
4 .inf {clusfileport.inf, clusfilepor
3 .reg {intfiltr.reg, samplereasons.r
3 .chm {clusterrecovery.chm, eventcom
<snip>
Rewriting for the sake of rewriting provides a poor return on investment – interop is the key word here. If you want to extend your existing systems, you can do that in the tool of your choice. Most systems can be extended to include other technologies – a typical Windows build, for example, takes advantage of dozens of different tools. If you want to write new scripts or systems, you can also do that in the tool of your choice.
In both cases, PowerShell happens to be a tool that solves certain problems extremely efficiently.
To address one specific detail in Raymond’s post,
“You see, batch files have the ability to modify environment variables and have the changes persist beyond the end of the batch file.”
Try this script. From a PowerShell.exe window, this wrapper script allows you to run batch files that persistently modify their environment variables.
##############################################################################
##
## Invoke-CmdScript.ps1
##
## Invoke the specified batch file (and parameters), but also propigate any
## environment variable changes back to the PowerShell environment that
## called it.
##
## ie:
##
## PS > type foo-that-sets-the-FOO-env-variable.cmd
## @set FOO=%*
## echo FOO set to %FOO%.
##
## PS > $env:FOO
##
## PS > Invoke-CmdScript "foo-that-sets-the-FOO-env-variable.cmd" Test
##
## C:\Temp>echo FOO set to Test.
## FOO set to Test.
##
## PS > $env:FOO
## Test
##
##############################################################################
param([string] $script, [string] $parameters)
$tempFile = [IO.Path]::GetTempFileName()
## Store the output of cmd.exe. We also ask cmd.exe to output
## the environment table after the batch file completes
cmd /c " `"$script`" $parameters && set > `"$tempFile`" "
## Go through the environment variables in the temp file.
## For each of them, set the variable in our local environment.
Get-Content $tempFile | Foreach-Object {
if($_ -match "^(.*?)=(.*)$")
{
Set-Content "env:\$($matches[1])" $matches[2]
}
}
Remove-Item $tempFile
[Edit: Updated to make more usable for commands with spaces in their names.]