Get Powershell and Command Script (Batch) files to play nice together
I needed a script that could perform SVN operations, kick off a legacy command script (.bat) and perform other mundane tasks. As this was on a Windows system I used my PowerShell experience to put together a script that tied everything together.
Up until now I have had minimal trouble kicking off command scripts- but for some reason PowerShell would not execute this one script. It just barfed and threw this error:
PS C:\SVN\SourceCode> Invoke-Expression "cmd /C `"cd $SourceDir & .\Projs\ParentBuilder.cmd`""
Setting environment for using Microsoft Visual Studio 2010 x86 tools.
Building with the Configuration set to Debug.
cmd.exe : 'KickOffBuild.cmd' is not recognized as an internal or external command,
At line:1 char:4
+ cmd <<<< /C "cd C:\SVN\SourceCode & .\Projs\ParentBuilder.cmd"
+ CategoryInfo : NotSpecified: ('KickOffBuild.cmd'...ternal command,:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
operable program or batch file.
Notes
- Command Shell present directory token [stackoverflow.com]
- DOS Call command and directory expansion tokens [computerhope.com]
- Execute multiple batch commands on a single line [answers.yahoo.com]
- Just a reminder: The Back-tick character ` is the escape character in PowerShell
Background
The legacy batch script is one of the build scripts left over from our old system (Our Configuration Management (CM) lead is putting together a new system that moves away from batch scripts). The hang-up appears to be a call command in the script which kicks off another script in the same directory.
As the script works fine when executed from a regular dos prompt it seemed logical to use this command to kick off the batch script:
Invoke-Expression "cmd /C `"cd $SourceDir & .\path\to\command.bat"
Note: cmd /C launches a new instance of cmd.exe which executes the command specified after /C then terminates the session
Unfortunately this command did not work- I still can't explain why. Since it is running in a natvie cmd.exe process everything should work the same as if I had run the command from the shell prompt manually.
Troubleshooting
I ran some tests which used fresh batch scripts that used the call command to kick off other scripts and found that I could duplicate the XXXX is not recognized as an internal or extranl command if the scripts were not in the %PATH% variable or if an absolute path to the script was not specified.
After a bit more finangling I realized that the dot . character does not mean 'current directory' in a batch script like it does in BASH. A few dozen google searches later and I found out that the 'right here' token in command script is the easy to remember %~dp0
I had to pre-pend %~dp0 to the call command and a few other Environment variables that were being set in the calling script. Here are a the affected lines of the script:
Before:
set ROOT=..\myProjectsDir
set BUILD=Debug
set PAUSEWITHCRITERIA=false
set OUTDIR=..\output\Admin
set OUTSDKDIR=..\output\SDK
set OUTSERVERDIR=..\output\server
set OUTMANAGEMENTDIR=..\output\management
call %~dp0KickOffBuild.cmd
After:
set ROOT=%~dp0..\myProjectsDir
set BUILD=Debug
set PAUSEWITHCRITERIA=false
set OUTDIR=%~dp0..\output\Admin
set OUTSDKDIR=%~dp0..\output\SDK
set OUTSERVERDIR=%~dp0..\output\server
set OUTMANAGEMENTDIR=%~dp0..\output\management
call %~dp0KickOffBuild.cmd
Note 1: There shoud NOT be a backslash following %~dp0 as this causes errors (from my limited experience working with the token)
Note 2: A keen eye will note that there is relative pathing that happens after I specify the 'This directory' token. If I had taken more time I could have placed a better token in place to eliminate the need to back-up a directory. As this is a legacy script that will be replaced sometime in the next few months I settled on the more direct fix.
Summary: Watch out for relative pathing and environment variables when calling complex legacy scripts from PowerShell.