Is there an equivalent of 'which' on the Windows command line? - windows

As I sometimes have path problems, where one of my own cmd scripts is hidden (shadowed) by another program (earlier on the path), I would like to be able to find the full path to a program on the Windows command line, given just its name.
Is there an equivalent to the UNIX command 'which'?
On UNIX, which command prints the full path of the given command to easily find and repair these shadowing problems.

Windows Server 2003 and later (i.e. anything after Windows XP 32 bit) provide the where.exe program which does some of what which does, though it matches all types of files, not just executable commands. (It does not match built-in shell commands like cd.) It will even accept wildcards, so where nt* finds all files in your %PATH% and current directory whose names start with nt.
Try where /? for help.
Note that Windows PowerShell defines where as an alias for the Where-Object cmdlet, so if you want where.exe, you need to type the full name instead of omitting the .exe extension. Alternatively, you can set an alias for it:
Set-Alias which where.exe
Update: Using Get-Command (alias: gcm) is recommended since it's native to PS and will get all command types: aliases, cmdlets, executables, and functions. Example:
gcm notepad*

While later versions of Windows have a where command, you can also do this with Windows XP by using the environment variable modifiers, as follows:
c:\> for %i in (cmd.exe) do #echo. %~$PATH:i
C:\WINDOWS\system32\cmd.exe
c:\> for %i in (python.exe) do #echo. %~$PATH:i
C:\Python25\python.exe
You don't need any extra tools and it's not limited to PATH since you can substitute any environment variable (in the path format, of course) that you wish to use.
And, if you want one that can handle all the extensions in PATHEXT (as Windows itself does), this one does the trick:
#echo off
setlocal enableextensions enabledelayedexpansion
:: Needs an argument.
if "x%1"=="x" (
echo Usage: which ^<progName^>
goto :end
)
:: First try the unadorned filenmame.
set fullspec=
call :find_it %1
:: Then try all adorned filenames in order.
set mypathext=!pathext!
:loop1
:: Stop if found or out of extensions.
if "x!mypathext!"=="x" goto :loop1end
:: Get the next extension and try it.
for /f "delims=;" %%j in ("!mypathext!") do set myext=%%j
call :find_it %1!myext!
:: Remove the extension (not overly efficient but it works).
:loop2
if not "x!myext!"=="x" (
set myext=!myext:~1!
set mypathext=!mypathext:~1!
goto :loop2
)
if not "x!mypathext!"=="x" set mypathext=!mypathext:~1!
goto :loop1
:loop1end
:end
endlocal
goto :eof
:: Function to find and print a file in the path.
:find_it
for %%i in (%1) do set fullspec=%%~$PATH:i
if not "x!fullspec!"=="x" #echo. !fullspec!
goto :eof
It actually returns all possibilities but you can tweak it quite easily for specific search rules.

Under PowerShell, Get-Command will find executables anywhere in $Env:PATH.
$ Get-Command eventvwr
CommandType Name Definition
----------- ---- ----------
Application eventvwr.exe c:\windows\system32\eventvwr.exe
Application eventvwr.msc c:\windows\system32\eventvwr.msc
And since powershell let's you define aliases, which can be defined like so.
$ sal which gcm # short form of `Set-Alias which Get-Command`
$ which foo
...
PowerShell commands are not just executable files (.exe, .ps1, etc). They can also be cmdlets, functions, aliases, custom executable suffixes set in $Env:PATHEXT, etc. Get-Command is able to find and list all of these commands (quite akin to Bash's type -a foo). This alone makes it better than where.exe, which.exe, etc which are typically limited to finding just executables.
Finding executables using only part of the name
$ gcm *disk*
CommandType Name Version Source
----------- ---- ------- ------
Alias Disable-PhysicalDiskIndication 2.0.0.0 Storage
Alias Enable-PhysicalDiskIndication 2.0.0.0 Storage
Function Add-PhysicalDisk 2.0.0.0 Storage
Function Add-VirtualDiskToMaskingSet 2.0.0.0 Storage
Function Clear-Disk 2.0.0.0 Storage
Cmdlet Get-PmemDisk 1.0.0.0 PersistentMemory
Cmdlet New-PmemDisk 1.0.0.0 PersistentMemory
Cmdlet Remove-PmemDisk 1.0.0.0 PersistentMemory
Application diskmgmt.msc 0.0.0.0 C:\WINDOWS\system32\diskmgmt.msc
Application diskpart.exe 10.0.17... C:\WINDOWS\system32\diskpart.exe
Application diskperf.exe 10.0.17... C:\WINDOWS\system32\diskperf.exe
Application diskraid.exe 10.0.17... C:\WINDOWS\system32\diskraid.exe
...
Finding custom executables
Unlike UNIX, where executables are files with the executable (+x) bit set, executables on windows are files present in one of the directories specified in the $PATH env. variable whose filename suffixes are named in the $PATHEXT env. variable (defaults to .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL).
As Get-Command also honours this env. variable, it can be extended to list custom executables. e.g.
$ $Env:PATHEXT="$Env:PATHEXT;.dll;.ps1;.psm1;.py" # temporary assignment, only for this shell's process
$ gcm user32,kernel32,*WASM*,*http*py
CommandType Name Version Source
----------- ---- ------- ------
ExternalScript Invoke-WASMProfiler.ps1 C:\WINDOWS\System32\WindowsPowerShell\v1.0\Invoke-WASMProfiler.ps1
Application http-server.py 0.0.0.0 C:\Users\ME\AppData\Local\Microsoft\WindowsApps\http-server.py
Application kernel32.dll 10.0.17... C:\WINDOWS\system32\kernel32.dll
Application user32.dll 10.0.17... C:\WINDOWS\system32\user32.dll
See Get-Command for more options and examples.

In Windows PowerShell:
set-alias which where.exe

If you have PowerShell installed (which I recommend), you can use the following command as a rough equivalent (substitute programName for your executable's name):
($Env:Path).Split(";") | Get-ChildItem -filter programName*
More is here:
My Manwich! PowerShell Which

The GnuWin32 tools have which, along with a whole slew of other Unix tools.

In Windows CMD which calls where:
$ where php
C:\Program Files\PHP\php.exe

Cygwin is a solution. If you don't mind using a third-party solution, then Cygwin is the way to go.
Cygwin gives you the comfort of *nix in the Windows environment (and you can use it in your Windows command shell, or use a *nix shell of your choice). It gives you a whole host of *nix commands (like which) for Windows, and you can just include that directory in your PATH.

In PowerShell, it is gcm, which gives formatted information about other commands. If you want to retrieve only path to executable, use .Source.
For instance: gcm git or (gcm git).Source
Tidbits:
Available for Windows XP.
Available since PowerShell 1.0.
gcm is an alias of Get-Command cmdlet.
Without any parameters, it lists down all the available commands offered by the host shell.
You can create a custom alias with Set-Alias which gcm and use it like: (which git).Source.
Official docs: https://technet.microsoft.com/en-us/library/ee176842.aspx

I have a function in my PowerShell profile named 'which'
function which {
get-command $args[0]| format-list
}
Here's what the output looks like:
PS C:\Users\fez> which python
Name : python.exe
CommandType : Application
Definition : C:\Python27\python.exe
Extension : .exe
Path : C:\Python27\python.exe
FileVersionInfo : File: C:\Python27\python.exe
InternalName:
OriginalFilename:
FileVersion:
FileDescription:
Product:
ProductVersion:
Debug: False
Patched: False
PreRelease: False
PrivateBuild: False
SpecialBuild: False
Language:

Go get unxutils from here: http://sourceforge.net/projects/unxutils/
gold on windows platforms, puts all the nice unix utilities on a standard windows DOS. Been using it for years.
It has a 'which' included. Note that it's case sensitive though.
NB: to install it explode the zip somewhere and add ...\UnxUtils\usr\local\wbin\ to your system path env variable.

If you can find a free Pascal compiler, you can compile this. At least it works and shows the algorithm necessary.
program Whence (input, output);
Uses Dos, my_funk;
Const program_version = '1.00';
program_date = '17 March 1994';
VAR path_str : string;
command_name : NameStr;
command_extension : ExtStr;
command_directory : DirStr;
search_dir : DirStr;
result : DirStr;
procedure Check_for (file_name : string);
{ Check existence of the passed parameter. If exists, then state so }
{ and exit. }
begin
if Fsearch(file_name, '') <> '' then
begin
WriteLn('DOS command = ', Fexpand(file_name));
Halt(0); { structured ? whaddayamean structured ? }
end;
end;
function Get_next_dir : DirStr;
{ Returns the next directory from the path variable, truncating the }
{ variable every time. Implicit input (but not passed as parameter) }
{ is, therefore, path_str }
var semic_pos : Byte;
begin
semic_pos := Pos(';', path_str);
if (semic_pos = 0) then
begin
Get_next_dir := '';
Exit;
end;
result := Copy(Path_str, 1, (semic_pos - 1)); { return result }
{ Hmm! although *I* never reference a Root drive (my directory tree) }
{ is 1/2 way structured), some network logon software which I run }
{ does (it adds Z:\ to the path). This means that I have to allow }
{ path entries with & without a terminating backslash. I'll delete }
{ anysuch here since I always add one in the main program below. }
if (Copy(result, (Length(result)), 1) = '\') then
Delete(result, Length(result), 1);
path_str := Copy(path_str,(semic_pos + 1),
(length(path_str) - semic_pos));
Get_next_dir := result;
end; { Of function get_next_dir }
begin
{ The following is a kludge which makes the function Get_next_dir easier }
{ to implement. By appending a semi-colon to the end of the path }
{ Get_next_dir doesn't need to handle the special case of the last entry }
{ which normally doesn't have a semic afterwards. It may be a kludge, }
{ but it's a documented kludge (you might even call it a refinement). }
path_str := GetEnv('Path') + ';';
if (paramCount = 0) then
begin
WriteLn('Whence: V', program_version, ' from ', program_date);
Writeln;
WriteLn('Usage: WHENCE command[.extension]');
WriteLn;
WriteLn('Whence is a ''find file''type utility witha difference');
Writeln('There are are already more than enough of those :-)');
Write ('Use Whence when you''re not sure where a command which you ');
WriteLn('want to invoke');
WriteLn('actually resides.');
Write ('If you intend to invoke the command with an extension e.g ');
Writeln('"my_cmd.exe param"');
Write ('then invoke Whence with the same extension e.g ');
WriteLn('"Whence my_cmd.exe"');
Write ('otherwise a simple "Whence my_cmd" will suffice; Whence will ');
Write ('then search the current directory and each directory in the ');
Write ('for My_cmd.com, then My_cmd.exe and lastly for my_cmd.bat, ');
Write ('just as DOS does');
Halt(0);
end;
Fsplit(paramStr(1), command_directory, command_name, command_extension);
if (command_directory <> '') then
begin
WriteLn('directory detected *', command_directory, '*');
Halt(0);
end;
if (command_extension <> '') then
begin
path_str := Fsearch(paramstr(1), ''); { Current directory }
if (path_str <> '') then WriteLn('Dos command = "', Fexpand(path_str), '"')
else
begin
path_str := Fsearch(paramstr(1), GetEnv('path'));
if (path_str <> '') then WriteLn('Dos command = "', Fexpand(path_str), '"')
else Writeln('command not found in path.');
end;
end
else
begin
{ O.K, the way it works, DOS looks for a command firstly in the current }
{ directory, then in each directory in the Path. If no extension is }
{ given and several commands of the same name exist, then .COM has }
{ priority over .EXE, has priority over .BAT }
Check_for(paramstr(1) + '.com'); { won't return if file is found }
Check_for(paramstr(1) + '.exe');
Check_for(paramstr(1) + '.bat');
{ Not in current directory, search through path ... }
search_dir := Get_next_dir;
while (search_dir <> '') do
begin
Check_for(search_dir + '\' + paramstr(1) + '.com');
Check_for(search_dir + '\' + paramstr(1) + '.exe');
Check_for(search_dir + '\' + paramstr(1) + '.bat');
search_dir := Get_next_dir;
end;
WriteLn('DOS command not found: ', paramstr(1));
end;
end.

Not in stock Windows but it is provided by Services for Unix and there are several simple batch scripts floating around that accomplish the same thing such this this one.

The best version of this I've found on Windows is Joseph Newcomer's "whereis" utility, which is available (with source) from his site.
The article about the development of "whereis" is worth reading.

None of the Win32 ports of Unix which that I could find on the Internet are satistactory, because they all have one or more of these shortcomings:
No support for Windows PATHEXT variable. (Which defines the list of extensions implicitely added to each command before scanning the path, and in which order.) (I use a lot of tcl scripts, and no publicly available which tool could find them.)
No support for cmd.exe code pages, which makes them display paths with non-ascii characters incorrectly. (I'm very sensitive to that, with the ç in my first name :-))
No support for the distinct search rules in cmd.exe and the PowerShell command line. (No publicly available tool will find .ps1 scripts in a PowerShell window, but not in a cmd window!)
So I eventually wrote my own which, that suports all the above correctly.
Available there:
http://jf.larvoire.free.fr/progs/which.exe

This batch file uses CMD variable handling to find the command that would be executed in the path. Note: that the current directory is always done before the path) and depending on which API call is used other locations are searched before/after the path.
#echo off
echo.
echo PathFind - Finds the first file in in a path
echo ======== = ===== === ===== ==== == == = ====
echo.
echo Searching for %1 in %path%
echo.
set a=%~$PATH:1
If "%a%"=="" (Echo %1 not found) else (echo %1 found at %a%)
See set /? for help.

You can first install Git from Downloading Git, and then open Git Bash and type:
which app-name

I am using GOW (GNU on Windows) which is a light version of Cygwin. You can grab it from GitHub here.
GOW (GNU on Windows) is the lightweight alternative to Cygwin. It uses
a convenient Windows installer that installs about 130 extremely
useful open source UNIX applications compiled as native win32
binaries. It is designed to be as small as possible, about 10 MB, as
opposed to Cygwin which can run well over 100 MB depending upon
options. - About Description(Brent R. Matzelle)
A screenshot of a list of commands included in GOW:

I have created tool similar to Ned Batchelder:
Searching .dll and .exe files in PATH
While my tool is primarly for searching of various dll versions it shows more info (date, size, version) but it do not use PATHEXT (I hope to update my tool soon).

Just have to post this Windows' one liner batch file:
C:>type wh.cmd
#for %%f in (%*) do for %%e in (%PATHEXT% .dll .lnk) do for %%b in (%%f%%e) do for %%d in (%PATH%) do if exist %%d\%%b echo %%d\%%b
A test:
C:>wh ssh
C:\cygwin64\bin\ssh.EXE
C:\Windows\System32\OpenSSH\\ssh.EXE
Not quite a one-liner if you wrap the code in setlocal enableextensions and endlocal.

For you Windows XP users (who have no where command built-in), I have written a "where like" command as a rubygem called whichr.
To install it, install Ruby.
Then
gem install whichr
Run it like:
C:> whichr cmd_here

TCC and TCC/LE from JPSoft are CMD.EXE replacements that add significant functionality. Relevant to the OP's question, which is a builtin command for TCC family command processors.

I have used the which module from npm for quite a while, and it works very well: https://www.npmjs.com/package/which
It is a great multi platform alternative.
Now I switched to the which that comes with Git. Just add to your path the /usr/bin path from Git, which is usually at C:\Program Files\Git\usr\bin\which.exe. The which binary will be at C:\Program Files\Git\usr\bin\which.exe. It is faster and also works as expected.

try this
set a=%~$dir:1
If "%for%"=="" (Echo %1 not found) else (echo %1 found at %a%)

It is possible to download all of the UNIX commands compiled for Windows, including which from this GitHub repository: https://github.com/George-Ogden/UNIX

Here is a function which I made to find executable similar to the Unix command 'WHICH`
app_path_func.cmd:
#ECHO OFF
CLS
FOR /F "skip=2 tokens=1,2* USEBACKQ" %%N IN (`reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\%~1" /t REG_SZ /v "Path"`) DO (
IF /I "%%N" == "Path" (
SET wherepath=%%P%~1
GoTo Found
)
)
FOR /F "tokens=* USEBACKQ" %%F IN (`where.exe %~1`) DO (
SET wherepath=%%F
GoTo Found
)
FOR /F "tokens=* USEBACKQ" %%F IN (`where.exe /R "%PROGRAMFILES%" %~1`) DO (
SET wherepath=%%F
GoTo Found
)
FOR /F "tokens=* USEBACKQ" %%F IN (`where.exe /R "%PROGRAMFILES(x86)%" %~1`) DO (
SET wherepath=%%F
GoTo Found
)
FOR /F "tokens=* USEBACKQ" %%F IN (`where.exe /R "%WINDIR%" %~1`) DO (
SET wherepath=%%F
GoTo Found
)
:Found
SET %2=%wherepath%
:End
Test:
#ECHO OFF
CLS
CALL "app_path_func.cmd" WINWORD.EXE PROGPATH
ECHO %PROGPATH%
PAUSE
Result:
C:\Program Files (x86)\Microsoft Office\Office15\
Press any key to continue . . .
https://www.freesoftwareservers.com/display/FREES/Find+Executable+via+Batch+-+Microsoft+Office+Example+-+WINWORD+-+Find+Microsoft+Office+Path

Related

Why "%~fI" parameter expansion is able to "access" not existing drives?

I'm using the following commands:
C:\>for %I in (a: b: c: ">:" "&:") do #rem %~fI
C:\>pushd c:
C:\>set "
and the output:
=&:=&:\
=>:=>:\
=A:=A:\
=B:=B:\
=C:=C:\
....
As the =Drive: variables are storing the last accessed path the corresponding drive , it looks like the %~fI expansion somehow accessed not existing drive (which is not possible) . (all parameter expansions create such variables)
When a modifier is used in the for replaceable parameter to request a path element, the for command (well, a function that retrieves the contents of the variables being read) uses the GetFullPathName function to adapt the input string to something that could be handled. This API function (well, some of the OS base functions called by this API) generates the indicated behaviour when a relative path is requested. You can test this c code (sorry, just a quick code test), calling the executable with the ex. ;: as the first argument.
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <stdio.h>
#define BUFFER_SIZE 4096
int main(int argc, char **argv){
char buffer[BUFFER_SIZE];
DWORD ret;
LPTSTR lpszVariable;
LPTCH lpvEnv;
if (argc != 2) return 1;
if (0 == GetFullPathName( argv[1], BUFFER_SIZE, buffer, NULL )){
printf ("GetFullPathName failed (%d)\n", GetLastError());
return 2;
}
printf("current active directory: %s\r\n", buffer );
if (NULL == (lpvEnv = GetEnvironmentStrings())) {
printf("GetEnvironmentStrings failed (%d)\n", GetLastError());
return 3;
}
lpszVariable = (LPTSTR) lpvEnv;
while (*lpszVariable) {
if (lpszVariable[0]== '=') printf("%s\n", lpszVariable);
lpszVariable += lstrlen(lpszVariable) + 1;
}
FreeEnvironmentStrings(lpvEnv);
return 0;
}
to get something like
D:\>test ;:
current active directory: ;:\
=;:=;:\
=C:=C:\Windows\System32
=D:=D:\
=ExitCode=00000000
EDITED 2016/12/23
This is for windows 10, but as windows 7 behaves the same it should share the same or similar code.
The output of environment strings to console is handled by DisplayEnvVariable function. In older windows versions (checked and XP did it this way) this function calls GetEnvironmentStrings to retrive the values, but now (checked and in Vista it was changed) a pointer to a memory area is used. Somehow (sorry, at this moment I can not give this problem more time), it points to a non updated copy of the environment (in this case the updated was not generated by cmd command, but from a base Rtl function called when resolving the current drive path), generating the observed behaviour.
It is not necessary to execute a pushd or cd command, any change to the environment or any process creation will result in an update of the pointer.
#echo off
setlocal enableextensions disabledelayedexpansion
echo = before ------------------------------
set "
for %%a in ( ";:" ) do rem %%~fa
echo = after -------------------------------
set "
<nul >nul more
echo = after more --------------------------
set "
You can replace the more line with a simple set "thisIsNotSet=" to get the same result
I think there are two things contributing:
A drive letter can actually be every character other than white-spaces, / and \. Check out the subst command, which accepts also an &, for example (although it is not listed by subst):
C:\>subst X: C:\
C:\>subst ^&: C:\
C:\>subst
X:\: => C:\
C:\>X:
X:\>^&:
&:\>
for does not access the file system unless it really needs to, which is the case when:
wildcards (like ?, *) are used in the set (where the file system needs to be accessed by the for command immediately);
the for reference (%I) expansion requires information from the file system:
for the modifiers ~s, ~a, ~t, ~z and ~$ENV:, information from the file system is required, of course;
for the modifiers ~n, ~x and ~p, and also the corresponding parts of ~f, which is nothing but ~dpnx, the file system is accessed for case preservation (if the path does not exist, the original case is maintained);
for the modifiers ~n, ~x, ~p and ~d, and also ~f, the file system needs to be accessed in case a relative path is provided, with or without a dedicated drive specified (for instance, abc\def, D:data, P:), because the current working directory path (of the given drive in case) needs to be determined;
So the ~d modifier, and also the corresponding part of ~f, is handled by pure string manipulation as long as the file system is not accessed according to the aforementioned conditions.
Simply try your original code but with absolute paths, like for %I in (a:\ b:\ c:\ ">:\" "&:\") do #rem %~fI, etc., and you will find that there are no corresponding =Drive: variables.
Summary
Drive letters can literally be almost any characters (see subst).
As soon as for accesses the file system to search for drives and paths, the accessed drives are recorded in the =Drive: variables.
Windows command interpreter tries to get real name of a file or directory on storage media on using an extension like %~fI by processing a QueryDirectory as it can be seen on using Sysinternals Process Monitor. But the loop variable I just holds a string value as defined in set.
From a programmers point of view what should the command interpreter do for example on following code?
#echo off
pushd "%SystemRoot%\Temp"
del #abcdefghi.tmp 2>nul
for %%I in (#abcdefghi.tmp) do echo %%~fI
popd
There is most likely no file with name #abcdefghi.tmp in directory for temporary files for system processes. But output is nevertheless
C:\Windows\Temp\#abcdefghi.tmp
The Windows command interpreter must built a string as the batch code expects a string. It can't replace %%~fI with nothing or with an error message text as this would definitely result in an undefined behavior on further processing of the command lines in the batch file.
Exiting batch processing completely is also no option for Windows command interpreter because the FOR loop could be used to check for existence of files.
So the Windows command interpreter makes its best to build from current directory and current string of loop variable a valid file name with path, or just file path, or just file name, ... independent on existence of file/directory or validity of created string.
The command line user respectively writer of batch code has to check for validity or existence and not Windows command interpreter on expanding loop variable reference.
IMO the drives aren't really accessed but parsed at an early stage.
A simple for loop can be used to parse nonexistent drives\pathes\files
> cd
C:\Users\LotPings
> for %A in (\nonexistent) do #echo %~pnxA
\nonexistent
> for %A in (\nonexistent\a.b) do #echo %~pnxA
\nonexistent\a.b
> for %A in (\nonexistent\a.b) do #echo %~nxA
a.b
> for %A in (\nonexistent\a.b) do #echo %~fA
C:\nonexistent\a.b
> for %A in (^>\nonexistent\a.b) do #echo %~zA
ECHO ist eingeschaltet (ON).
> for %A in (^>\nonexistent\a.b) do #echo %~aA
ECHO ist eingeschaltet (ON).
> for %A in (^>\nonexistent\a.b) do #echo %~fA
C:\Users\LotPings\.\nonexistent\a.b
The very last one is quite interesting

Post-Build Event Works With one Project But Not the Other

I have 2 projects for which I am trying to create a generic Post-Build event batch file.
Here is the command in Visual Studio:
Post-Build event
if $(ConfigurationName) == Release ("$(ProjectDir)PostBuildRelease.bat" "$(TargetDir)" #(VersionNumber) "$(TargetFileName)" "$(TargetName)")
So I am calling the file PostBuildRelease.bat with 4 parameters:
Bin\Release Directory
Project Version
File Name With Extension
File Name Without Extension
Project 1
This works perfectly with this batch script:
CMD
SET parameter=%1 REM Full path to new bin\release\
SET parameter=%2 REM Full Version Number
SET parameter=%3 REM File name + extension
SET parameter=%4 REM File name - extension
SET "productionpath=Z:\Unused\Apps\LyncVdiChecker\"
MOVE %productionpath%%3 %productionpath%"_archive\"%4"."%DATE:~0,2%%DATE:~3,2%%DATE:~6,4%"-"%2
XCOPY %3 %productionpath%
Where the assembly is copied to Z:\Unused\Apps\LyncVdiChecker\ and the existing version copied to _archive in the same folder. The archived version also has the date and version number replace the file extension.
Project 2
This batch script also works perfectly (it does the same thing but in a different folder and for a different project):
CMD
SET parameter=%1 REM Full path to new bin\release\
SET parameter=%2 REM Full Version Number
SET parameter=%3 REM File name + extension
SET parameter=%4 REM File name - extension
SET "productionpath=Z:\Unused\Apps\IT Support App\"
MOVE "Z:\Unused\Apps\IT Support App\"%3 "Z:\Unused\Apps\IT Support App\_archive\"%4"."%DATE:~0,2%%DATE:~3,2%%DATE:~6,4%"-"%2
XCOPY %3 "Z:\Unused\Apps\IT Support App"
However, if I try using the same script from Project1 (the more generic version) in Project2, I get errors, even though the 2 scripts are equivalent:
Errors
The command "if Release == Release ("C:\Users\Seb.Kotze\Source\Repos\Applications\ITSelfHelp\ITHelp\PostBuildRelease.bat" "C:\Users\Seb.Kotze\Source\Repos\Applications\ITSelfHelp\ITHelp\bin\Release\" 2.0.6100.20905 "IT Self Help.exe" "IT Self Help")" exited with code 4.
Output Window:
The syntax of the command is incorrect.
Invalid number of parameters
This error is rather unhelpful, so I tried commenting out the 2 lines MOVE and XCOPY and build again:
Removed MOVE
Same error as above.
Output window:
Invalid number of parameters
Remove XCOPY
No Visual Studio Error, but this appears in the output window:
The syntax of the command is incorrect.
Parameter Output
When I echo out the parameters being used in Project2, everything seems to be in order:
"Path\to\Bin\Release"
2.0.6100.21082
"IT Self Help.exe"
"IT Self Help"
Z:\Unused\Apps\IT Support App\
How can I debug this issue? How is it possible that my script runs fine without any issues, but when run against a different project none of the commands are recognised? Any help with this is much appreciated!
You should normalize all your arguments, so they don't contain outer quotes.
Then you can use them in a reliable way.
The syntax set "variable=%~1" avoids outer quotes in the variable itself.
set "TargetDir=%~1"
set "VersionNumber=%~2"
set "TargetFileName=%~3"
set "TargetName=%~4"
SET "productionpath=Z:\IT Support App\"
set "dateStamp=%DATE:~0,2%%DATE:~3,2%%DATE:~6,4%"
MOVE "Z:\IT App\%TargetFileName%" "Z:\IT App\_archive\%TargetName%.%dateStamp%-%VersionNumber%"
XCOPY "%TargetFileName%" "Z:\IT App"
The problem is that the script is messing with the double quotes resulting in invalid paths and invalid number of arguments passed. When dealing with paths built dynamically, it's best to strip any existing " from the parts, and after the path is complete, surround it in ".
Dealing with batch arguments is explained on MSDN. Same thing for variables can be found on SS64.
I've played a bit with the file, and I was able to run it (from command line). The changes you should make in your (Project1) file:
SET productionpath="Z:\Unused\Apps\LyncVdiChecker\"
MOVE "%productionpath:"=%%~3" "%productionpath:"=%_archive\%~4.%DATE:~0,2%%DATE:~3,2%%DATE:~6,4%-%~2"
XCOPY "%~3" "%productionpath:"=%"
I moved the " from the productionpath line to the beginning of its contents. That way will work with paths that contain SPACE s.
In the MOVE and XCOPY lines, I did what I explained above: even if the syntax is not that clear, it's more robust (the last "%productionpath:"=%" could be simple written as %productionpath%, but I left it in the the 1st form for consistency).
Note: You could remove the CMD command at the beginning of your batch, since it starts a new cmd instance(process) that doesn't end.
I found a solution to this, but I am still not sure what the cause was.
I suspect it has something to do with either one of:
Spaces in productionpath causing the command parameter declaration to escape
Quotes around one or more of the parameters creating a non-existent file path
After trying out a few changes to the script, I found that changing the productionpath declaration to SET productionpath="Z:\Unused\Apps\IT Support App\" solved the issue:
CMD
SET parameter=%1 REM Full path to new bin\release\
SET parameter=%2 REM Full Version Number
SET parameter=%3 REM File name + extension
SET parameter=%4 REM File name - extension
SET productionpath="Z:\Unused\Apps\IT Support App\"
MOVE "Z:\Unused\Apps\IT Support App\"%3 "Z:\Unused\Apps\IT Support App\_archive\"%4"."%DATE:~0,2%%DATE:~3,2%%DATE:~6,4%"-"%2
XCOPY %3 "Z:\Unused\Apps\IT Support App"
Making the same change to the Project1 script did not cause that to break either, so this seems safe.
Update
After reading some of the other answers, I amended the script once again to the following:
CMD
SET "TargetDir=%~1"
SET "VersionNumber=%~2"
SET "TargetFileName=%~3"
SET "TargetName=%~4"
SET "ProductionPath=Z:\Unused\Apps\IT Support App\"
SET "ArchivePath=%ProductionPath%_archive\"
SET "DateStamp=%DATE:~0,2%%DATE:~3,2%%DATE:~6,4%"
MOVE "%ProductionPath%%TargetFileName%" "%ArchivePath%%TargetName%.%DateStamp%-%VersionNumber%"
XCOPY "%TargetFileName%" "%ProductionPath%"
Notice the "normalisation" of the paramaters - this removes all quotation marks from their values.
Also now using named parameters.

Different behavior of cmd on Win7 and XP

I am trying to run following code through cmd.
"C:\Program Files\Beyond Compare 2\BC2.exe" #"C:\New Folder\Myscript.txt" "C:\New Folder\A.txt" "C:\New Folder\B.txt"
This will actually open Beyond Compare and compare two text files.
The problem is ,when i run this code on cmd[Version 6.1.7601] it runs correctly but when i run it on version 5.1.2600 , it shows a fatal error :- Could not find C:/New .
I understand the error is due to space in the name(New Folder) , but why is it running fine on Win 7 .Does two versions of cmd have some difference in the way they accept arguments ?
Content of Myscript.txt :-
file-report layout:side-by-side &
options:display-all &
output-to:%3 output-options:html-color,wrap-word %1 %2
I can't explain why it is not working, but I have some potential solutions
1) Run with the current directory at the location of the files
Since the space is in the folder name, and all files are in the same location, you can avoid the folder name by simply changing directory to that folder and using a relative path.
pushd "c:\new folder"
"C:\Program Files\Beyond Compare 2\BC2.exe" #Myscript.txt A.txt B.txt
Of course this will not work if your files are in different locations, or if the file names have spaces (assuming spaces are really the problem)
2) Use the short 8.3 names
I hate the short 8.3 names because of the many bugs associated with them. But sometimes they can be useful.
You can get the short name of a file or folder by using DIR /X. Or you could use the following in a batch script to programmatically get the short paths.
for %%A in ("C:\New Folder\Myscript.txt") do (
for %%B in ("C:\New Folder\A.txt") do (
for %%C in ("C:\New Folder\B.txt") do (
"C:\Program Files\Beyond Compare 2\BC2.exe" #"%%~fsA" "%%~fsB" "%%~fsC"
)
)
)
Of course the above will not do any good if short 8.3 names are disabled on your volume.
If i understood correctly Raymond's comment ,the parsing is done by Beyond Compare not cmd.
I tried to use
file-report layout:side-by-side &
options:display-all &
output-to:"%3" output-options:html-color,wrap-word "%1" "%2"
and it worked fine on XP but shows error on windows 7 .It seems the beyond compare behaves differently for different OS.

Capture CMD output with AutoHotkey

I'm trying to read Windows CMD's stdout with AutoHotkey. For example, I'd like to have the output of the setconsole command inside AHK stored in a variable. I already achieved it a while ago, which makes me all the more perplex why it's not working now.
In the AHK forums, there's a rather old thread about CMDret, a DLL based functionality to do exactly what I want. The first problem was to find a working download for it, since all the links in the post were dead. Google gave me another site, hosting v3.1.2. Altough there seems to be a newer one (v3.2.1 respectively 4d Beta), I checked it out and tested a simple example:
msgbox % CMDret(COMSPEC " /C set")
CMDret(CMD)
{
VarSetCapacity(StrOut, 10000)
RetVal := DllCall("cmdret.dll\RunReturn", "str", CMD, "str", StrOut)
Return, %StrOut%
}
Unfortunately, the MsgBox contained nothing. I then checked out RetVal which had a value of 0; and the attached readme says:
If the function fails, the return value is zero.
Further down, it says:
Note: only 32 bit console applications will currently work with the
this dll version of CMDret (v3.1.2 or lower). Calls that require
command.com will likely not produce any output and may crash. To avoid
this I have included a file named "cmdstub.exe" with the download (in
the Win9x folder). This file should be used when calling 16 bit
console applications to enable returning output.
In conclusion, I am not sure what the problem is. My machine is running on 64 bit. But is the corresponding clause in the readme supposed to solely exclude 16 bit systems or does it rather only include 32 bit?
If the computing architecture is probably not the problem, then what could be?
What I am looking for is either one of the following:
Can I fix the problem and keep using v3.1.2?
Has anyone a working source (or even a local copy) of a newer version I could check out?
Is there another approach [library, .ahk code, etc.] I could use for my purpose? (preferably similar, because CMDret seems very straightforward)
If you don't need a live output, you could use the cmd box itself to save a text file of itself and then you could have autohotkey detect when the console's PID finished (using the returned result of runwait and read the outputted file into memory.
So you could do this in your run command (just a regular cmd parameter):
ipconfig > myoutput.txt
exit
Now you have a text file with the ipconfig output in it.
OR
you could do the same thing, but instead of outputting to a text file, you could output to the clipboard, like this:
ipconfig | clip
Then, since the output is on the clipboard, you can easily grab it into autohotkey.
New recommended 2 ways of doing as of Nov 2019 - https://www.autohotkey.com/docs/FAQ.htm#output:
How can the output of a command line operation be retrieved?
Testing shows that due to file caching, a temporary file can be very fast for relatively small outputs. In fact, if the file is deleted immediately after use, it often does not actually get written to disk. For example:
RunWait %ComSpec% /c dir > C:\My Temp File.txt
FileRead, VarToContainContents, C:\My Temp File.txt
FileDelete, C:\My Temp File.txt
To avoid using a temporary file (especially if the output is large), consider using the
Shell.Exec() method as shown in the examples for the Run command.
Example for the Run command - https://www.autohotkey.com/docs/commands/Run.htm#StdOut:
MsgBox % RunWaitOne("dir " A_ScriptDir)
RunWaitOne(command) {
shell := ComObjCreate("WScript.Shell")
exec := shell.Exec(ComSpec " /C " command)
return exec.StdOut.ReadAll()
}
Note: the latter method (shell.Exec) will cause quick display of a cmd window.
You can reduce the duration of its appearance by putting these lines at the top of your script, which will also cause the flickering to happen only once the first time you call the cmd command. From https://autohotkey.com/board/topic/92032-how-to-hide-a-wscript-comspec-window/:
;Following 2 lines : the cmd window will flash only once and quickly
DllCall("AllocConsole")
WinHide % "ahk_id " DllCall("GetConsoleWindow", "ptr")
How about this script, StdoutToVar ?
It has support for 64bit consoles.
http://www.autohotkey.com/board/topic/15455-stdouttovar/page-7
This has been bugging me for some time now - and finally this works !
The only prerequisite for this is MS sqlcmd.exe, a database called AHK_Dev
and of course AHK_DBA to read the value when you wish to make use of it.
PS. make sure you replace {yourDir} and {yourServer} with you own values!
USE AHK_DEV
CREATE TABLE [dbo].[AHK_DOS]([dos_out] [varchar](max) NULL) ON [PRIMARY];
insert into ahk_dos select 'empty'
Create the follow script ... call it dos_out.bat
#echo off
if "%1" == "" (
set v_cmd=""
) else (
set v_cmd=%1
)
set v_cmd=%v_cmd:~1,-1%
SETLOCAL ENABLEDELAYEDEXPANSION
if "!v_cmd!" == "" (
set v_cmd="echo ... %COMPUTERNAME% %USERNAME% %DATE% %TIME%"
set v_cmd=!v_cmd:~1,-1!
)
set v_data=""
FOR /F "usebackq delims=¬" %%i in (`!v_cmd!`) do (
set v_data="!v_data:~1,-1!%%i~"
)
set q_cmd="set nocount on;update ahk_dos set dos_out=N'!v_data:~1,-1!'"
"{yourDir}\Microsoft SQL Server\90\Tools\Binn\sqlcmd.exe" -S {yourServer} -E -d ahk_dev -Q !q_cmd! -W
set q_cmd="set nocount on;select len(dos_out) as out_len, dos_out from ahk_dos"
"{yourDir}\Microsoft SQL Server\90\Tools\Binn\sqlcmd.exe" -S {yourServer} -E -d ahk_dev -Q !q_cmd! -W -w 8000
pause
you can run it from AHK using...
dosCmd2db(c) {
runwait, {yourDir\}dos_out.bat "%c%", , , dospid
msgbox %dospid% closed
}
dosCmd2db("")
dosCmd2db("echo This is a test")
dosCmd2db("dir")
As the same field is being updated each time, you would clearly need to do something between each one to make this example useful!
Try it, and let me know how you get on
Regards, Geoff
Just an update to #amynbe answer.
MsgBox % RunWaitOne("dir " A_ScriptDir)
RunWaitOne(command) {
shell := ComObjCreate("WScript.Shell")
exec := shell.Exec(ComSpec " /C " command)
return exec.StdOut.ReadAll() }
Note: the latter method (shell.Exec)
will cause quick display of a cmd window. You can reduce
> the duration of its appearance by putting these lines at the top of
> your script, which will also cause the flickering to happen only once
> the first time you call the cmd command.
You can just do this below to hide cmd and avoid flashing.
MsgBox % RunWaitOne("dir " A_ScriptDir)
RunWaitOne(command) {
DetectHiddenWindows On
Run %ComSpec%,, Hide, pid
WinWait ahk_pid %pid%
DllCall("AttachConsole", "UInt", pid)
shell := ComObjCreate("WScript.Shell")
exec := shell.Exec(ComSpec " /C " command)
DllCall( "FreeConsole" )
return exec.StdOut.ReadAll()
}
I found a script only solution that works for AutoHotKey L 64bit at:
http://www.autohotkey.com/board/topic/67687-ahkahk-lusing-wsh-to-interact-with-command-line-progs/
After playing with it a bit I was able to capthre the entire output of a 40k text file that I listed using the DOS Type command. There is a demo that shows how you can interact with time command, which is nice if you need limited two way interaction with a dos command or batch script.

How can I split a string with batch commands in Windows?

I have an LED sign set up with a Windows computer, and am trying to make it display my Linux computer's temperature:
acpitz-virtual-0
Adapter: Virtual device
temp1: +97.7°F (crit = +183.2°F)
Now, here's the batch file on my computer
wget 192.168.1.58/sensor1.txt
I have a windows version of wget in the folder.
type sensor1.txt | findstr /v acpitz-virtual-0 | findstr /v Adapter: > msg.txt
set /p msg= < msg.txt
prismcom.exe usb {HOLD} %msg%
Now my sign flashes the equivalent of
temp1: +97.7°F (crit = +183.2°F)
I need it to flash
+97.7°F,
or even better,
+97.7 F
I've been trying with FIND and FOR and commands like that, but with no luck. How can I modify my string to work?
Thanks!
Say the variable that the temperature is stored in is called "temp", and has the value of "temp1: +97.7°F (crit = +183.2°F)". This little snippet should work wonders...
setlocal enabledelayedexpansion
set dis=!temp:~7,5! F
set check=!temp:~10,1!
if %check% neq . set dis=!temp:~7,6! F
Here is the syntax:
set variable=!variable_to_be_constrained:~offset,amount_of_characters!
One thing I should mention is that the variable "TEMP" (or "temp", capitalization generally doesn't matter too much with .bat files) is a predefined command line variable, and generally those should never be overwritten. However, in this case it's purely for aesthetic purposes and you can change it to whatever you want.
--EDIT--
Added two lines to allow for temperatures over 100 Fahrenheit. Also changed temp to dis.
If you have a Windows version of wget, then you might as well also have a special command to parse the temperature. In C you can easily do what you want:
#include <stdio.h>
int main(void) {
char buf[1000];
scanf("temp1: %[+0-9.]", buf);
printf("%sF", buf);
return 0;
}
Compile this with any C compiler. Obtain a free one from MinGW (preferred because the compiled code won't require any extra DLLs) or Cygwin (which does require an extra DLL). You can also try Microsoft Visual Studio Express, but it's more hassle.
If the code above is in filter.c, then the command for MinGW or Cygwin will be
gcc filter.c -o filter
This will produce a file called filter.exe, which you can use in your pipeline with
type sensor1.txt | findstr /v acpitz-virtual-0 | findstr /v Adapter: | filter > msg.txt
The moral is that limited tools (like cmd.exe) are often not worth the trouble.

Resources