Windows Scripting: Difference between revisions

From Andreida
 
(9 intermediate revisions by the same user not shown)
Line 1: Line 1:
=== comments ===
There are no real comments for Windows batch files (.cmd / .bat).

You can put "rem" at the start of a line but it is not a comment,
it is a command which does nothing with its parameters.

Meaning: the parameters are sometimes read and interpreted before "rem" is seen,
so you can get an error for something behind the rem.

But often it works.

rem print hello

Lines are printed before they are "used", with
@echo off
as the first line, you can stop that.

@echo off
rem echo Hello
echo World

=== directory of current script ===
=== directory of current script ===
change to the directory of the executed script:
change to the directory of the executed script:
Line 94: Line 115:
* the "(" must be on the same line as the if
* the "(" must be on the same line as the if
* the ") else (" can not be on multiple lines
* the ") else (" can not be on multiple lines

If there is only one valid count of parameters, you can do it like this:
<pre>
@echo off

call create-argCount %*

if [%argCount%] NEQ [1] (
call :syntax
exit /B 1
)
</pre>


=== text color ===
=== text color ===
Line 154: Line 187:
In "helpers" I have "create-argCount.cmd" (see above or below on this wiki page) and "echo-color.cmd".
In "helpers" I have "create-argCount.cmd" (see above or below on this wiki page) and "echo-color.cmd".


===== echo-color.cmd =====
Create a file "echo-color.cmd":
Create a file "echo-color.cmd":
<pre>
<pre>
Line 205: Line 239:


</pre>
</pre>

===== create-links-for-ssh.cmd =====

If you create links with "mklink" you can create soft or hard links. For some reason you need fewer rights for hard links. But if you want to backup the linked data with Mercurial, then you must use soft links, because Mercurial does not recognize changes in hard links outside the repository.



In "create-links" I have multiple files for link creation, for example "create-links-for-ssh.cmd":
In "create-links" I have multiple files for link creation, for example "create-links-for-ssh.cmd":
Line 219: Line 258:


set FILE=config
set FILE=config
mklink /H %FILE% %DIR_FILES%\%FILE%
mklink %FILE% %DIR_FILES%\%FILE%
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")


set FILE=authorized_keys
set FILE=authorized_keys
mklink /H %FILE% %DIR_FILES%\%FILE%
mklink %FILE% %DIR_FILES%\%FILE%
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")


set FILE=id_rsa
set FILE=id_rsa
mklink /H %FILE% %DIR_FILES%\%FILE%
mklink %FILE% %DIR_FILES%\%FILE%
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")


set FILE=id_rsa.pub
set FILE=id_rsa.pub
mklink /H %FILE% %DIR_FILES%\%FILE%
mklink %FILE% %DIR_FILES%\%FILE%
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")


set FILE=id_rsa.ppk
set FILE=id_rsa.ppk
mklink /H %FILE% %DIR_FILES%\%FILE%
mklink %FILE% %DIR_FILES%\%FILE%
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")


set FILE=known_hosts
set FILE=known_hosts
mklink /H %FILE% %DIR_FILES%\%FILE%
mklink %FILE% %DIR_FILES%\%FILE%
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")


Line 349: Line 388:
exit /B
exit /B
</pre>
</pre>

=== variable for home directory of current user ===
Normally you will already have some variables like %USERPROFILE%.
Use it like
echo %USERPROFILE%
cd /D %USERPROFILE%

=== predefined environment variables ===
To see the current environment variables call
set

Keep in mind that not all of them exist for every user, depending on the exact Windows version and installed programs.

=== add your script directory to %PATH% ===
%PATH% is the environment variable which is used in case you just call something without giving a path. Like ... opening a console, typing "my-script" and pressing enter.
Then Windows will search the current directory (yes, differently than Linux, you don't have to give the "." path) and all directories mentioned in %PATH% for "my-script" and call the first one it finds.

You add directories to that path with
* right-click START button
* System
* Advanced System Settings
* Environment Variables...
* select "Path"
* Edit...
* Add a line with your path
* press "Ok"

There are two areas. "User variables for the current user" and "System variables". Depending on your needs both can do the job or only one can do it. Will the script always be executed by the current user?

Latest revision as of 23:17, 19 May 2024

comments

There are no real comments for Windows batch files (.cmd / .bat).

You can put "rem" at the start of a line but it is not a comment, it is a command which does nothing with its parameters.

Meaning: the parameters are sometimes read and interpreted before "rem" is seen, so you can get an error for something behind the rem.

But often it works.

rem print hello

Lines are printed before they are "used", with

@echo off 

as the first line, you can stop that.

@echo off
rem echo Hello
echo World

directory of current script

change to the directory of the executed script:

chdir /D %~dp0

The /D allows to change drives too.

remove quotes from an argument

@echo off
call :func1 "I am a long, long string."
exit /B 0

:func1
echo %1
echo %~1
exit /B 0

Output:

"I am a long, long string."
I am a long, long string.

save the current directory and return to it

set DIR_START=%cd%
cd /D whereEver/AndDoStuff
cd /D %DIR_START%

functions

@echo off
echo 1
call :func_1 2
call :func_1 3
echo 4
exit /B

:func_1
echo %1 (called in a function)
exit /B 0

Output:

>test
1
2 (called in a function)
3 (called in a function)
4

parameters

  • Parameters to a script are %1 to %9
  • %0 is the call and should contain the script name
  • %10 does not exist
  • %* "is" all parameters, even if there are 15
  • if you want to have more than 9 parameters, google for shift or use something like
FOR %%A IN (%*) DO (
  echo xxx %%A xxx
)

Output:

>test 1 2 3 4 5 6 7 8 9 10 11 12 13
x 1 x
x 2 x
x 3 x
x 4 x
x 5 x
x 6 x
x 7 x
x 8 x
x 9 x
x 10 x
x 11 x
x 12 x
x 13 x

test for parameters

@echo off

if [%1]==[] (
  echo Parameter 1 missing!
) else (
  echo Paramater 1 found: %1
)
  • '[' and ']' around "%1" are just for the case it is empty. You can use anything you want:
 if xx%1xx==xxxx (
  • you need a space between "[]" and '(' in the if condition
  • the "(" must be on the same line as the if
  • the ") else (" can not be on multiple lines

If there is only one valid count of parameters, you can do it like this:

@echo off

call create-argCount %*

if [%argCount%] NEQ [1] (
	call :syntax
	exit /B 1
)

text color

The documentation from MS is here: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences?redirectedfrom=MSDN

You will need to output a certain character which is just called ESC. It is the "character" with the number 27 in the ASCII table, so you can create it on the console if you hold ALT and enter with the numlock digits "027". When you release ALT afterwards you should get "^[". You can try it but it will be of not much use. It would be:

  • ALT + 027
  • [92m
  • <return>

Now your prompt should be green. To get back to normal:

  • ALT + 027
  • [0m
  • <return>

Try the following script: (create a test.cmd and execute it from the command line)

@echo off

for /F %%a in ('echo prompt $E ^| cmd') do set "ESC=%%a"
set GREEN=%ESC%[92m
set DEFAULT=%ESC%[0m
set ERROR=%ESC%[101;93m

call :out Dies ist ein erster Test.
call :out Dies ist ein zweiter Test.
call :outError This is not a test, please leave this script immediately!

set ESC=
set ERROR=
SET GREEN=
exit /B
:out
  echo %GREEN% %* %DEFAULT%
  exit /B
  
:outError
  echo %ERROR% %* %DEFAULT%
  exit /B  

You can create the ESC variable with

set ESC=

then use ALT+0+2+7 to get the correct "character" at that spot. It will look like:

set ESC=ESC

but the second ESC will look different, because it "is" just one character. Copy/paste will be a burden and just looking at such a script will not tell you how to recreate it. That is the reason the variant where you create the ESC with the for (I got the idea from someone called Aacini in a forum) is much much better in my opinion.

echo with color by calling a script

As always, the paths will have to be changed depending on your setup. I am using

 bin
   win
     helpers
     create-links
     32
     64

In "helpers" I have "create-argCount.cmd" (see above or below on this wiki page) and "echo-color.cmd".

echo-color.cmd

Create a file "echo-color.cmd":

@echo off

for /F %%a in ('echo prompt $E ^| cmd') do set "ESC=%%a"
set GREEN=%ESC%[92m
set DEFAULT=%ESC%[0m
set RED=%ESC%[101;93m

call %~dp0\create-argCount.cmd %*
if [%argCount%]==[2] (
	if /I [%~1]==[DEFAULT] (
		call :default %~2 
		exit /B
	) else if /I [%~1]==[GREEN] (
		call :green %~2
		exit /B
	) else if /I [%~1]==[RED] (
		call :red %~2
		exit /B
	)
)

call :syntax

set ESC=
set RED=
SET GREEN=

exit /B

:syntax
	echo echo-color v1 by Andreas Duffner, 2024
	echo Syntax:  echo-color ^</?^>
	echo          echo-color ^<COLOR^> ^<"TEXT"^>
	echo Example: echo-color RED "You are doing it wrong!"
exit /B

:default
	echo %DEFAULT%%*%DEFAULT%
exit /B

:green
	echo %GREEN%%*%DEFAULT%
exit /B
  
:red
	echo %RED%%*%DEFAULT%
exit /B  

create-links-for-ssh.cmd

If you create links with "mklink" you can create soft or hard links. For some reason you need fewer rights for hard links. But if you want to backup the linked data with Mercurial, then you must use soft links, because Mercurial does not recognize changes in hard links outside the repository.


In "create-links" I have multiple files for link creation, for example "create-links-for-ssh.cmd":

@echo off
set DIR_START=%cd%
set HELPERS=%~dp0\..\helpers

chdir /D %~dp0
cd ..\..\..\documents\always\keys\andreas\
set DIR_FILES=%cd%

cd /D c:\Users\Andreas\.ssh\

set FILE=config
mklink %FILE% %DIR_FILES%\%FILE%
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")

set FILE=authorized_keys
mklink %FILE% %DIR_FILES%\%FILE%
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")

set FILE=id_rsa
mklink %FILE% %DIR_FILES%\%FILE%
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")

set FILE=id_rsa.pub
mklink %FILE% %DIR_FILES%\%FILE%
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")

set FILE=id_rsa.ppk
mklink %FILE% %DIR_FILES%\%FILE%
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")

set FILE=known_hosts
mklink %FILE% %DIR_FILES%\%FILE%
if %ERRORLEVEL%==0 (call %HELPERS%\echo-color green "%FILE%: Ok") else (call %HELPERS%\echo-color red "%FILE%: Error")


cd %DIR_START%

pause

output empty line

Put a period immediately after the echo. No space between echo and period.

@echo off
echo Normal 1
echo.
echo Normal 2

calling another script and keeping the variables it creates

call %~dp0\SetDateTimeToVariables-Europe.cmd

In this call we call a script in the same directory as the calling script. Keep in mind that the working directory does not have to be the directory of the working script, so we have to be more exact when we call the 2nd script.

Creating files with current date/time in the name

Get the current date/time in European format into a variable

Create the file "SetDateTimeToVariables-Europe.cmd":

@echo off
rem v2 (2022-11-23), use this for dates like "2022-11-23"
for /f "tokens=1-3 delims=- " %%a in ('date /T') do set year=%%a
for /f "tokens=1-3 delims=- " %%a in ('date /T') do set month=%%b
for /f "tokens=1-3 delims=- " %%a in ('date /T') do set day=%%c
set TODAY=%year%-%month%-%day%
rem @echo today: %TODAY% year: %year% month: %month% day: %day%

for /f "tokens=1 delims=: " %%h in ('time /T') do set hour=%%h
for /f "tokens=2 delims=: " %%m in ('time /T') do set minutes=%%m
set NOW=%hour%-%minutes%
rem @echo %NOW%

Use the earlier created script in one of your normal scripts

In this example we create a backup of a Tekkit-Lite installation:

set DIR_SAVES_PARENT=d:\Games\MineCraft\Technic\installed\modpacks\tekkitlite\
set DIR_SAVES="saves"
set DIR_ARCHIVE=d:\data\_temp-backup\
set PATH_7_ZIP=%~dp0\32\7za
set BACKUP_NAME="MC-Tekkit-Lite-All-Worlds"


set DIR_START=%cd%
call %~dp0\SetDateTimeToVariables-Europe.cmd
cd  /D %DIR_SAVES_PARENT%
rem echo %TODAY%
%PATH_7_ZIP% a -tzip %DIR_ARCHIVE%\%TODAY%--%NOW%-%BACKUP_NAME%.zip -r %DIR_SAVES%
cd /D %DIR_START%

The above script is complete and working. Of course you have to have 7zip in the correct directory and bla, bla bla. You get it. Right?

The relevant parts are:

call %~dp0\SetDateTimeToVariables-Europe.cmd

and

%PATH_7_ZIP% a -tzip %DIR_ARCHIVE%\%TODAY%--%NOW%-%BACKUP_NAME%.zip -r %DIR_SAVES%

The call to SetDateTimeToVariables-Europe.cmd creates variables and fills them with content and later we use these variables to create a meaningful filename.

escape character

If you want to output certain symbols, Windows will not allow it because they have a meaning and are therefore interpreted. The Escape character for the normal Windows command shell is '^'.

@echo off
echo abc def ^<xxx^>

count of arguments

create a helper script "create-argCount.cmd":

set argCount=0
for %%x in (%*) do Set /A argCount+=1

create a script "test.cmd" that uses it:

@echo off
call create-argCount.cmd %*
echo Count of arguments: %argCount%

call it:

test 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Count of arguments: 15

structure for a script to check for parameters

Do only check for the correct version, anything else is wrong. No need to check for "/?" or "--help".

Keep in mind that this is a simplified example. You will need to watch for correct working directories and paths. The call to create-argCount.cmd could need a "%~dp0" of some kind in front or something completely different, depending on your setup.

This is an example for a script called "test.cmd":

@echo off
call create-argCount.cmd %*

if %argCount% == 2 (
	echo todo
) else (
	call :syntax
)

exit /B
:syntax
echo test v1 by Andreas Duffner, 2024
echo Syntax:  
echo    test ^<color^> ^<"text"^>
echo    test RED "You are doing it wrong!"
exit /B

variable for home directory of current user

Normally you will already have some variables like %USERPROFILE%. Use it like

echo %USERPROFILE%
cd /D %USERPROFILE%

predefined environment variables

To see the current environment variables call

set

Keep in mind that not all of them exist for every user, depending on the exact Windows version and installed programs.

add your script directory to %PATH%

%PATH% is the environment variable which is used in case you just call something without giving a path. Like ... opening a console, typing "my-script" and pressing enter. Then Windows will search the current directory (yes, differently than Linux, you don't have to give the "." path) and all directories mentioned in %PATH% for "my-script" and call the first one it finds.

You add directories to that path with

  • right-click START button
  • System
  • Advanced System Settings
  • Environment Variables...
  • select "Path"
  • Edit...
  • Add a line with your path
  • press "Ok"

There are two areas. "User variables for the current user" and "System variables". Depending on your needs both can do the job or only one can do it. Will the script always be executed by the current user?