Shell expansion variables %~f0, %~p0, %~x0, %~s0, %~a0, %~t0, %~z0, %~dp0, %~nx0, %~fs0 and %~ftza0 are broken when script file is enclosed in quotes #19747

Closed
opened 2026-01-31 06:52:28 +00:00 by claunia · 5 comments
Owner

Originally created by @juj on GitHub (Apr 20, 2023).

Windows Terminal version

all

Windows build number

10.0.19041.508

Other Software

No response

Steps to reproduce

  1. Uncompress windows_terminal_broken_variable_expansion.zip
  2. Run correct.bat contained in the zip file and observe the generated print.
  3. Then run broken.bat and observe the generated print.

The zip file contains the following bat scripts:

testscript.bat

@echo off
echo.
:: these seem to work ok
echo Path of current script without quotes: %~0
echo Drive letter of current script: %~d0
echo Filename of current script: %~n0
:: the following all fail
echo Full path of current script: %~f0
echo Path of current script: %~p0
echo File extension of current script: %~x0
echo Short name path of current script: %~s0
echo File attributes of current script: %~a0
echo Date/time of current script: %~t0
echo Byte size of current script: %~z0
echo Drive + Absolute path of current script: %~dp0
echo Filename + extension of current script: %~nx0
echo Full short path name of current script: %~fs0
echo Curdir listing of current script: %~ftza0

correct.bat

@echo off
setlocal
set PATH=%PATH%;%~dp0subdir
testscript

broken.bat

@echo off
setlocal
set PATH=%PATH%;%~dp0subdir
"testscript"

Expected Behavior

Both correct.bat and broken.bat should print out exact same output.

Running correct.bat prints the expected behavior:

E:\code\emsdk\19207>correct.bat

Path of current script without quotes: testscript
Drive letter of current script: E:
Filename of current script: testscript
Full path of current script: E:\code\emsdk\19207\subdir\testscript.bat
Path of current script: \code\emsdk\19207\subdir\
File extension of current script: .bat
Short name path of current script: E:\code\emsdk\19207\subdir\TESTSC~1.BAT
File attributes of current script: --a--------
Date/time of current script: 04/20/2023 02:17 PM
Byte size of current script: 699
Drive + Absolute path of current script: E:\code\emsdk\19207\subdir\
Filename + extension of current script: testscript.bat
Full short path name of current script: E:\code\emsdk\19207\subdir\TESTSC~1.BAT
Curdir listing of current script: --a-------- 04/20/2023 02:17 PM 699 E:\code\emsdk\19207\subdir\testscript.bat

Actual Behavior

broken.bat will print out incorrect expansion of the shell expression variables ~f0, %~p0, %~x0, %~s0, %~a0, %~t0, %~z0, %~dp0, %~nx0, %~fs0 and %~ftza0. In that script, the invocation of the test script is enclosed in quotes, which is sometimes necessary e.g. when a path contains spaces in it.

In other words, calling testscript.bat works, but calling "testscript.bat" does not.

Running broken.bat prints the incorrect behavior:

E:\code\emsdk\19207>broken.bat

Path of current script without quotes: testscript
Drive letter of current script: E:
Filename of current script: testscript
Full path of current script: E:\code\emsdk\19207\testscript
Path of current script: \code\emsdk\19207\
File extension of current script:
Short name path of current script: E:\code\emsdk\19207\testscript
File attributes of current script:
Date/time of current script:
Byte size of current script:
Drive + Absolute path of current script: E:\code\emsdk\19207\
Filename + extension of current script: testscript
Full short path name of current script: E:\code\emsdk\19207\testscript
Curdir listing of current script: E:\code\emsdk\19207\testscript

Note the contents of correct.bat:

@echo off
setlocal
set PATH=%PATH%;%~dp0subdir
testscript

and broken.bat is identical, but it just encloses the invocation of testscript in quotes:

@echo off
setlocal
set PATH=%PATH%;%~dp0subdir
"testscript"

which should not have any functional difference, yet the behavior of the scripts differ.

Originally created by @juj on GitHub (Apr 20, 2023). ### Windows Terminal version all ### Windows build number 10.0.19041.508 ### Other Software _No response_ ### Steps to reproduce 1. Uncompress [windows_terminal_broken_variable_expansion.zip](https://github.com/microsoft/terminal/files/11285473/windows_terminal_broken_variable_expansion.zip) 2. Run `correct.bat` contained in the zip file and observe the generated print. 3. Then run `broken.bat` and observe the generated print. The zip file contains the following bat scripts: `testscript.bat` ```bat @echo off echo. :: these seem to work ok echo Path of current script without quotes: %~0 echo Drive letter of current script: %~d0 echo Filename of current script: %~n0 :: the following all fail echo Full path of current script: %~f0 echo Path of current script: %~p0 echo File extension of current script: %~x0 echo Short name path of current script: %~s0 echo File attributes of current script: %~a0 echo Date/time of current script: %~t0 echo Byte size of current script: %~z0 echo Drive + Absolute path of current script: %~dp0 echo Filename + extension of current script: %~nx0 echo Full short path name of current script: %~fs0 echo Curdir listing of current script: %~ftza0 ``` `correct.bat` ```bat @echo off setlocal set PATH=%PATH%;%~dp0subdir testscript ``` `broken.bat` ```bat @echo off setlocal set PATH=%PATH%;%~dp0subdir "testscript" ``` ### Expected Behavior Both `correct.bat` and `broken.bat` should print out exact same output. Running `correct.bat` prints the expected behavior: ``` E:\code\emsdk\19207>correct.bat Path of current script without quotes: testscript Drive letter of current script: E: Filename of current script: testscript Full path of current script: E:\code\emsdk\19207\subdir\testscript.bat Path of current script: \code\emsdk\19207\subdir\ File extension of current script: .bat Short name path of current script: E:\code\emsdk\19207\subdir\TESTSC~1.BAT File attributes of current script: --a-------- Date/time of current script: 04/20/2023 02:17 PM Byte size of current script: 699 Drive + Absolute path of current script: E:\code\emsdk\19207\subdir\ Filename + extension of current script: testscript.bat Full short path name of current script: E:\code\emsdk\19207\subdir\TESTSC~1.BAT Curdir listing of current script: --a-------- 04/20/2023 02:17 PM 699 E:\code\emsdk\19207\subdir\testscript.bat ``` ### Actual Behavior `broken.bat` will print out incorrect expansion of the shell expression variables `~f0`, `%~p0`, `%~x0`, `%~s0`, `%~a0`, `%~t0`, `%~z0`, `%~dp0`, `%~nx0`, `%~fs0` and `%~ftza0`. In that script, the invocation of the test script is enclosed in quotes, which is sometimes necessary e.g. when a path contains spaces in it. In other words, calling `testscript.bat` works, but calling `"testscript.bat"` does not. Running `broken.bat` prints the incorrect behavior: ``` E:\code\emsdk\19207>broken.bat Path of current script without quotes: testscript Drive letter of current script: E: Filename of current script: testscript Full path of current script: E:\code\emsdk\19207\testscript Path of current script: \code\emsdk\19207\ File extension of current script: Short name path of current script: E:\code\emsdk\19207\testscript File attributes of current script: Date/time of current script: Byte size of current script: Drive + Absolute path of current script: E:\code\emsdk\19207\ Filename + extension of current script: testscript Full short path name of current script: E:\code\emsdk\19207\testscript Curdir listing of current script: E:\code\emsdk\19207\testscript ``` Note the contents of `correct.bat`: ```bat @echo off setlocal set PATH=%PATH%;%~dp0subdir testscript ``` and `broken.bat` is identical, but it just encloses the invocation of `testscript` in quotes: ```bat @echo off setlocal set PATH=%PATH%;%~dp0subdir "testscript" ``` which should not have any functional difference, yet the behavior of the scripts differ.
Author
Owner

@zadjii-msft commented on GitHub (Apr 20, 2023):

Wait, just to confirm - does this repro the same way in both conhost (the vintage console window), and the Windows Terminal/?

If so, I'd be inclined to say that this is just the way cmd.exe works...

@zadjii-msft commented on GitHub (Apr 20, 2023): Wait, just to confirm - does this repro the same way in both `conhost` (the vintage console window), and the Windows Terminal/? If so, I'd be inclined to say that this is just the way `cmd.exe` works...
Author
Owner

@juj commented on GitHub (Apr 20, 2023):

Wait, just to confirm - does this repro the same way in both conhost (the vintage console window), and the Windows Terminal/?

Yes

If so, I'd be inclined to say that this is just the way cmd.exe works...

I don't know if Windows Terminal developers would be the same as cmd.exe developers, but if this is not the right place, would you be able to escalate the issue to the relevant developers of cmd.exe inside Microsoft?

It does not seem that this should be shrugged off as a case of "broken by design".

@juj commented on GitHub (Apr 20, 2023): > Wait, just to confirm - does this repro the same way in both conhost (the vintage console window), and the Windows Terminal/? Yes > If so, I'd be inclined to say that this is just the way cmd.exe works... I don't know if Windows Terminal developers would be the same as cmd.exe developers, but if this is not the right place, would you be able to escalate the issue to the relevant developers of cmd.exe inside Microsoft? It does not seem that this should be shrugged off as a case of "broken by design".
Author
Owner

@zadjii-msft commented on GitHub (Apr 20, 2023):

Oh I'm so sorry to be the person that lets you know, but yea, cmd.exe isn't actively maintained anymore (beyond security patches). Yea, the Terminal team does own cmd.exe, but we're not gonna be making any changes to it anytime soon. You can read some of our reasoning here: Why do we avoid changing CMD.exe?. It's literally never worked for us without secretly breaking someone else.

If you'd like a modern shell, PowerShell is really quite good. Yori and Clink are some other team favorites that are more cmd-like.

Sorry 😕

@zadjii-msft commented on GitHub (Apr 20, 2023): Oh I'm so sorry to be the person that lets you know, but yea, cmd.exe isn't actively maintained anymore (beyond security patches). Yea, the Terminal team does own `cmd.exe`, but we're not gonna be making any changes to it anytime soon. You can read some of our reasoning here: [Why do we avoid changing CMD.exe?](https://github.com/microsoft/terminal/blob/main/doc/Niksa.md#cmd). It's literally never worked for us without secretly breaking someone else. If you'd like a modern shell, PowerShell is really quite good. Yori and Clink are some other team favorites that are more cmd-like. Sorry 😕
Author
Owner

@SilvoWeiss commented on GitHub (Jun 2, 2023):

When calling TestScript why are you not using full filename with included extension. Calling both TestScript.bat and "TestScript.bat" works exactly the same as expected.

@SilvoWeiss commented on GitHub (Jun 2, 2023): When calling `TestScript` why are you not using full filename with included extension. Calling both `TestScript.bat` and `"TestScript.bat"` works exactly the same as expected.
Author
Owner

@sbc100 commented on GitHub (Sep 29, 2025):

When calling TestScript why are you not using full filename with included extension. Calling both TestScript.bat and "TestScript.bat" works exactly the same as expected.

In this case the script is our compiler emcc or em++. Folks should be free to call that without that extension I would hope. Windows has the builtin feature of automatically addeding that extension right? That way the actual entry point type (.bat vs .com vs .exe) could be an implementation detail that we could change in the future (for example we are looking at using .exe, due to all the strange issues like this that using .bat files seems to entail: see https://github.com/emscripten-core/emscripten/pull/24858) rather than something that users need to be aware of.

Not including that extension also means that compiler commands look the same on all platforms (we don't use any extension on macOS/Linux).

@sbc100 commented on GitHub (Sep 29, 2025): > When calling `TestScript` why are you not using full filename with included extension. Calling both `TestScript.bat` and `"TestScript.bat"` works exactly the same as expected. In this case the script is our compiler `emcc` or `em++`. Folks should be free to call that without that extension I would hope. Windows has the builtin feature of automatically addeding that extension right? That way the actual entry point type (`.bat` vs `.com` vs `.exe`) could be an implementation detail that we could change in the future (for example we are looking at using `.exe`, due to all the strange issues like this that using `.bat` files seems to entail: see https://github.com/emscripten-core/emscripten/pull/24858) rather than something that users need to be aware of. Not including that extension also means that compiler commands look the same on all platforms (we don't use any extension on macOS/Linux).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#19747