batch file to check if Python is installed - windows

I've written a batch script that checks if Python is installed, if it's not installed - it initiates the Python installer contained in the same folder as itself.
I'm using the following code:
reg query "hkcu\software\Python 2.6"
if ERRORLEVEL 1 GOTO NOPYTHON
:NOPYTHON
ActivePython-2.6.4.8-win32-x86.msi
reg query "hklm\SOFTWARE\ActiveState\ActivePerl\" 1>>Output_%date%_%time%.log 2>&1
if ERRORLEVEL 1 GOTO NOPERL
reg query "hklm\SOFTWARE\Gtk+"
if ERRORLEVEL 1 GOTO NOPYGTK
:NOPERL
ActivePerl-5.10.1.1006-MSWin32-x86-291086.msi 1>>Output_%date%_%time%.log 2>&1
:NOPYGTK
pygtk_windows_installer.exe
But in some cases the installer starts up even if Python is installed. What is the problem here?

For those who just want a simple check if Python is installed and can be executed without going into the registy, in your batch file:
:: Check for Python Installation
python --version 2>NUL
if errorlevel 1 goto errorNoPython
:: Reaching here means Python is installed.
:: Execute stuff...
:: Once done, exit the batch file -- skips executing the errorNoPython section
goto:eof
:errorNoPython
echo.
echo Error^: Python not installed

Your code doesn't branch after the registry query is done. No matter what the first if ERRORLEVEL evaluates to, the next step is always to step into the :NOPYTHON label.
Ed: Here is an example how to make it work. The idea is to add another goto statement which will skip the :NOPYTHON label if desired.
reg query "hkcu\software\Python 2.6"
if ERRORLEVEL 1 GOTO NOPYTHON
goto :HASPYTHON
:NOPYTHON
ActivePython-2.6.4.8-win32-x86.msi
:HASPYTHON
reg query "hklm\SOFTWARE\ActiveState\ActivePerl\" 1>>Output_%date%_%time%.log 2>&1

Here's my method.
The python -V command will return the version number and the find command with the aid of the /v switch will search for the omittance of Python and there's also a normal one without that switch.
#echo off & title %~nx0 & color 5F
goto :DOES_PYTHON_EXIST
:DOES_PYTHON_EXIST
python -V | find /v "Python" >NUL 2>NUL && (goto :PYTHON_DOES_NOT_EXIST)
python -V | find "Python" >NUL 2>NUL && (goto :PYTHON_DOES_EXIST)
goto :EOF
:PYTHON_DOES_NOT_EXIST
echo Python is not installed on your system.
echo Now opeing the download URL.
start "" "https://www.python.org/downloads/windows/"
goto :EOF
:PYTHON_DOES_EXIST
:: This will retrieve Python 3.8.0 for example.
for /f "delims=" %%V in ('python -V') do #set ver=%%V
echo Congrats, %ver% is installed...
goto :EOF

Related

In a CMD batch file, can I determine if it was run from powershell?

I have a Windows batch file whose purpose is to set some environment variables, e.g.
=== MyFile.cmd ===
SET MyEnvVariable=MyValue
Users can run this prior to doing work that needs the environment variable, e.g.:
C:\> MyFile.cmd
C:\> echo "%MyEnvVariable%" <-- outputs "MyValue"
C:\> ... do work that needs the environment variable
This is roughly equivalent to the "Developer command prompt" shortcuts installed by Visual Studio, which set environment variables needed to run VS utilities.
However if a user happens to have a Powershell prompt open, the environment variable is of course not propagated back to Powershell:
PS C:\> MyFile.cmd
PS C:\> Write-Output "${env:MyEnvVariable}" # Outputs an empty string
This can be confusing for users who switch between CMD and PowerShell.
Is there a way I can detect in my batch file MyFile.cmd that it was called from PowerShell, so that I can, for example, display a warning to the user? This needs to be done without any 3rd party utility.
Your own answer is robust and while it is generally slow due to needing to run a PowerShell process, it can be made significantly faster by optimizing the PowerShell command used to determine the calling shell:
#echo off
setlocal
CALL :GETPARENT PARENT
IF /I "%PARENT%" == "powershell" GOTO :ISPOWERSHELL
IF /I "%PARENT%" == "pwsh" GOTO :ISPOWERSHELL
endlocal
echo Not running from Powershell
SET MyEnvVariable=MyValue
GOTO :EOF
:GETPARENT
SET "PSCMD=$ppid=$pid;while($i++ -lt 3 -and ($ppid=(Get-CimInstance Win32_Process -Filter ('ProcessID='+$ppid)).ParentProcessId)) {}; (Get-Process -EA Ignore -ID $ppid).Name"
for /f "tokens=*" %%i in ('powershell -noprofile -command "%PSCMD%"') do SET %1=%%i
GOTO :EOF
:ISPOWERSHELL
echo. >&2
echo ERROR: This batch file may not be run from a PowerShell prompt >&2
echo. >&2
exit /b 1
On my machine, this runs about 3 - 4 times faster (YMMV) - but still takes almost 1 second.
Note that I've added a check for process name pwsh as well, so as to make the solution work with PowerShell Core too.
Much faster alternative - though less robust:
The solution below relies on the following assumption, which is true in a default installation:
Only a system environment variable named PSModulePath is persistently defined in the registry (not also a user-specific one).
The solution relies on detecting the presence of a user-specific path in PSModulePath, which PowerShell automatically adds when it starts.
#echo off
echo %PSModulePath% | findstr %USERPROFILE% >NUL
IF %ERRORLEVEL% EQU 0 goto :ISPOWERSHELL
echo Not running from Powershell
SET MyEnvVariable=MyValue
GOTO :EOF
:ISPOWERSHELL
echo. >&2
echo ERROR: This batch file may not be run from a PowerShell prompt >&2
echo. >&2
exit /b 1
Alternative approach for launching a new cmd.exe console window on demand:
Building on the previous approach, the following variant simply re-invokes the batch file in a new cmd.exe window on detecting that it is being run from PowerShell.
This is not only more convenient for the user, it also mitigates the problem of the solutions above yielding false positives: When run from an interactive cmd.exe session that was launched from PowerShell, the above solutions will refuse to run, even though they should, as PetSerAl points out.
While the solution below also doesn't detect this case per se, it still opens a useable - albeit new - window with the environment variables set.
#echo off
REM # Unless already being reinvoked via cmd.exe, see if the batch
REM # file is being run from PowerShell.
IF NOT %1.==_isNew. echo %PSModulePath% | findstr %USERPROFILE% >NUL
REM # If so, RE-INVOKE this batch file in a NEW cmd.exe console WINDOW.
IF NOT %1.==_isNew. IF %ERRORLEVEL% EQU 0 start "With Environment" "%~f0" _isNew & goto :EOF
echo Running from cmd.exe, setting environment variables...
REM # Set environment variables.
SET MyEnvVariable=MyValue
REM # If the batch file had to be reinvoked because it was run from PowerShell,
REM # but you want the user to retain the PowerShell experience,
REM # restart PowerShell now, after definining the env. variables.
IF %1.==_isNew. powershell.exe
GOTO :EOF
After setting all environment variables, note how the last IF statement, also re-invokes PowerShell, but in the same new window, based on the assumption that the calling user prefers working in PowerShell.
The new PowerShell session will then see newly defined environment variables, though note that you'll need two successive exit calls to close the window.
As Joe Cocker used to say "I get by with a little help from my friends".
In this case from Lieven Keersmaekers, whose comments led me to the following solution:
#echo off
setlocal
CALL :GETPARENT PARENT
IF /I "%PARENT%" == "powershell.exe" GOTO :ISPOWERSHELL
endlocal
echo Not running from Powershell
SET MyEnvVariable=MyValue
GOTO :EOF
:GETPARENT
SET CMD=$processes = gwmi win32_process; $me = $processes ^| where {$_.ProcessId -eq $pid}; $parent = $processes ^| where {$_.ProcessId -eq $me.ParentProcessId} ; $grandParent = $processes ^| where {$_.ProcessId -eq $parent.ParentProcessId}; $greatGrandParent = $processes ^| where {$_.ProcessId -eq $grandParent.ParentProcessId}; Write-Output $greatGrandParent.Name
for /f "tokens=*" %%i in ('powershell -command "%CMD%"') do SET %1=%%i
GOTO :EOF
:ISPOWERSHELL
echo.
echo ERROR: This batch file may not be run from a PowerShell prompt
echo.
cmd /c "exit 1"
GOTO :EOF
I did something like this for Chocolatey's RefreshEnv.cmd script: Make refreshenv.bat error if powershell.exe is being used.
My solution didn't end being used, for unrelated reasons, but it's available in this repo: beatcracker/detect-batch-subshell. Here is copy of it, just in case.
Script that will only run if called directly from interactive command processor session
Script will detect if it's run from non-interactive session (cmd.exe /c detect-batch-subshell.cmd) and show approriate error message.
Non-interactive shell includes PowerShell/PowerShell ISE, Explorer, etc... Basically anything that will try to execute script by running it in the separate cmd.exe instance.
Hovewer, dropping into the cmd.exe session from PowerShell/PowerShell ISE and executing script there will work.
Dependencies
wmic.exe - comes with Windows XP Professional and up.
Example:
Open cmd.exe
Type detect-batch-subshell.cmd
Output:
> detect-batch-subshell.cmd
Running interactively in cmd.exe session.
Example:
Open powershell.exe
Type detect-batch-subshell.cmd
Output:
PS > detect-batch-subshell.cmd
detect-batch-subshell.cmd only works if run directly from cmd.exe!
Code
detect-batch-subshell.cmd
#echo off
setlocal EnableDelayedExpansion
:: Dequote path to command processor and this script path
set ScriptPath=%~0
set CmdPath=%COMSPEC:"=%
:: Get command processor filename and filename with extension
for %%c in (!CmdPath!) do (
set CmdExeName=%%~nxc
set CmdName=%%~nc
)
:: Get this process' PID
:: Adapted from: http://www.dostips.com/forum/viewtopic.php?p=22675#p22675
set "uid="
for /l %%i in (1 1 128) do (
set /a "bit=!random!&1"
set "uid=!uid!!bit!"
)
for /f "tokens=2 delims==" %%i in (
'wmic Process WHERE "Name='!CmdExeName!' AND CommandLine LIKE '%%!uid!%%'" GET ParentProcessID /value'
) do (
rem Get commandline of parent
for /f "tokens=1,2,*" %%j in (
'wmic Process WHERE "Handle='%%i'" GET CommandLine /value'
) do (
rem Strip extra CR's from wmic output
rem http://www.dostips.com/forum/viewtopic.php?t=4266
for /f "delims=" %%x in ("%%l") do (
rem Dequote path to batch file, if any (3rd argument)
set ParentScriptPath=%%x
set ParentScriptPath=!ParentScriptPath:"=!
)
rem Get parent process path
for /f "tokens=2 delims==" %%y in ("%%j") do (
rem Dequote parent path
set ParentPath=%%y
set ParentPath=!ParentPath:"=!
rem Handle different invocations: C:\Windows\system32\cmd.exe , cmd.exe , cmd
for %%p in (!CmdPath! !CmdExeName! !CmdName!) do (
if !ParentPath!==%%p set IsCmdParent=1
)
rem Check if we're running in cmd.exe with /c switch and this script path as argument
if !IsCmdParent!==1 if %%k==/c if "!ParentScriptPath!"=="%ScriptPath%" set IsExternal=1
)
)
)
if !IsExternal!==1 (
echo %~nx0 only works if run directly from !CmdExeName!^^!
exit 1
) else (
echo Running interactively in !CmdExeName! session.
)
endlocal
Like the answer from beatcracker I think it would be better to not take assumptions about the external shell that can be used to launch the batch script, for instance, the issue can also arise when running the batch file through the bash shell.
Because it exclusively uses the native facilities of CMD and has no dependency on any external tool or the WMI, the execution time is very fast.
#echo off
call :IsInvokedInternally && (
echo Script is launched from an interactive CMD shell or from another batch script.
) || (
echo Script is invoked by an external App. [PowerShell, BASH, Explorer, CMD /C, ...]
)
exit /b
:IsInvokedInternally
setlocal EnableDelayedExpansion
:: Getting substrings from the special variable CMDCMDLINE,
:: will modify the actual Command Line value of the CMD Process!
:: So it should be saved in to another variable before applying substring operations.
:: Removing consecutive double quotes eg. %systemRoot%\system32\cmd.exe /c ""script.bat""
set "SavedCmdLine=!cmdcmdline!"
set "SavedCmdLine=!SavedCmdLine:""="!"
set /a "DoLoop=1, IsExternal=0"
set "IsCommand="
for %%A in (!SavedCmdLine!) do if defined DoLoop (
if not defined IsCommand (
REM Searching for /C switch, everything after that, is CMD commands
if /i "%%A"=="/C" (
set "IsCommand=1"
) else if /i "%%A"=="/K" (
REM Invoking the script with /K switch creates an interactive CMD session
REM So it will be considered an internal invocatoin
set "DoLoop="
)
) else (
REM Only check the first command token to see if it references this script
set "DoLoop="
REM Turning delayed expansion off to prevent corruption of file paths
REM which may contain the Exclamation Point (!)
REM It is safe to do a SETLOCAL here because the we have disabled the Loop,
REM and the routine will be terminated afterwards.
setlocal DisableDelayedExpansion
if /i "%%~fA"=="%~f0" (
set "IsExternal=1"
) else if /i "%%~fA"=="%~dpn0" (
set "IsExternal=1"
)
)
)
:: A non-zero ErrorLevel means the script is not launched from within CMD.
exit /b %IsExternal%
It checks the command line that used to launch the CMD shell to tell if script have been launched from within CMD or by an external app using the command line signature /C script.bat which is typically used by non CMD shells to launch batch scripts.
If for any reason the external launch detection needs to bypasses, for instance when manually launching the script with additional commands to take advantage the defined variables, it can done by prepending # to the path of the script in CMD command line:
cmd /c #MyScript.bat & AdditionalCommands

I am using a Batch File to search a list of functions for an exact match, wondering if its possible to do a keyword search instead

I am using a Batch File to search a list of functions for an exact match, wondering if its possible to do a key word search, so right now i have to type "open cmd"
(variations on spacing and capital letters are accounted for)
Id like to switch it over to a system that can look for "cmd" and perform the action so "hey, open cmd please" would yield the same result as the old system
Old system:
setlocal
:: /STARTUP
set speech=start scripts\nircmd.exe speak text
cls
:begin
set TALK=TypeSomething
SET /P TALK=
set TALK=%TALK:?=%
call :%TALK: =% 2>NUL
if %errorlevel% equ 0 goto begin
exit /B 0
:unknown
echo Old function no longer supported
:opencmd
:BOSopencmd
:cmd
echo Command Prompt has now been opened in a new window, sir.
%speech% "Command Prompt has now been opened in a new window, sir."
start scripts\cmd.bat
exit /B 0
It is based of a chat bot i tried to make in middle school so the %speech% is not an important item, i can add that and the echo later. I just need a system that works like the old one if possible. The other i can have any number of functions with
:cmd
start cmd
Exit /B 0
or
:reddit
start http://www.reddit.com/
exit /B 0
at these need to be able to stack. I can transition to having scripts for each function in a separate batch files if needed. Ive tried trying findstr but it wasn't giving the desired results. Ive exhausted my knowledge on what i might be able to do but I've come up short lol, If you are having trouble understanding what i'm asking don't hesitate to let me know
I learn by taking things apart so partial code is appreciated but will not be much help until after I've figured out what does what .
Here's a sample of how you might approach it using ECHO, FINDSTR, and CALL (This is a modified example from the original per your request to be able to process multiple keywords):
#echo off
set TST_FNDFLG=FALSE
set TST_USRANS=
set /P TST_USRANS=Enter keywords:
if "%TST_USRANS%" == "" goto ENDIT
echo %TST_USRANS% | findstr /i "CMD" >NUL 2>&1
if ERRORLEVEL 1 goto TRYRED
call :DOCMD
:TRYRED
echo %TST_USRANS% | findstr /i "REDDIT" >NUL 2>&1
if ERRORLEVEL 1 goto TRYGOO
call :DORED
:TRYGOO
echo %TST_USRANS% | findstr /i "GOOGLE" >NUL 2>&1
if ERRORLEVEL 1 goto TRYEND
call :DOGOO
goto TRYEND
:DOCMD
if [%TST_FNDFLG%] == [FALSE] echo.
echo CMD was found in "%TST_USRANS%"
set TST_FNDFLG=TRUE
goto :EOF
:DORED
if [%TST_FNDFLG%] == [FALSE] echo.
echo REDDIT was found in "%TST_USRANS%"
set TST_FNDFLG=TRUE
goto :EOF
:DOGOO
if [%TST_FNDFLG%] == [FALSE] echo.
echo GOOGLE was found in "%TST_USRANS%"
set TST_FNDFLG=TRUE
goto :EOF
:TRYEND
echo.
if [%TST_FNDFLG%] == [TRUE] echo No more keywords found
if [%TST_FNDFLG%] == [FALSE] echo Did not find any known keywords
goto ENDIT
:ENDIT
echo.
set TST_USRANS=
set TST_FNDFLG=

Batch files can not supress "terminate job"

Im trying to open a 2nd batch file and detect if it normally exited or closed by a user (ctrl+c or x or window termiate etc..)
so Im using this following example by Batch run script when closed
#Echo off
set errorlevel=1
start /w %comspec% /c "mode 70,10&title Folder Confirmation Box&color 1e&echo.&echo. Else the close window&pause>NUL&exit 12345"
echo %errorlevel%
pause
Im trying to keep 1st batch waiting (/W) since I will check for errorlevel later on
But after closing the 2nd batch file I get an error like ^cterminate batch job (Y/N)?
I tried the suggestion over https://superuser.com/questions/35698/how-to-supress-terminate-batch-job-y-n-confirmation
with the script
rem Bypass "Terminate Batch Job" prompt.
if "%~2"=="-FIXED_CTRL_C" (
REM Remove the -FIXED_CTRL_C parameter
SHIFT
) ELSE (
REM Run the batch with <NUL and -FIXED_CTRL_C
CALL <NUL %1 -FIXED_CTRL_C %*
GOTO :EOF
)
That works quite fine
So is there a way of starting from same batch file and avoiding the terminating?
Or do I have to create a new batch from same batch and call it?
(I don't want them to see the file aswell)
Do not assign values to a volatile environment variable like errorlevel using set command. Doing that causes it becomes unvolatile in current context.
Always use title in START "title" [/D path] [options] "command" [parameters].
start "" /W cmd /c "anycommand&exit /B 12345" always returns 12345 exit code. It's because all the cmd line with & concatenated commands is prepared in parsing time (the same as a command block enclosed in parentheses) and then run entirely, indivisibly. Omit &exit /B 12345 to get proper exit code from anycommand, or replace it with something like start "" /W cmd /c "anycommand&&exit /B 12345||exit /B 54321" to get only success/failure indication.
Next code snippet could help:
#ECHO OFF
SETLOCAL enableextensions
set "_command=2nd_batch_file.bat"
:: for debugging purposes
set "_command=TIMEOUT /T 10 /NOBREAK"
:: raise errorlevel 9009 as a valid file name can't contain a vertical line
invalid^|command>nul 2>&1
echo before %errorlevel%
start "" /w %comspec% /C "mode 70,10&title Folder Confirmation Box&color 1e&echo(&echo( Else the close window&%_command%"
echo after %errorlevel%
Output shows sample %_command% exit codes: 0 or 1 if came to an end properly but -1073741510 if terminated forceably by Ctrl+C or Ctrl+Break or red ×
==>D:\bat\SO\31866091.bat<nul
before 9009
after 0
==>D:\bat\SO\31866091.bat<nul
before 9009
after 1
==>D:\bat\SO\31866091.bat<nul
before 9009
^CTerminate batch job (Y/N)?
after -1073741510
==>
This works for me:
call :runme start /w "Child Process" %comspec% /c "child.bat & exit 12345" <NUL >NUL 2>NUL
echo %ERRORLEVEL%
goto :eof
:runme
%*
goto :eof
The idea is to call a subroutine in the current script rather than calling out to an external script. You can still redirect input and output for a subroutine call.

Batch File Help: determining version to decide action

As our office upgrades to Window 7, I have been tasked to update the loginscript to work with Windows 7. The creators of said script are long gone, and I am not a batch file expert.
What I am trying to do is determine the OS. As I do some network administration duties, I need to be able to log on to a server without running the login script whereas I will need to the login script to run if I log into a Windows XP or Windows 7 computer.
I found I couldn't use the VER command as Windows 7 and Windows Server 2008 return the exact same results.
This is what I have:
if exist %loginscriptdir%\sysinfo.txt goto setver
if not exist %loginscriptdir%\sysinfo.txt wmic os get name /value > %loginscriptdir%\sysinfo.txt
type %loginscriptdir%\sysinfo.txt > %loginscriptdir%\sysinfo1.txt
:setver
set WinVer=Unknown
set errorlevel=0
If %WinVer% == "Unknown" (
findstr /c:"Windows XP Professional" %loginscriptdir%\sysinfo1.txt
if %errorlevel%==1 set WinVer=XP
) else (
findstr /c:"Windows 7 Enterprise" %loginscriptdir%\sysinfo1.txt
if %errorlevel%==1 set WinVer=Win7
)
set result=false
if %WinVer% == "XP" set result=true
if %WinVer% == "Win7" set result=true
if "%result%" == "false" (
goto skipicon1
Throughout the script, I wrote in breaks to find the values. Example:
REM -----
ECHO "%WinVer%"
ECHO "%result%"
ECHO "%errorlevel%"
ECHO Press any key to continue 4.
pause>null
REM -----
The fourth break comes at the end of the script I pasted above. These are the results:
"Unknown"
"false"
"0"
Press any key to continue 4.
Here you go. This is the best way I've found to get the OS accurately from win2kpro-winserver2k10. It also tells if it's 32/64 bit and what sp is installed but you don't have to. Just check %cap% in this example.
#echo off
setlocal
call :GetOS cap bit sp
echo %cap%%bit% (%sp%)
exit /b
:GetOS caption servicepack
setlocal
set arc=%PROCESSOR_ARCHITECTURE%
set key="HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
for /f "tokens=3*" %%a in (
'reg query %key%^|findstr /i ProductName') do set cap=%%a %%b
for /f "tokens=3*" %%a in (
'reg query %key%^|findstr /i CSDVersion') do set sp=%%a %%b
endlocal & set %1=%cap% & set %2=%arc% & set %3=%sp%
exit /b
The issue with the code is the expansion of the %errorlevel% value. Since it is contained within a scope of parentheses, the value will not be updated till after the scope ends. Meaning that %errorlevel% will always equal its value when the scope began. To fix this you would have to use delayed expansion. setlocal enabledelayedexpansion and !errorlevel!. Here is a StackOverflow post about delayed expansion: Enable and Disable Delayed Expansion, what does it do?
You may just want to use the version detection method shown at ss64.com http://ss64.com/nt/ver.html
Here is the example from ss64.com but simplified:
#echo off
setlocal
:: Get windows Version
for /f "tokens=4,5,6 delims=[.] " %%A in ('ver') do (
set "Major=%%~A"
set "Minor=%%~B"
set "Build=%%~C"
)
if "%Major%.%Minor%"=="5.1" goto WinXP
if "%Major%.%Minor%"=="6.1" goto Win7
echo Unsupported Version Detected "%Major%.%Minor%"
goto End
:WinXP
echo Windows XP Detected
goto End
:Win7
echo Windows 7 or Server 2008 Detected
goto End
:End
endlocal
exit /b 0

%~$PATH:1 expansion issue

So I recently stumbled on the (potentially) useful %~$PATH:1 expansion, however I seem to be unable to make it work correctly. I tried to use it to make a cheap Windows version of the which command, however the syntax seems to be defeating me. My batch file looks like this:
#echo off
echo %~$PATH:1
However when I run this with for example
which cmd
all I get as output of "ECHO is off.", which means according to the docs that the %~$PATH:1 didn't find "cmd". What am I doing wrong?
Checking for files with the extensions .exe, .cmd or .bat is not enough. The set of applicable extensions is defined in the environment variable PATHEXT.
Here is my version of a which command that honors the PATHEXT variable upon search:
#echo off
rem Windows equivalent of Unix which command
setlocal enabledelayedexpansion
if "%~1"=="" (
echo Usage: which cmdname
exit /b 1
)
call :findOnPath "%~1"
if not errorlevel 1 exit /b 0
for %%E in (%PATHEXT:;= %) do (
call :findOnPath "%~1%%E"
if not errorlevel 1 exit /b 0
)
echo "%~1" not found on PATH.
exit /b 1
:findOnPath
if not "%~$PATH:1" == "" (
echo "%~$PATH:1"
exit /b 0
)
exit /b 1
Shoot! I just figured it out! I need to use the full "cmd.exe" as a parameter instead of just "cmd". D'oh! ;] So, the complete which.cmd script looks like this:
#echo off
call :checkpath %1
call :checkpath %1.exe
call :checkpath %1.cmd
call :checkpath %1.bat
:checkpath
if "%~$PATH:1" NEQ "" echo %~$PATH:1
Yeah! Finally a which command on Windows! ;]
I have been using this one for a while, it also checks built-in commands

Resources