Is there any standard equivalent of a .bashrc script for cmd?

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
jfl
Posts: 100
Joined: 26 Oct 2012 06:40
Location: Saint Hilaire du Touvet, France
Contact:

Is there any standard equivalent of a .bashrc script for cmd?

#1 Post by jfl » 26 Jan 2019 11:32

... And if not, could we work together to propose something common?
Basically, by agreeing on common conventions, we could all do our favorite tricks without risking to step on each other's feet.

This thought came from the study of possible solutions to the doskey /history issue.
The problem is to automate the installation of the macro definition, without side effects on existing cmd AutoRun commands, if any.

In Linux, this kind of thing is easily done using /etc/bashrc, ~/.bashrc, or /etc/profile scripts, which in turn invokes all scripts in /etc/profile.d.

Features wanted:
- One batch that runs every time cmd.exe starts.
- Easily extendable without modifying any existing file.
- Usable both as Administrator and as a standard user. (albeit with more limited capabilities)
- Fast and secure.

siberia-man
Posts: 126
Joined: 26 Dec 2013 09:28
Contact:

Re: Is there any standard equivalent of a .bashrc script for cmd?

#2 Post by siberia-man » 26 Jan 2019 12:53

The short answer -- not at all. But It can be emulated by setting the specially configured script in Registry. For instance:

Code: Select all

reg ass "HKCU\Software\Microsoft\Command Processor" /v "AutoRun" /t REG_EXPAND_SZ /d "full-path-to-the-specially-configured-script"
Time ago I investigated this possibility. The resulting script can be found here: https://github.com/ildar-shaimordanov/c ... setcmd.bat

I think it partially complies with your requirements:
Features wanted:
+ One batch that runs every time cmd.exe starts.
+ Easily extendable without modifying any existing file.
+ Usable both as Administrator and as a standard user. (albeit with more limited capabilities)
? Fast and secure (fast - yes, secure - not).

ShadowThief
Expert
Posts: 901
Joined: 06 Sep 2013 21:28
Location: Virginia, United States

Re: Is there any standard equivalent of a .bashrc script for cmd?

#3 Post by ShadowThief » 26 Jan 2019 22:13

Couldn't we just include the doskey alias file in a cmd shortcut?

jeb
Expert
Posts: 914
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: Is there any standard equivalent of a .bashrc script for cmd?

#4 Post by jeb » 28 Jan 2019 01:48

Hi,

I'm using the registry key to start a batch file, that adds some doskey macros.

BUT it's a bit tricky, as the batch file will also be called for new cmd instances inovked by FOR /F :!:
If the batch file doesn't check that, you can get really strange behaviour in all of your batch files.
Think of a trivial autostart.bat

Code: Select all

@echo off
echo This is my autostart.bat
And then try this on the command line

Code: Select all

for /F %a in ('ver') do @echo %a
Output wrote:This is my autostart.bat
Microsoft

But it can be avoided by checking the cmdcmdline variable.
In the normal case cmdcmdline contains "C:\Windows\system32\cmd.exe"
But called from a FOR/F it contains "C:\Windows\system32\cmd.exe /c <for-f-cmd>"

Code: Select all

@echo off
setlocal EnableDelayedExpansion
set "cmd=!cmdcmdline!"

if "!cmd!" == "!cmd:/=!" (
    2>nul (
      doskey npp="%ProgramFiles(x86)%\notepad++\notepad++.exe" $*
      doskey python3="C:\python34\python.exe" $*
    )

    echo Autorun: %~f0
    echo !cmd!#
    endlocal
    cd c:\temp
)

siberia-man
Posts: 126
Joined: 26 Dec 2013 09:28
Contact:

Re: Is there any standard equivalent of a .bashrc script for cmd?

#5 Post by siberia-man » 28 Jan 2019 02:34

Thank you, jeb. This is significantly important notice. Does this trick 100% reliable? Someone can a) launch the second instance of cmd.exe using the quoted and full path to it; b) or launch cmd.exe with parameters. Will the trick be able to recognize them and react properly (to configure new session or not).

jeb
Expert
Posts: 914
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: Is there any standard equivalent of a .bashrc script for cmd?

#6 Post by jeb » 28 Jan 2019 03:01

Hi siberia-man,

- It is reliable to detect an invocation from FOR/F.
- Invokations from pipes doesn't trigger the autostart at all.
- When a new instance of cmd.exe is launched directly it can fail, as it's possible to add any possible parameter, but then it only drops the additional commands. It shouldn't create problems for other batch files

But as a general rule, the autostart batch should avoid any output, as it could disturb other batch files.
And from my own observations, it's really hard to find such problems, as I always forget the autostart :(

jfl
Posts: 100
Joined: 26 Oct 2012 06:40
Location: Saint Hilaire du Touvet, France
Contact:

Re: Is there any standard equivalent of a .bashrc script for cmd?

#7 Post by jfl » 29 Jan 2019 04:15

Thanks @siberia-man and @jeb for your insights.
I'll try building something based on siberia-man's code, which looks real close to what I had in mind, and making sure to apply all jeb's suggestions.

Basically, my idea is to minimize the amount of code in the main AutoRun.cmd script:
It'd define a number of variables, reusing what setcmd.bat does. Then like the Unix /etc/profile script, it'd scan an AutoRun.d subdirectory, and call all scripts there in turn, in alphabetical order.
And I'd move the history management from the setcmd.bat script to an optional history.cmd script in that AutoRun.d subdirectory. (This history management being much more powerful than what I wanted to do!)
Note that the AutoRun.d subdirectory would not be in the PATH, so there's no risk that scripts there fire by mistake.

It think such a scheme would also make jahwi's proposal for a Bget Package Manager much more desirable:
Each package could install its own autorun files (if any) into the AutoRun.d subdirectory, without having to edit any common file, nor touching at cmd's AutoRun registry value. (With all the associated risks.)
What's missing is a way to define the installation paths, so that bget installs packages seamlessly into YOUR favorite installation directories.
I suggest defining a number of additional path variables in the main AutoRun.cmd script, like those typically used in Unix ./configure scripts (bindir, libdir, etc).
They'd have reasonable defaults, but you could override them by providing your own _MyPaths.cmd script (Choose your own name!) in the AutoRun.d subdirectory.

jfl
Posts: 100
Joined: 26 Oct 2012 06:40
Location: Saint Hilaire du Touvet, France
Contact:

Re: Is there any standard equivalent of a .bashrc script for cmd?

#8 Post by jfl » 05 Feb 2019 08:04

Finally, I've rewritten a new script from scratch: AutoRun.cmd
Its features are:
  1. Manage multiple independent AutoRun scripts, that all run when a new cmd.exe window starts.
  2. Define Unix-compatible installation variables.
To install AutoRun.cmd, copy it to your favorite cmd scripts directory (A directory that must be in your PATH); Then run as Administrator: AutoRun -i
If you first want to know what this installation would do, run: AutoRun -X -i
If another AutoRun script is already present, this installation will detect it, and refuse to run. It's possible to override that by using the -f option. In that case the previous AutoRun script is moved to one of the AutoRun.cmd.d extension directories. (See below)
Run 'AutoRun -l' to list AutoRun scripts currently installed.
Run 'AutoRun -?' to get a help screen with all options.

I'd appreciate if others can try this AutoRun.cmd, and and give me their feedback.

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

The first AutoRun.cmd feature (Managing multiple independent AutoRun scripts) is inspired from the way Linux login scripts work:
  • Put in %ALLUSERSPROFILE%\AutoRun.cmd.d\ the AutoRun scripts that should be run for all users.
  • Put in %USERPROFILE%\AutoRun.cmd.d\ the AutoRun scripts that should be run for you only.
Once AutoRun.cmd is installed, all the *.bat and *.cmd scripts in the above AutoRun.cmd.d directories will be run every time a new cmd.exe window is started.
The advantage is that any number of scripts can be put there, in any order, without having to deal with the command processor's AutoRun registry value.

For example, if you create an "%ALLUSERSPROFILE%\AutoRun.cmd.d\history.bat" file that just contains...

Code: Select all

doskey history=doskey /history $*
... Then all your new cmd.exe windows will support a new history command, with an output that can be piped to another command. (See this post for details.)

Likewise, if you put siberia-man's setcmd.bat in "%ALLUSERSPROFILE%\AutoRun.cmd.d\", you end-up with a far more sophisticated version of the history command, and of several other Unix commands. (Be sure to first remove my own history.bat from above, as both scripts define the same history macro!)

I plan to use this AutoRun scheme to define a which macro, that will allow implementing a -i option for my which.exe tool for Windows, similar to how "which -i" works in Unix.
Currently, my which.exe detects cmd.exe built-in commands by running a sub-shell with options "/c help". This works, but it's slow.
And extending this method to detect macros would not work at all, as a sub-shell does not have any. (Or actually did not have any before I defined this AutoRun scheme :wink:)
That future which macro will pipe the current shell macros to 'which.exe -i $*'. This will be very fast, and will ensure that which.exe reports macros for the current shell, whatever they are now.

Important: All these AutoRun scripts must NOT write anything to the screen, or change the current directory, or do anything that might surprise existing scripts when they start a sub-cmd shell. Basically they should only set new variables, or create doskey macros.

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

The second AutoRun.cmd feature is the definition of a number of standard GNU installation directory variables.
AutoRun.cmd defines reasonable defaults, closely matching the defaults required for Unix. But you're encouraged to create an AutoRun script, that defines your personal preferences.
The goal is that installation scripts, like jahwi's Bget Package Manager, eventually use these variables, and store files in the right place for you, without you having to specify anything by default.

The standard variables defined by AutoRun.cmd are: (There are many more in the GNU spec above, but these are the most relevant ones for Windows batch scripts.)
  • prefix - Base name of a directory where to install everything below. Default: set "prefix=%ProgramFiles%"
  • exec_prefix - Base name of a directory where to install all programs and scripts below. Default: set "exec_prefix=%prefix%"
  • bindir - Name of the directory where to install programs and scripts. Default: set "bindir=%exec_prefix%\bin"
  • libdir - Name of the directory where to install libraries and DLLs. Default: set "libdir=%exec_prefix%\lib"
  • includedir - Name of the directory where to install include files. Default: set "includedir=%prefix%\include"
Then it also defines a set of non-standard variables, as in Windows we often deal with two sets of executables, that should be installed in different places.
  • bindir_x86 - Name of the directory where to install 32-bits x86 *.exe programs. Default: Depends on %PROCESSOR_ARCHITECTURE%.
  • bindir_AMD64 - Name of the directory where to install 64-bits AMD64 *.exe programs. Default: Depends on %PROCESSOR_ARCHITECTURE%.
  • libdir_x86 - Name of the directory where to install 32-bits x86 libraries and DLLs. Default: Depends on %PROCESSOR_ARCHITECTURE%.
  • libdir_AMD64 - Name of the directory where to install 64-bits AMD64 libraries and DLLs. Default: Depends on %PROCESSOR_ARCHITECTURE%.
For example, I created on my laptop a "%ALLUSERSPROFILE%\AutoRun.cmd.d\dirs.cmd" file containing:

Code: Select all

set "bindir=C:\JFL\Tools"
set "bindir_x86=C:\JFL\Tools\WIN32"
set "bindir_AMD64=C:\JFL\Tools\WIN64"
And the variables created are:

Code: Select all

C:\JFL\Temp>set | findstr dir
bindir=C:\JFL\Tools
bindir_AMD64=C:\JFL\Tools\WIN64
bindir_x86=C:\JFL\Tools\WIN32
includedir=C:\Program Files\include
libdir=C:\Program Files\lib
libdir_AMD64=C:\Program Files\lib
libdir_x86=C:\Program Files (x86)\lib
windir=C:\WINDOWS

C:\JFL\Temp>
Finally 'autorun -l' reports:

Code: Select all

C:\JFL\Temp>autorun -l
AutoRun scripts:
all users: C:\JFL\Tools\AutoRun.cmd

Extension scripts:
all users: C:\ProgramData\AutoRun.cmd.d\history.bat
all users: C:\ProgramData\AutoRun.cmd.d\dirs.cmd

C:\JFL\Temp>

jfl
Posts: 100
Joined: 26 Oct 2012 06:40
Location: Saint Hilaire du Touvet, France
Contact:

Re: Is there any standard equivalent of a .bashrc script for cmd?

#9 Post by jfl » 05 Feb 2019 16:18

@jeb

I had severe difficulties distinguishing "normal" cmd.exe invokations from for /f invokations.

First, on my system, in the normal case, CMDCMDLINE does not just contain "C:\Windows\system32\cmd.exe".
Sometimes it is: set "CMDCMDLINE="C:\WINDOWS\system32\cmd.exe" " (Notice the additional quotes around %COMSPEC%, and the trailing space!)
At other times it is: set "CMDCMDLINE="C:\WINDOWS\system32\cmd.exe" /k "cd \JFL\Temp"" (That's with the arguments defined in my Command Prompt.lnk shortcut)
So I cannot rely on the presence or absence of the / character as you suggested.

I then tried to extract the first two (possibly quoted) tokens from CMDCMDLINE, looking for /c in the second one.
(This is what was in the code that I pushed first on GitHub earlier today.)
But I now find that manipulating CMDCMDLINE is VERY tricky, as this variable may contain unquoted > or | characters.
These triggered unexpected error messages when calling the subroutine I use to separate the tokens.
I've fixed that in the updated version that I pushed tonight.

I also experimented with simply running all AutoRun scripts everytime, even for for /f cases.
To be sure that AutoRun scripts don't do anything that might disrupt for /f commands, I enforce the no-output and no-cd rules, by redirecting >NUL 2>&1, and cd %CD% afterwards.
This actually works fine... except for a severe performance degradation for scripts that do a heavy usage of for /f commands. :cry:

Eventually I found a heuristic that seems to give good results:
- If the first token is == %COMSPEC% without quotes, then this is a for /f, and I should not run AutoRun scripts.
- If the second token is /c, then this a manual execution of a single command, and it's not worth running AutoRun scripts.
In all other cases, run AutoRun scripts.
With this heuristic, everything works fine, and the performance of scripts doing a heavy usage of for /f commands is barely affected. :)

I've pushed an AutoRun.cmd update on GitHub, with all the above changes. (CMDCMDLINE fix; Enforcing limitations; for /f detection heuristic.)

jfl
Posts: 100
Joined: 26 Oct 2012 06:40
Location: Saint Hilaire du Touvet, France
Contact:

Re: Is there any standard equivalent of a .bashrc script for cmd?

#10 Post by jfl » 06 Feb 2019 03:18

An interesting application of previous work on this forum:

Create a file called "%ALLUSERSPROFILE%\AutoRun.cmd.d\pid.bat", containing:

Code: Select all

@echo off
call :getPID PID
exit /b

:getPID  [RtnVar]
::
:: Store the Process ID (PID) of the currently running script in environment variable RtnVar.
:: If called without any argument, then simply write the PID to stdout.
::
setlocal disableDelayedExpansion
:getLock
set "lock=%temp%\%~nx0.%time::=.%.lock"
set "uid=%lock:\=:b%"
set "uid=%uid:,=:c%"
set "uid=%uid:'=:q%"
set "uid=%uid:_=:u%"
setlocal enableDelayedExpansion
set "uid=!uid:%%=:p!"
endlocal & set "uid=%uid%"
2>nul ( 9>"%lock%" (
  for /f "skip=1" %%A in (
    'wmic process where "name='cmd.exe' and CommandLine like '%%<%uid%>%%'" get ParentProcessID'
  ) do for %%B in (%%A) do set "PID=%%B"
  (call )
))||goto :getLock
del "%lock%" 2>nul
endlocal & if "%~1" equ "" (echo(%PID%) else set "%~1=%PID%"
exit /b
Then every cmd.exe shell will have a PID variable, like the $$ variable in Unix.
Ex:

Code: Select all

C:\JFL\Temp>set pid
PID=16392

C:\JFL\Temp>cmd
Microsoft Windows [Version 10.0.17763.253]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\JFL\Temp>set pid
PID=248

C:\JFL\Temp>exit

C:\JFL\Temp>set pid
PID=16392

C:\JFL\Temp>
:D

jeb
Expert
Posts: 914
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: Is there any standard equivalent of a .bashrc script for cmd?

#11 Post by jeb » 06 Feb 2019 09:29

Hi jfl,

nice work!
jfl wrote:
05 Feb 2019 16:18
Eventually I found a heuristic that seems to give good results:
- If the first token is == %COMSPEC% without quotes, then this is a for /f, and I should not run AutoRun scripts.
- If the second token is /c, then this a manual execution of a single command, and it's not worth running AutoRun scripts.
In all other cases, run AutoRun scripts.
Both rules have some flaws...
- If the first token is == %COMSPEC% without quotes, then this is a FOR /F OR it's invoked by drag&drop
- If the second token is /c, then this could be a manual execution OR invoked by drag&drop

drag&drop produces quotes around the batch file and for each argument containing spaces:

Code: Select all

cmdcmdline: C:\Windows\system32\cmd.exe /c ""C:\Temp\test.bat" "C:\documents\doc  1.txt" C:\documents\doc2.txt"
while a FOR /F normally produces a cmd /c without quotes

Code: Select all

cmdcmdline: C:\Windows\system32\cmd.exe /c echo 123

Post Reply