Checking if command extensions are available

Microsoft Windows
Post Reply
User avatar
MigrationUser
Posts: 336
Joined: 2021-Jul-12, 1:37 pm
Contact:

Checking if command extensions are available

Post by MigrationUser »

02 Mar 2014 03:01
agent009

The SETLOCAL page proposes the following method for checking if command extensions are available:

Code: Select all

VERIFY errors 2>nul
SETLOCAL ENABLEEXTENSIONS
IF ERRORLEVEL 1 echo Unable to enable extensions
However this method has two problems:

1) Using SETLOCAL may not be desired if the script needs to modify environment variables of the calling shell session or script.
2) This method will fail if someone tries running your script in 9x/DOS command.com, which will fail to execute SETLOCAL and set ERRORLEVEL to 0 (at least this is what happened when I tried this code in command.com on XP).

Here is another method which relies on the fact that CALL :LABEL syntax only works when command extensions are available:

Code: Select all

verify>NUL
call :EXTTEST 2>NUL
if not ErrorLevel 1 goto EXTERROR

echo:Command extensions v2 or later are available.
exit /b 0

:EXTTEST
if CmdExtVersion 2 exit /b 1
goto :EOF

:EXTERROR
echo:This script requires command extensions v2 or later!>&2
verify error 2>NUL
:: this should be the last line of the file, so that script can exit when command extensions are not available
The above code works under cmd.exe on both my 64bit Win7 notebook and XP box and under command.com on XP box - the script jumps to :EXTERROR label if executed using cmd /y /c and command /c.

If you do not want to require command extensions v2, :EXTTEST subroutine may be modified as following:

Code: Select all

:EXTTEST
verify error 2>NUL
goto :EOF
----------------------------

#2 02 Mar 2014 12:36
foxidrive
agent009 wrote:

Here is another method which relies on the fact that CALL :LABEL syntax only works when command extensions are available:
What command would you use to disable the "CALL :LABEL" feature, which is a default feature of the CMD prompt?

Last edited by foxidrive (02 Mar 2014 12:37)

----------------------------

#3 03 Mar 2014 21:29
agent009

cmd /y /c whatever.bat

- or -

cmd /e:off /c whatever.bat

- or -

command /c whatever.bat

The latter uses legacy 16bit command.com, which is available on most 32bit versions of Windows, but not present on 64bit versions.

----------------------------

#4 03 Mar 2014 23:00
foxidrive

Thanks, yes they all do it.

----------------------------

#5 04 Mar 2014 22:23
Simon Sheppard

Good points above, I've added a link back to this thread from the SETLOCAL page

----------------------------

#6 06 Mar 2014 00:13
agent009


Here's an improved version which does not cause command.com to print 'bad command or file name' to StdErr:

Code: Select all

@echo off
verify>NUL
if ErrorLevel 1 goto NOCMDEXT
call:ECHO>NUL
if ErrorLevel 2 goto CMDEXTV2
if ErrorLevel 1 goto CMDEXTV1
goto NOCMDEXT

:ECHO
if CmdExtVersion 2 exit /b %CMDEXTVERSION%
verify error 2>NUL
goto :EOF

:CMDEXTV2
echo:Command extensions v2 or later available
exit /b 0

:CMDEXTV1
echo:Command extensions v1 available
goto :EOF

:NOCMDEXT
echo:Command extensions not available
:: END OF FILE
I've discovered a trick with CALL:ECHO (without spaces), which behaves differently depending command interpreter used and command extensions status:
- under cmd.exe with command extensions enabled, this simply calls :ECHO label
- under cmd.exe with command extensions disabled, any 'CALL :<LABEL>' statements seem to be silently ignored except that they reset ErrorLevel to 0 (at least, this is the behavior I observed under WinXP and Win7)
- for command.com, this appears to be exactly equivalent to CALL ECHO: (which outputs blank line to StdOut).

There is one problem with command.com, however - CALL:ECHO won't set ErrorLevel and there is no way to reliably set it to any known value without resorting to external commands. On the other hand, VERIFY command executed without parameters is known to reset ErrorLevel to 0 in all versions of cmd.exe - so, if ErrorLevel stays non-zero after VERIFY, we can be fairly certain that our script is NOT being executed by cmd.exe and skip the following command extensions check.

In the above example, if ErrorLevel <X> ... statement effectively becomes equivalent to if CmdExtVersion <X> ... after executing CALL:ECHO>NUL line, except that it won't cause errors under command.com

----------------------------

#7 06 Mar 2014 02:04
agent009

Now... here is an even more interesting trick I found for detecting if the script is not running under cmd.exe. It appears that for command.com, CALL:GOTO LABEL is the same thing as CALL GOTO:LABEL which is essentially the same thing as GOTO LABEL. So, this line will send command.com to label :LABEL while cmd.exe will try to call label :GOTO with parameter "LABEL" (or simply reset ErrorLevel to 0 and be done with it if command extensions are disabled). So, here is how our script could look like:

Code: Select all

call:GOTO DOS
if ErrorLevel 2 goto CMDEXTV2
if ErrorLevel 1 goto CMDEXTV1
goto NOCMDEXT

:GOTO
set "CMDEXTVERSION="
if CmdExtVersion 2 exit /b %CMDEXTVERSION%
verify error 2>NUL
goto :EOF

:CMDEXTV2
echo:Command extensions v2 or later available
exit /b 0

:CMDEXTV1
echo:Command extensions v1 available
goto :EOF

:NOCMDEXT
echo:Command extensions not available
goto :EOF

:DOS
echo:DOS mode

:EOF
:: END OF FILE
Resetting dynamic variable CMDEXTVERSION before using it is also a good idea to make sure we get 'real' OS-assigned value in case it was explicitly set to something else by user or parent process.

On a side note, here is a trick to prevent command.com from complaining about SETLOCAL statements:

echo:>NUL &setlocal EnableExtensions DisableDelayedExpansion 2>NUL

Since command.com does not know anything about multi-command statements and multiple redirects, it simply ignores everything after the first redirect. Space between >NUL and & is required - otherwise command.com will write output to a file called NUL&SETL. Also, 2>NUL redirect at the end should suppress possible error messages about unknown parameters if script is executed under NT4 or OS/2.

----------------------------

#8 06 Mar 2014 19:57
agent009

UPDATE! I've ran a few tests with different (non-MS) flavors of command.com under DosBox and, unfortunately, the CALL:GOTO trick only seems to work with the version that is shipped with 32bit XP. This might be a behavior in all Microsoft implementations or it could simply be a bug/feature specific to the particular version. However, I found a much easier way to check for command extensions:

Code: Select all

@echo off
if "~x0"=="%~x0" goto NOCMDEXT 
if "%%~x0"=="%~x0" goto NOCMDEXT
if CmdExtVersion 2 goto CMDEXTV2
goto CMDEXTV1

:CMDEXTV1
echo Command extensions v1 available
goto :EOF

:CMDEXTV2
echo Command extensions v2 or later available
exit /b 0

:NOCMDEXT
echo Command extensions not available
:: END OF FILE
This method relies on the fact that under cmd.exe with command extensions enabled, %~x0 sequence will always expand to file extension of the script itself, which may be either a string starting with a . (dot) or an empty string. Without command extensions, this sequence will be interpreted as a 'stray' % (which may be either discarded or retained as literal character depending on command interpreter implementation, hence two checks) followed by literal '~x0' string. This method should work reliably for any flavor of cmd.exe/command.com without causing any errors.

----------------------------

#9 07 Mar 2014 16:03
foxidrive

Command extensions are such useful little critters that it's hard for me to see a reason why you would disable them.

It the spirit of discussion I would comment that Win9x is a legacy OS and it's batch code has little similarity to the enhanced NT series code, so I'm unsure why people would run NT series batch code on a Win9x machine.
Maybe I misunderstood your comment re 9x and dos.

I cut my teeth on MSDOS and was a died in the wool DOS man, and batch scripting was my hobby and passion - but these days you see very little call for MSDOS solutions, or Win9x solutions.

----------------------------

#10 07 Mar 2014 21:45
agent009

Well, I agree that Win9x systems are all but obsolete nowadays and I also see little reason why one would disable command extensions or use legacy shells on NT-based systems. Yet, an option to permanently disable command extensions is still there and legacy (MS-DOS 5) command.com is still included in all recent 32-bit versions of Windows (not to mention a number of 3rd party DOS-based shells available out there), so we cannot rule out the possibility that the user or system administrator would make use of those options for one reason or another. The problem is that many CMD scripts will readily run under legacy shells, but features not supported by classic DOS syntax could result in (potentially unsafe) undefined behavior - so, if you are making batch scripts for public domain, it does not hurt to add a few lines of code to prevent this.

----------------------------

#11 08 Mar 2014 06:30
foxidrive
agent009 wrote:

and legacy (MS-DOS 5) command.com is still included in all recent 32-bit versions of Windows
That's not so. The command.com in NT series passes off commands to CMD.EXE to run, but has some variables and behaviour set to make legacy programs think it is command.com
agent009 wrote:

The problem is that many CMD scripts will readily run under legacy shells, but features not supported by classic DOS syntax could result in (potentially unsafe) undefined behavior - so, if you are making batch scripts for public domain, it does not hurt to add a few lines of code to prevent this.

One aspect I see is that 99.9% of scripts for support questions here and there will NOT run in MSDOS or Win9x command.com, and it would be easier to detect the OS version and just print a message saying that "Your OS is unsupported" but that would have to be added to pretty much *every* script, as would your suggestion.

To extend this, some batch scripts that will run in XP and later will not run in NT, merely because in some elements the sentence case is significant in NT.
You also get extra commands in later versions of Windows - where do you draw the line about testing for previous versions of the OS?

Ideally the script writer could add :: tested in Win version xxx at the top of each script, but the horse has long since bolted. smile

----------------------------

#12 08 Mar 2014 11:17
agent009
foxidrive wrote:

That's not so. The command.com in NT series passes off commands to CMD.EXE to run, but has some variables and behaviour set to make legacy programs think it is command.com
Aye it may be using cmd.exe internally for executing commands... but it parses command lines on its own and pretty much emulates the behavior of DOS command.com at that. Try running the following script using 'cmd /x /c', 'cmd /y /c' and 'command /c' and see the difference wink

Code: Select all

@echo off
>NUL echo & goto WINNT
goto DOS

:WINNT
echo Running Windows NT
if "~x0"=="%~x0" goto NOCMDEXT
if "%%~x0"=="%~x0" goto NOCMDEXT
set "CMDEXTVERSION="
if "%CMDEXTVERSION%"=="" set "CMDEXTVERSION=1"
echo Command extensions available (v%CMDEXTVERSION%)
goto :EOF

:DOS
echo Running DOS
:NOCMDEXT
echo Command extensions not available
foxidrive wrote:

One aspect I see is that 99.9% of scripts for support questions here and there will NOT run in MSDOS or Win9x command.com, and it would be easier to detect the OS version and just print a message saying that "Your OS is unsupported" but that would have to be added to pretty much *every* script, as would your suggestion.
I wouldn't say that detecting OS version in a batch script is exactly 'easy' - the only 'easy' check is if "%OS%"=="Windows_NT" .... More precise OS version check would involve parsing 'ver' output or reading registry with external commands.

Besides it's not the OS version, but the command interpreter version that matters... when your script is executed by command.com on 32bit NT systems, values of all environment variables and registry keys will be the same as if it was executed by cmd.exe.
foxidrive wrote:

To extend this, some batch scripts that will run in XP and later will not run in NT, merely because in some elements the sentence case is significant in NT. You also get extra commands in later versions of Windows - where do you draw the line about testing for previous versions of the OS?

Distinguishing NT4 from 2k and later with command extensions enabled is simply a matter of doing if CmdExtVersion 2 ... check or checking whether CMDEXTVERSION environment variable is dynamic (like in the above example)... and there is no real difference with command extensions disabled. AFAIK, there have been no significant changes to cmd.exe since Win2k and, since MS has all but abandoned development of CMD shell in favor of PowerShell, it is unlikely that we'll ever see any new internal commands or major changes to existing ones. As for detecting availability of external commands, you just try to run the command and do if ErrorLevel 9009 ... check afterward.

----------------------------

#13 24 Jan 2016 14:32
Gentil
agent009 wrote:

UPDATE! I've ran a few tests with different (non-MS) flavors of command.com under DosBox and, unfortunately, the CALL:GOTO trick only seems to work with the version that is shipped with 32bit XP. This might be a behavior in all Microsoft implementations or it could simply be a bug/feature specific to the particular version. However, I found a much easier way to check for command extensions:

Code: Select all

@echo off
    if "~x0"=="%~x0" goto NOCMDEXT 
    if "%%~x0"=="%~x0" goto NOCMDEXT
    if CmdExtVersion 2 goto CMDEXTV2
    goto CMDEXTV1

    :CMDEXTV1
    echo Command extensions v1 available
    goto :EOF

    :CMDEXTV2
    echo Command extensions v2 or later available
    exit /b 0

    :NOCMDEXT
    echo Command extensions not available
    :: END OF FILE
This method relies on the fact that under cmd.exe with command extensions enabled, %~x0 sequence will always expand to file extension of the script itself, which may be either a string starting with a . (dot) or an empty string. Without command extensions, this sequence will be interpreted as a 'stray' % (which may be either discarded or retained as literal character depending on command interpreter implementation, hence two checks) followed by literal '~x0' string. This method should work reliably for any flavor of cmd.exe/command.com without causing any errors.
Awesome, this worked for me too agent009. Thanks for posting your code.
Post Reply