4

I have an extensive set of CMD scripts for an automation suite.

In a console using CMD.exe, everything works fine. After installing the Windows Creator's update, where PowerShell becomes the new default Windows shell via Explorer's menu options, my scripts break at-random. I can't provide any meaningful code for repro for two main reasons:

  1. No obvious error is occurring; my automated scripts just hang, eventually
  2. The halt isn't even occurring in the same place each time

What I can tell you is that the suite heavily relies on exit codes, use of findstr.exe, and type.

I know things like Windows macros, e.g., %Var% are not compatible, but I was assuming that since the only call I did was to a .bat file, .bat behavior would be the only thing I would need to worry about.

If that's not the case, should my initial .bat be triggering the direct execution of a CMD.exe instance with my parameters? If so, what's the best way to do that, PowerShell-friendly?

kayleeFrye_onDeck
  • 6,648
  • 5
  • 69
  • 80
  • 4
    Several CMD commands are actually aliased in PowerShell to "equivalent" PowerShell commands, but the PowerShell commands don't quite work the same as the CMD commands. Try explicitly calling CMD against your batch file with `CMD /C MyBatchFilePathName`. – Jeff Zeitlin Jun 19 '17 at 18:50
  • 3
    btw. there's no type.exe.`TYPE` is internal command and to call it you need bat file or to call `cmd /c type..` – npocmaka Jun 19 '17 at 18:58
  • 4
    @JeffZeitlin, running a .bat or .cmd from PowerShell calls `CreateProcess` on the .bat file, which will look up the `%ComSpec%` interpreter and run the batch file with the /c option. There's no need for what you suggest. – Eryk Sun Jun 19 '17 at 19:00
  • There's a ton of re-direction using `>` and `>>`, @TessellatingHeckler – kayleeFrye_onDeck Jun 19 '17 at 19:00
  • Oh ho! Aliases... those could indeed be causing all sorts of unpredictable behavior, especially on exit-code assumptions. While I doubt an alias would change explicit previous exit codes, it could have introduced new ones for more specific behavior, leading to exit codes not designed against, which could admittedly cause unpredictable behavior. E.g., 6 behaviors could have lead to exit code N, while the alias would only have half of them mapped to the same exit code, where the other three were triggered earlier for different exit codes. Hmm... That still doesn't mesh with @eryksun's comment. – kayleeFrye_onDeck Jun 19 '17 at 19:05
  • Aliases considered harmful. To see a list of your current aliases, use the `Get-Alias` command. DIR=Get-ChildItem TYPE=Get-Content – lit Jun 19 '17 at 19:09
  • 1
    @eryksun - Note that the user has specified Win10 Creator's Update, with PS the 'default' shell. I don't have that configuration handy to check, but I can see that causing `COMSPEC` to be set to `powershell.exe` rather than `cmd.exe`, and thus cause issues. That's why I suggested calling `CMD` explicitly. – Jeff Zeitlin Jun 19 '17 at 19:09
  • 3
    @JeffZeitlin, the change in the Creators update is to the Windows graphical shell only, to increase the awareness of PowerShell and make it more convenient to run. They would never change `%ComSpec%` to powershell.exe because that would break most batch scripts. Batch files have to be run via cmd.exe. – Eryk Sun Jun 19 '17 at 19:14
  • `ComSpec` is set to `C:\Windows\system32\cmd.exe` in a console using PowerShell when querying with `Get-ChildItem Env:` in the Creator's Update. – kayleeFrye_onDeck Jun 19 '17 at 19:16
  • @TessellatingHeckler the question is specifically asking for fundamental incompatibilities between PowerShell and CMD when triggering a CMD/Batch script in PowerShell, not debugging a portion of my code. If I could tell you where in my scripts the halt was occurring, I could give you meaningful info/sample code. However, it's not consistent where it is halting, no error appears to be triggering, and there's no hybridization of CMD and powershell scripts; all CMD scripts. Therefore, I felt this was a reasonable question to ask without code. – kayleeFrye_onDeck Jun 19 '17 at 19:26
  • 3
    The console has undergone major enhancements in every release of Windows 10, including the Creators update. New code has the potential to introduce bugs, which could maybe cause console client applications to hang. Try using the legacy console, which you can enable in the console properties->options dialog. – Eryk Sun Jun 19 '17 at 19:29
  • I turned the initial batch call into a shim-batch which in-turn called what was the original batch file's contents, calling that batch specifically using `cmd.exe /c batch_name.bat %*` -- it may be too early to call, but no halt happened on my first test-run. I'll do more testing, and if there is no known answer for this question before I get back, I think I'll post an unaccepted answer as a workaround until someone finds an actual answer. Does that seem appropriate? – kayleeFrye_onDeck Jun 19 '17 at 20:18
  • It's got to be some undocumented weirdness if in-fact PowerShell is not using the PowerShell environment or aliases when launching `.bat`/`.cmd` files. After some extensive testing to make sure repros were only triggering with the old code, using a batch like a shim to do what @JeffZeitlin suggested is fixing the problem 100% of the time: https://stackoverflow.com/a/44641176/3543437 – kayleeFrye_onDeck Jun 19 '17 at 23:12
  • Are you able to test your scripts when run from PowerShell on Windows 7? It would be interesting to know whether this is a new problem, or just a newly discovered one. – Harry Johnston Jun 20 '17 at 03:37
  • Unfortunately not, @HarryJohnston :( I'd be interested both in older OS problems, and different versions of PowerShell. Unfortunately I would likely need to identify the root cause before I could do the debugging any justice. – kayleeFrye_onDeck Jun 20 '17 at 17:46

3 Answers3

4

eryksun's comments on the question are all worth heeding.

This section of the answer provides a generic answer to the generic question in the question's title. See the next section for considerations specific to the OP's scenario.

Generally speaking, there are only a few things to watch out for when invoking a batch file from PowerShell:

  • Always include the specific filename extension (.bat or .cmd) in the filename, e.g., script_name.bat

    • This ensures that no other forms of the same command (named script_name, in the example) with higher precedence are accidentally executed, which could be:
      • In case of a command name without a path component:
        • An alias, function, cmdlet, or an external executable / PowerShell script (*.ps1) that happens to be located in a directory listed earlier in the $env:PATH (%PATH%) variable; if multiple executables by the same name are in the same (earliest) directory, the next point applies too.
      • In case of a command name with a path component:
        • A PowerShell script (*.ps1) or executable with the same filename root whose extension comes before .bat or .cmd in the %PATHEXT% environment variable.
  • If the batch file is located in the current directory, you must prefix its filename with ./

    • By design, as a security measure, PowerShell - unlike cmd.exe - does NOT invoke executables located in the current directory by filename only, so invoking script_name.bat to invoke a batch file of that name in the current directory does not work.[1]

    • Instead, you must use a path to target such an executable so as to explicitly signal the intent to execute something located in the current directory, and the simplest approach is to use prefix ./ (.\, if running on Windows only); e.g., ./script_name.bat.

  • When passing parameters to the batch file:

    • Either: be aware of PowerShell's parsing rules, which are applied before the arguments are passed to the batch file - see this answer of mine.
    • Or: use --% (the PSv3+ stop-parsing symbol) to pass the remaining arguments as if they'd been passed from a batch file (no interpretation by PowerShell other than expansion of %<var>%-style environment-variable references).

[1] eryksun points out that on Vista+ you can make cmd behave like PowerShell by defining environment variable NoDefaultCurrentDirectoryInExePath (its specific value doesn't matter).
While ill-advised, you can still force both environments to always find executables in the current directory by explicitly adding . to the %PATH% / $env:PATH variable; if you prepend ., you get the default cmd behavior.


As for your specific scenario:

After installing the Windows Creator's update, where PowerShell becomes the new default Windows shell via Explorer's menu options

This applies to the following scenarios:

  • Pressing Win-X (system-wide keyboard shortcut) now offers PowerShell rather than cmd in the shortcut menu that pops up.

  • Using File Explore's File menu now shows Open Windows PowerShell in place of Open command prompt (cmd).

However, nothing has changed with respect to how batch files are invoked when they are opened / double-clicked from File Explorer: The subkeys of HKEY_CLASSES_ROOT\batchfile and HKEY_CLASSES_ROOT\cmdfile in the registry still define the shell\open verb as "%1" %*, which should invoke a batch file implicitly with cmd /c, as before.

However, per your comments, your batch file is never run directly from File Explorer, because it require parameter values, and it is invoked in two fundamental ways:

  • Explicitly via a cmd console, after entering cmd in the Run dialog that is presented after pressing Win-R (system-wide keyboard shortcut).

    • In this case, everything should work as before: you're invoking your batch file from cmd.
  • Explicitly via PowerShell, using File Explorer's File menu.

    • Per your comments, the PowerShell console may either be opened:

      • directly in the directory in which the target batch file resides.
      • in an ancestral directory, such as the root of a thumb drive on which the batch file resides.
    • In both cases, PowerShell's potential interpretation of arguments does come into play.

    • Additionally, in the 2nd case (ancestral directory), the invocation will only work the same if the batch file either does not depend on the current directory or explicitly sets the current directory (such as setting it to its own location with cd /d "%~dp0").

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Only invocation from the script's directory via an Admin PowerShell console was tried against the original halts. I just reverted revisions on one of my Windows boxes to test invocation from the directory the script runs in instead of the script location. Example would use for what's been verified would be, `.\halting_script.bat C:\dir\ToDoStuffIn TrueOrFalse AlphaNumParam TrueOrFalse` e.g., `.\script.bat C:\temp true mt true` -- besides the directory, no special characters or quotes would be expected. – kayleeFrye_onDeck Jun 21 '17 at 01:07
  • @kayleeFrye_onDeck: These parameters should be passed through as-is by PowerShell, so, unfortunately, I still don't have an explanation. – mklement0 Jun 21 '17 at 01:43
1

This is a non-answer solution if encountering the question's specific behavior. I've verified all my halting scripts stopped halting after implementing a shim-like workaround.

As erykson said, there doesn't appear to be a reason why using a shim would be required. The goal is then to explicitly launch the script in CMD when using PowerShell, which aligns with Jeff Zeitlin's original suggestion in the question's comments.

So, let's say you're in my shoes with your own script_name.bat.

script_name.bat was your old script that initializes and kicks off everything. We can make sure that whatever was in script_name.bat is correctly run via CMD instead of PowerShell by doing the following:

  1. Rename script_name.bat to script_name_shim.bat
  2. Create a new script_name.bat in the same directory
  3. Set its contents to:

    @echo off
    CMD.exe /C "%~dp0script_name_shim.bat" %*
    exit /b %errorlevel%
    

That will launch your script with CMD.exe regardless of the fact that you started in PowerShell, and it will also use all your command-line arguments too.

kayleeFrye_onDeck
  • 6,648
  • 5
  • 69
  • 80
  • 2
    It's great if that helped you, but, as you imply, the _why_ remains a mystery - there should be no reason for such a shim; the only tricky part if PowerShell is involved is how parameters are parsed by PowerShell up front - but you seem to just be passing them through. As an aside: better to enclose `%~dp0script_name_shim.bat` in double quotes. – mklement0 Jun 20 '17 at 03:08
0

This looks like a chicken egg problem, wihtout knowing the code it's difficult to tell where the problem is.
There are a ton of ways to start batches with cmd.exe even in win10cu.

Aliases are only a problem when working interactively with the PowerShell console and expecting behavior as it used to be in cmd.exe.
The aliases depend also on the loaded/imported modules and profiles.

  • This small PowerShell script will get all items from Help.exe and perform a Get-Command with the item.
  • internal commands without counterparts in PoSh are filtered out by the ErrorAction SilentlyContinue.
  • Applications (*.exe files) are assumed identical and removed by the where clause.

help.exe |
  Select-String '^[A-Z][^ ]+'|
    ForEach-Object {
      Get-Command $_.Matches.Value -ErrorAction SilentlyContinue
    }| Where-Object CommandType -ne 'Application'|Select *|
       Format-Table -auto CommandType,Name,DisplayName,ResolvedCommand,Module

Sample output on my system, all these items will likely work differently in PowerShell:

CommandType Name   DisplayName            ResolvedCommand Module
----------- ----   -----------            --------------- ------
      Alias call   call -> Invoke-Method  Invoke-Method   pscx
      Alias cd     cd -> Set-LocationEx   Set-LocationEx  Pscx.CD
      Alias chdir  chdir -> Set-Location  Set-Location
      Alias cls    cls -> Clear-Host      Clear-Host
      Alias copy   copy -> Copy-Item      Copy-Item
      Alias del    del -> Remove-Item     Remove-Item
      Alias dir    dir -> Get-ChildItem   Get-ChildItem
      Alias echo   echo -> Write-Output   Write-Output
      Alias erase  erase -> Remove-Item   Remove-Item
      Alias fc     fc -> Format-Custom    Format-Custom
   Function help                                          pscx
      Alias md     md -> mkdir            mkdir
   Function mkdir
   Function more
      Alias move   move -> Move-Item      Move-Item
   Function Pause
      Alias popd   popd -> Pop-Location   Pop-Location
   Function prompt
      Alias pushd  pushd -> Push-Location Push-Location
      Alias rd     rd -> Remove-Item      Remove-Item
      Alias ren    ren -> Rename-Item     Rename-Item
      Alias rmdir  rmdir -> Remove-Item   Remove-Item
      Alias set    set -> Set-Variable    Set-Variable
      Alias sc     sc -> Set-Content      Set-Content
      Alias sort   sort -> Sort-Object    Sort-Object
      Alias start  start -> Start-Process Start-Process
      Alias type   type -> Get-Content    Get-Content
  • 1
    You might want to strictly define what you mean by, _"when working interactively with the PowerShell console"_. This suite is purely CMD scripts, but the initial script is being triggered inside a console loaded with PowerShell, directly used as `.\batch_name.bat params` – kayleeFrye_onDeck Jun 19 '17 at 21:07
  • This list is easier for me to filter by what I'm not using, haha! `sort`...`fc`... `more`... `cls`... and `help`. I'm pretty sure I use all the rest. Good list, LotPings! The difference between `chdir` and `cd` is a little sneaky! – kayleeFrye_onDeck Jun 19 '17 at 21:10
  • 1
    Ack, reworded the text. –  Jun 19 '17 at 21:11
  • 2
    @kayleeFrye_onDeck, these PowerShell aliases have nothing to do with a batch file being executed by a cmd.exe process. All it inherits from PowerShell is the console handle and standard handles. PowerShell waits in the background for cmd.exe to exit. – Eryk Sun Jun 19 '17 at 21:42