Batch To get Count Of 0 Byte Files - windows

I'm trying to run a batch to get the number of 0 byte files in a directory. If the count is one or more then I want to delete the files otherwise quit. Here is what I have so far.
#echo off
if "%~z1" == "" (
echo File does not exist.
) else if "%~z1" == "0" (
echo File is empty.
) else (
echo File is non-empty.
)

(Edited, see comments. Original answer below.)
The following script counts empty files in a directory, then, if the count turns out greater than 0, deletes the empty files. The directory is specified as a parameter to the batch script. For example, if you need to process the directory of C:\Users\DS\Downloads, call the script like this (assuming script.bat is the script file's name):
script.bat C:\Users\DS\Downloads
This is the script:
#ECHO OFF
IF "%~1" == "" (ECHO Usage: %~nx0 path\to\files& GOTO :EOF)
SET "workdir=%~1"
SET count=0
SET "command=SET /A count+=1"
CALL :processempty
ECHO Number of empty files: %count%
IF %count% GTR 0 (
ECHO Deleting files...
SET "command=DEL ^"%%~F^""
CALL :processempty
)
GOTO :EOF
:processempty
FOR %%F IN ("%workdir%\*") DO (
IF "%%~zF" == "0" %command%
)
Original answer follows:
#ECHO OFF
SETLOCAL
SET firstfile=
SET delfirstfile=
FOR %%F IN (*) DO (
IF "%%~zF" == "0" (
IF DEFINED firstfile (
ECHO DEL "%%F"
SET delfirstfile=1
) ELSE (
SET "firstfile=%%F"
)
)
)
IF DEFINED delfirstfile ECHO DEL "%firstfile%"
ENDLOCAL
The above script works like this:
It iterates through all the files in the current directory and checks their sizes.
When the first empty file is found, its name is stored into a variable.
Every subsequent empty file is deleted and another variable is set to indicate that the first empty file should be deleted too.
After the loop the indicator variable is checked. If it is set, the first empty file is deleted.
PS. I would suggest you first to run this script as is to make sure it works correctly. Afterwards you'll need to remove ECHO in front of each of the two DEL commands to let the script actually remove files.

Related

Iterate and managing files with batch in network route vs local route

I run my code by inserting this lines to my window explorer address bar. It iterates all the files in the selected directory (just with the desired extensions specified in the list). It renames the files by adding a sequential number then append to fajlnevek to be able to paste it from the clipboard at the end. It also moves the renamed files to a new directory. I don't understand why it does not work if I change the path from network route to my simple local.
It works with this kind of network routes:
\\something.ab.lol.aa\MakroModul\teszt.bat "\\something.ab.lol.aa\Datenimport_Edge" "\\something.ab.lol.aa\folder" FILENAME
..but it doesn't work with my local route:
C:\Users\ABC\Name, My Name(A B-C44) - 990_BACKUP\\teszt.bat "C:\Temp\test\from" "C:\Temp\test\to" FILENAME
The code:
#echo on
setlocal ENABLEDELAYEDEXPANSION
set/a fileNum = 1
chcp 28591
set arg1=%~1
set arg2=%~2
set arg3=%~3
mkdir "%arg2%"
set list=txt csv xls pdf doc ppt
set fajlnevek=
for %%a in (%list%) do (
for %%f in (%arg1%\*.%%a) do (
echo %arg3%_!fileNum!%%~xf
echo "%%~nf%%~xf"
ren %arg1%\"%%~nf%%~xf" %arg3%_!fileNum!%%~xf
if .!fajlnevek!==. (
set fajlnevek=%arg2%\%arg3%_!fileNum!%%~xf
) else (
set fajlnevek=!fajlnevek!,%arg2%\%arg3%_!fileNum!%%~xf
)
set/a fileNum += 1
)
move "%arg1%\*.%%a" "%arg2%"
)
echo %fajlnevek% | clip
pause
(arg first from place
arg second to place
third arg new file name

Modify a specific setting in an INI file on multiple PCs using Windows Batch

My goal is to modify a single setting in an INI file over the network on multiple PC's using an external list of PCs, and output the results to a log file.
Needs to be a windows batch file. Network is locked out of running scripts such as PS.
Security is not an issue, as read\write access is open at my level to all targeted PCs.
The content of the INI file to be modified, is unique to the PC, so blanket overwrite/copy is not an option.
The removal or addition of blank lines in the INI is not an issue.
The file PCList.txt, could contain 1-100 PC names or IP's, one per line.
Output to a log file with PCNAME: 'Success' or 'Fail'
So far, I have found and modified a script that will edit a file locally, but have been unable to get it to work with a FOR/DO loop to process that action for each PC in the list - OR - add the logging output
#Echo Off
SetLocal EnableDelayedExpansion
Set _PathtoFile=C:\Test\Sample.ini
Set _OldLine=Reboot=
Set _NewLine=Reboot=1
Call :_Parse "%_PathtoFile%"
Set _Len=0
Set _Str=%_OldLine%
Set _Str=%_Str:"=.%987654321
:_Loop
If NOT "%_Str:~18%"=="" Set _Str=%_Str:~9%& Set /A _Len+=9& Goto _Loop
Set _Num=%_Str:~9,1%
Set /A _Len=_Len+_Num
PushD %_FilePath%
If Exist %_FileName%.new Del %_FileName%.new
If Exist %_FileName%.old Del %_FileName%.old
Set _LineNo=0
For /F "Tokens=* Eol=" %%I In (%_FileName%%_FileExt%) Do (
Set _tmp=%%I
Set /A _LineNo+=1
If /I "!_tmp:~0,%_Len%!"=="%_OldLine%" (
>>%_FileName%.new Echo %_NewLine%
) Else (
If !_LineNo! GTR 1 If "!_tmp:~0,1!"=="[" Echo.>>%_FileName%.new
SetLocal DisableDelayedExpansion
>>%_FileName%.new Echo %%I
EndLocal
))
Ren %_FileName%%_FileExt% %_FileName%.old
Ren %_FileName%.new %_FileName%.ini
PopD
Goto :EOF
:_Parse
Set _FilePath=%~dp1
Set _FileName=%~n1
Set _FileExt=%~x1
Goto :EOF
Here is the sample files: Settings.ini
[SAMPLE SETTINGS]
SERVER=MYPC
Reboot=0
[SECTION2]
SETTINGX=1234
[SECTION3]
SETTINGX=4567
PCList.txt
MY-PC
YOUR_PC
NETSERVER
192.168.10.100
Still trying to wrap my head around everything that this script is doing - this was the only information provided in the tech's answer (source of the initial script)
It preserves the original file by renaming it with a .old extension
It will remove all blank lines, but will insert a blank line before any line that starts with [ (unless it's the first line in the file)
I get the length of the specified search line in case the old line in the file has trailing spaces.
If more than one line starts with the old line text, it will be changed as well. Example, if the file has these lines: test=line1 test=line1 again and you set _OldLine to test=line1, both lines will be changed.
If that might be a problem,
change this line: If /I "!_tmp:~0,%_Len%!"=="%_OldLine%" (
to this: If /I "!_tmp!"=="%_OldLine%" (
Just keep in mind that with this, if the old line in the file has trailing spaces, it won't be changed unless you include them in the _OldLine variable
The main thing I need at this point is getting this action to take place using the above script... Or something similar, on a list of PC's listed in an external TXT file - over the network. I'm open to alternative approaches provided they are windows batch scripting, and do not include calling an external application.
Wish list, (by no means required at this time):
Ability to specify or set the [SECTION] in the INI where the setting being modified is found, some potential for the same setting to be found in multiple sections within the same INI (not the case with my initial needs, but I can see it being the case in the future)
Batch file code for the task
I suggest to use the following code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "SectionName=[SAMPLE SETTINGS]"
set "EntryName=Reboot"
set "EntryValue=1"
set "LogFile=%~dpn0.log"
set "TempFile=%TEMP%\%~n0.tmp"
set "ListFile=%~dp0PCList.txt"
if not exist "%ListFile%" (
echo ERROR: List file %ListFile%" not found.>"%LogFile%"
goto EndBatch
)
del /A /F "%LogFile%" 2>nul
for /F "usebackq delims=" %%I in ("%ListFile%") do call :UpateIniFile "\\%%I\C$\Test\Sample.ini"
goto EndBatch
:UpateIniFile
if not exist %1 (
echo File not found: %1>>"%LogFile%"
goto :EOF
)
set "EmptyLines="
set "EntryUpdate="
set "CopyLines="
(for /F delims^=^ eol^= %%I in ('%SystemRoot%\System32\findstr.exe /N "^" %1 2^>nul') do (
set "Line=%%I"
setlocal EnableDelayedExpansion
if defined CopyLines (
echo(!Line:*:=!
endlocal
) else if not defined EntryUpdate (
echo(!Line:*:=!
if /I "!Line:*:=!" == "!SectionName!" (
endlocal
set "EntryUpdate=1"
)
) else (
if /I "!Line:*:=!" == "!EntryName!=!EntryValue!" (
endlocal
goto ValueExists
)
if "!Line:*:=!" == "" (
endlocal
set /A EmptyLines+=1
) else (
set "Line=!Line:*:=!"
if "!Line:~0,1!!Line:~-1!" == "[]" (
echo !EntryName!=!EntryValue!
if defined EmptyLines for /L %%J in (1,1,!EmptyLines!) do echo(
echo !Line!
endlocal
set "EntryUpdate=3"
set "CopyLines=1"
) else (
if defined EmptyLines for /L %%L in (1,1,!EmptyLines!) do echo(
for /F delims^=^=^ eol^= %%J in ("!Line!") do (
if /I not "%%~J" == "!EntryName!" (
echo !Line!
endlocal
) else (
echo !EntryName!=!EntryValue!
endlocal
set "EntryUpdate=2"
set "CopyLines=1"
)
)
set "EmptyLines="
)
)
)
))>"%TempFile%"
if not defined EntryUpdate (
>>"%TempFile%" echo %SectionName%
>>"%TempFile%" echo %EntryName%=%EntryValue%
set EntryUpdate=4
)
if %EntryUpdate% == 1 (
>>"%TempFile%" echo %EntryName%=%EntryValue%
set "EntryUpdate=3"
)
move /Y "%TempFile%" %1 2>nul
if errorlevel 1 (
echo Failed to update: %1>>"%LogFile%"
del "%TempFile%"
goto :EOF
)
if %EntryUpdate% == 2 (
echo Value updated in: %1>>"%LogFile%"
goto :EOF
)
if %EntryUpdate% == 3 (
echo Entry added to: %1>>"%LogFile%"
goto :EOF
)
if %EntryUpdate% == 4 (
echo Section+entry to: %1>>"%LogFile%"
goto :EOF
)
:ValueExists
echo Value existed in: %1>>"%LogFile%"
del "%TempFile%"
goto :EOF
:EndBatch
endlocal
File related variables defined at top
The log file is created in directory of the batch file with batch file name, but with file extension .log.
The temporary file is created on local machine in directory for temporary files with batch file name, but with file extension .tmp.
The list file containing the computer names or IP addresses must be PCList.txt in directory of the batch file.
Explanation of main code
For each line in the list file not starting with ; the first FOR loop calls a subroutine with the full file name of the INI file to update with appropriate UNC path. A semicolon at beginning of a line can be used to comment out a computer name or IP address.
Requirements for the INI file update routine
The subroutine is written to fulfill following requirements:
The subroutine should not modify the INI file if it contains already the entry to update with the correct value in correct section. I don't like it setting archive attribute and modifying the last modification date of a file if the file contents is not really modified at all.
It should replace the value of the entry in the correct section on being different to the value defined at top of the batch file.
It should add the entry with the wanted value to the section if the section is already existing, but does not yet contain the entry. The entry should be added after last non-empty line of the section.
It should add the section and the entry with the wanted value at end of the file if the file does not contain the section at all.
The subroutine is written to keep all empty lines in file, except empty lines at end of the file if just the entry or the section and the entry must be appended at end of the file. It does not add empty lines. It could be enhanced to additionally reformat an INI file with making sure that there is always exactly one empty line above a section except at top of the file.
Sample set of files
There were six files used in current directory for testing with using as first FOR loop the following command line instead of the command line in posted batch file code:
for %%I in (*.ini) do call :UpateIniFile "%%I"
File1.ini with having the section and the entry, but with wrong value:
[SAMPLE SETTINGS]
SERVER=MYPC
Reboot=0
[SECTION2]
SETTINGX=1234
[SECTION3]
SETTINGX=4567
File2.ini with having the section and the entry with the wanted value:
[SAMPLE SETTINGS]
SERVER=MYPC
Reboot=1
[SECTION2]
SETTINGX=1234
[SECTION3]
SETTINGX=4567
File3.ini with having the section at top, but not containing the entry:
[SAMPLE SETTINGS]
SERVER=MYPC
[SECTION2]
SETTINGX=1234
[SECTION3]
SETTINGX=4567
File4.ini with missing the section completely:
[SECTION2]
SETTINGX=1234
[SECTION3]
SETTINGX=4567
File5.ini with having the section at end, but not containing the entry:
[SECTION2]
SETTINGX=1234
[SECTION3]
SETTINGX=4567
[SAMPLE SETTINGS]
SERVER=MYPC
File6.ini is like File1.ini, but read-only attribute is set for this file.
Results for sample set of files
The batch file writes to the log file for this sample set:
Value updated in: "File1.ini"
Value existed in: "File2.ini"
Entry added to: "File3.ini"
Section+entry to: "File4.ini"
Entry added to: "File5.ini"
Failed to update: "File6.ini"
File2.ini and File6.ini are not updated at all.
The other four files have the following lines after batch file execution:
File1.ini:
[SAMPLE SETTINGS]
SERVER=MYPC
Reboot=1
[SECTION2]
SETTINGX=1234
[SECTION3]
SETTINGX=4567
File3.ini:
[SAMPLE SETTINGS]
SERVER=MYPC
Reboot=1
[SECTION2]
SETTINGX=1234
[SECTION3]
SETTINGX=4567
File4.ini:
[SECTION2]
SETTINGX=1234
[SECTION3]
SETTINGX=4567
[SAMPLE SETTINGS]
Reboot=1
File5.ini:
[SECTION2]
SETTINGX=1234
[SECTION3]
SETTINGX=4567
[SAMPLE SETTINGS]
SERVER=MYPC
Reboot=1
So the batch file makes a good job for the sample set of files.
Explanation of INI file update routine
There is first checked if the file to update is found at all and an appropriate information is appended to the log file if the file cannot be found.
The subroutine undefines next three environment variables used in the code below.
The FOR loop used in subroutine UpateIniFile is explained in full detail by my answer on
How to read and print contents of text file line by line?
Every line in the INI file output by FINDSTR with line number and colon at beginning is assigned first to environment variable Line with delayed expansion not enabled to prevent line corruption on containing exclamation marks.
Next delayed expansion is enabled which results in creating also a copy of the existing environment variables. Please read lower half of this answer for details about the commands SETLOCAL and ENDLOCAL. It is really important to understand what these two commands do to understand the rest of the code.
The first IF condition is true if the entry of which value should be updated is already in the temporary file with the wanted value and so all remaining lines in the file can be copied to temporary file without any further processing. In this case the line read from file is output with removing the line number and the colon added by FINDSTR to the line.
The second IF condition is true if the section defined at top of the batch file was not found up to current line. In this case the line read from file is also output with line number and colon, but an additional case-insensitive string comparison is done to find out if this line contains the section of interest. If the section of interest is found, the environment variable EntryUpdate is defined with value 1 in the environment defined on entering the subroutine.
Otherwise the current line is below the section of interest.
If this line without line number and colon is case-insensitive equal the entry to update with wanted value, the processing of the lines can be stopped as the file contains the entry already with the wanted value. Command GOTO is used to exit the FOR loop and continue batch file processing below the batch label ValueExists where the temporary file is deleted and the information is written into the log file that the value exists already in the current file before leaving the subroutine.
If the line in section of interest without line number and colon is an empty line, the environment variable EmptyLines is incremented by one in previous environment as defined on entering the subroutine. The value 0 is used on EmptyLines not defined all as explained by help of command SET. There is nothing else done on line in section of interest is an empty line.
For a non-empty line in section of interest the environment variable Line is first redefined with removal of line number and colon for easier processing this line further.
The next condition compares case-sensitive the first and the last character of the line extra enclosed in double quotes with the string "[]" to find out if this line is the beginning of a new section. If this condition is true, the section of interest does not contain the entry at all. Therefore the entry with the wanted value is output now and next all empty lines found perhaps before the new section not yet output. Then the line with the new section is output and in environment defined on entering the subroutine the environment variable EntryUpdate is redefined with string value 3 and environment variable CopyLines is defined as all other lines in file can now be just copied to the temporary file.
But if the current line is not a new section, it is a non-empty line in section of interest. It could be the line with the entry of which value is not the wanted value or a different entry. Therefore first all empty lines are output first which are perhaps above the current entry line in section of interest.
Next one more command FOR is used to split up the current line and get assigned to the loop variable J the string left to first equal sign (after zero or more equal signs at beginning of the line which hopefully no INI file contains ever).
If the current line in section of interest is case-insensitive not the entry of which value should be updated, the entry line is simply output. Otherwise the entry line to update is found and so the line is output with the entry name and the wanted value before the environment variable EntryUpdate is redefined with string value 2 and environment variable CopyLines is defined to copy all other lines simply to the temporary file.
Everything output inside the FOR loop processing the lines of the INI file is redirected into the temporary file.
It could be that current INI file does not contain the section of interest at all which can be found out after processing all lines by checking if the environment variable EntryUpdate is still not defined. In this case the section and the entry with the wanted value is appended to the temporary file with ignoring environment variable EmptyLines to ignore all empty lines at bottom of the INI file.
It is also possible that the last section in the INI file is the section of interest, but the entry with the value to update is not found up to the end of the file. So one more IF condition is used to check if it is necessary to append at end of the file with ignoring all empty lines at end of the file the entry line with the wanted value.
Now the temporary file contains the lines which the just processed INI file should contain after finishing batch file execution. So the temporary file is moved to INI file directory with replacing the INI file if it is not read-only or write-protected by file permissions or write-protected by file access permissions because of being currently opened by the application using this INI file.
If the current INI file cannot be replaced with the wanted modification, the temporary file is deleted and this error condition is recorded in the log file. Otherwise the current INI file is successfully updated and that is recorded in the log file with the information how the update was done by the batch file.
Additional information
It should be clear now that the Windows command processor is not designed for file contents modification because it is designed for running commands and executables. There are lots of script interpreters more suitable for this task than cmd.exe.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
del /?
echo /?
endlocal /?
findstr /?
for /?
goto /?
if /?
move /?
set /?
setlocal /?
See also: Where does GOTO :EOF return to?
What I ended up with:
#echo off
setlocal EnableExtensions Disabledelayedexpansion
set TODAY=%Date:~4,2%-%Date:~7,2%-%Date:~10,4%
set Target=SMSStart.ini
set SectionName=[Maintenance]
set EntryName=Reboot
set EntryValue=1
set LogFile=./INI_VAL_CHANGE_%TODAY%_Log.txt
set TempFile=%TEMP%\%~n0.tmp
set ListFile=PCList.txt
if not exist "./%ListFile%" (
echo ERROR: List file %ListFile%" not found.>"%LogFile%"
goto EndBatch
)
XCOPY /Y %~dp0*_log.txt %~dp0%LOGS>nul
ERASE %~dp0*_log.txt /Q
Echo PC list: %ListFile%
Echo Run: %DATE%:%TIME%
Echo PC list: %ListFile%>>%LogFile%
Echo Run: %DATE%:%TIME%>>%LogFile%
Echo Target File: %Target%
Echo Target File: %Target%>>%LogFile%
Echo Search Key: %EntryName%= Update: "%EntryName%=%EntryValue%"
Echo Search Key: %EntryName%= Update: "%EntryName%=%EntryValue%">>%LogFile%
Echo =============================
Echo =============================>>%LogFile%
for /F "usebackq delims=" %%I in ("%ListFile%") do call :UpateIniFile "\\%%I\Storeman\%Target%"
goto EndBatch
:UpateIniFile
set "EmptyLines="
set "EntryUpdate="
set "CopyLines="
(for /F delims^=^ eol^= %%I in ('%SystemRoot%\System32\findstr.exe /N "^" %1 2^>nul') do (
set "Line=%%I"
setlocal EnableDelayedExpansion
if defined CopyLines (
echo(!Line:*:=!
endlocal
) else if not defined EntryUpdate (
echo(!Line:*:=!
if /I "!Line:*:=!" == "!SectionName!" (
endlocal
set "EntryUpdate=1"
)
) else (
if /I "!Line:*:=!" == "!EntryName!=!EntryValue!" (
endlocal
goto ValueExists
)
if "!Line:*:=!" == "" (
endlocal
set /A EmptyLines+=1
) else (
set "Line=!Line:*:=!"
if "!Line:~0,1!!Line:~-1!" == "[]" (
echo !EntryName!=!EntryValue!
if defined EmptyLines for /L %%J in (1,1,!EmptyLines!) do echo(
echo !Line!
endlocal
set "EntryUpdate=3"
set "CopyLines=1"
) else (
if defined EmptyLines for /L %%L in (1,1,!EmptyLines!) do echo(
for /F delims^=^=^ eol^= %%J in ("!Line!") do (
if /I not "%%~J" == "!EntryName!" (
echo !Line!
endlocal
) else (
echo !EntryName!=!EntryValue!
endlocal
set "EntryUpdate=2"
set "CopyLines=1"
)
)
set "EmptyLines="
)
)
)
))>"%TempFile%"
if not defined EntryUpdate (
>>"%TempFile%" echo %SectionName%
>>"%TempFile%" echo %EntryName%=%EntryValue%
set EntryUpdate=4
)
if %EntryUpdate% == 1 (
>>"%TempFile%" echo %EntryName%=%EntryValue%
set "EntryUpdate=3"
)
move /Y "%TempFile%" %1 2>nul
if errorlevel 1 (
echo Failed to update: %1 : !time:~0,8!
echo Failed to update: %1 : !time:~0,8!>>"%LogFile%"
del "%TempFile%"
goto :EOF
)
if %EntryUpdate% == 2 (
echo !EntryName!=!EntryValue! updated in: %1 : !time:~0,8!
echo !EntryName!=!EntryValue! updated in: %1 : !time:~0,8!>>"%LogFile%"
goto :EOF
)
if %EntryUpdate% == 3 (
echo !EntryName!=!EntryValue! added to: %1 : !time:~0,8!
echo !EntryName!=!EntryValue! added to: %1 : !time:~0,8!>>"%LogFile%"
goto :EOF
)
if %EntryUpdate% == 4 (
echo Section+ !EntryName!=!EntryValue! to: %1 : !time:~0,8!
echo Section+ !EntryName!=!EntryValue! to: %1 : !time:~0,8!>>"%LogFile%"
goto :EOF
)
:ValueExists
echo !EntryName!=!EntryValue! existed in: %1 : !time:~0,8!
echo !EntryName!=!EntryValue! existed in: %1 : !time:~0,8!>>"%LogFile%"
del "%TempFile%"
goto :EOF
:EndBatch
endlocal
pause
#Mofi - Trying to shore up the code as per your suggestions:
move /Y "%TempFile%" %1 2>nul
if errorlevel 1 (
echo Failed to update: %1 : !time:~0,8!
echo Failed to update: %1 : !time:~0,8!>>"%LogFile%"
del "%TempFile%"
goto :EOF
Trying to figure out why when the batch is unable to reach a PC on the network and outputs an errorlevel 1, the script returns !time:~0,8! rather than the actual timestamp?
Here is the current version I am working from:
#echo off
setlocal EnableExtensions Disabledelayedexpansion
set "TODAY=%Date:~4,2%-%Date:~7,2%-%Date:~10,4%"
set "Target=SMSStart.ini"
set "TarDIR=Storeman"
set "SectionName=[Maintenance]"
set "EntryName=Reboot"
set "EntryValue=1"
set "LogFile=.\INI_VAL_CHANGE_%TODAY%_Log.txt"
set "TempFile=%TEMP%\%~n0.tmp"
set "ListFile=PCList.txt"
if not exist ".\%ListFile%" (
echo ERROR: List file %ListFile%" not found.>"%LogFile%"
goto EndBatch
)
XCOPY /Y "%~dp0*_log.txt" %~dp0%LOGS>nul
ERASE "%~dp0*_log.txt" /Q
Echo INI File Updater
Echo INI File Updater>>%LogFile%
Echo ==============================
Echo ==============================>>%LogFile%
Echo PC list: %ListFile%
Echo Run: %DATE%:%TIME%
Echo PC list: %ListFile%>>%LogFile%
Echo Run: %DATE%:%TIME%>>%LogFile%
Echo Target File: %Target%
Echo Target File: %Target%>>%LogFile%
Echo Target DIR: \%TarDIR%
Echo Target DIR: \%TarDIR%>>%LogFile%
Echo INI Section : %SectionName%
Echo INI Section : %SectionName%>>%LogFile%
Echo Search Key: %EntryName%= Update: "%EntryName%=%EntryValue%"
Echo Search Key: %EntryName%= Update: "%EntryName%=%EntryValue%">>%LogFile%
Echo ==============================
Echo.
Echo ==============================>>%LogFile%
Echo.>>%LogFile%
for /F "usebackq delims=" %%I in ("%ListFile%") do call :UpateIniFile "\\%%I\%TarDIR%\%Target%"
goto EndBatch
:UpateIniFile
set "EmptyLines="
set "EntryUpdate="
set "CopyLines="
(for /F delims^=^ eol^= %%I in ('%SystemRoot%\System32\findstr.exe /N "^" %1 2^>nul') do (
set "Line=%%I"
setlocal EnableDelayedExpansion
if defined CopyLines (
echo(!Line:*:=!
endlocal
) else if not defined EntryUpdate (
echo(!Line:*:=!
if /I "!Line:*:=!" == "!SectionName!" (
endlocal
set "EntryUpdate=1"
)
) else (
if /I "!Line:*:=!" == "!EntryName!=!EntryValue!" (
endlocal
goto ValueExists
)
if "!Line:*:=!" == "" (
endlocal
set /A EmptyLines+=1
) else (
set "Line=!Line:*:=!"
if "!Line:~0,1!!Line:~-1!" == "[]" (
echo !EntryName!=!EntryValue!
if defined EmptyLines for /L %%J in (1,1,!EmptyLines!) do echo(
echo !Line!
endlocal
set "EntryUpdate=3"
set "CopyLines=1"
) else (
if defined EmptyLines for /L %%L in (1,1,!EmptyLines!) do echo(
for /F delims^=^=^ eol^= %%J in ("!Line!") do (
if /I not "%%~J" == "!EntryName!" (
echo !Line!
endlocal
) else (
echo !EntryName!=!EntryValue!
endlocal
set "EntryUpdate=2"
set "CopyLines=1"
)
)
set "EmptyLines="
)
)
)
))>"%TempFile%"
if not defined EntryUpdate (
>>"%TempFile%" echo %SectionName%
>>"%TempFile%" echo %EntryName%=%EntryValue%
set EntryUpdate=4
)
if %EntryUpdate% == 1 (
>>"%TempFile%" echo %EntryName%=%EntryValue%
set "EntryUpdate=3"
)
move /Y "%TempFile%" %1 2>nul
if errorlevel 1 (
echo Failed to update: %1 : !time:~0,8!
echo Failed to update: %1 : !time:~0,8!>>"%LogFile%"
del "%TempFile%"
goto :EOF
)
if %EntryUpdate% == 2 (
echo !EntryName!=!EntryValue! updated in: %1 : !time:~0,8!
echo !EntryName!=!EntryValue! updated in: %1 : !time:~0,8!>>"%LogFile%"
goto :EOF
)
if %EntryUpdate% == 3 (
echo !EntryName!=!EntryValue! added to: %1 : !time:~0,8!
echo !EntryName!=!EntryValue! added to: %1 : !time:~0,8!>>"%LogFile%"
goto :EOF
)
if %EntryUpdate% == 4 (
echo Section+ !EntryName!=!EntryValue! to: %1 : !time:~0,8!
echo Section+ !EntryName!=!EntryValue! to: %1 : !time:~0,8!>>"%LogFile%"
goto :EOF
)
:ValueExists
echo !EntryName!=!EntryValue! existed in: %1 : !time:~0,8!
echo !EntryName!=!EntryValue! existed in: %1 : !time:~0,8!>>"%LogFile%"
del "%TempFile%"
goto :EOF
:EndBatch
Echo.
Echo.>>%LogFile%
Echo ---COMPLETE---
Echo ---COMPLETE--->>"%LogFile%"
Echo.
endlocal
pause

How to package all my functions in a batch file as a seperate file?

My question is related to this question. I have several bunch of actions that need to be executed from a batch file and I would like to model them as functions and call from a master sequence. From the above question, it is clear that I can do this with the call syntax
call:myDosFunc
My question is that can I place all these functions in a seperate batch file (functions.bat) and somehow 'include' that in the main batch file and call them? Another option would be to utilize the possibility to invoke functions.bat from main.bat with the call syntaxt, but I'm not sure if I can invoke that with a specific function instead of executing the whole batch file.
In short, I'm looking for something similar to the C programming world where my functions reside in a DLL and the main program contains only the high-level logic and calls the functions from the DLL.
I think a routing function in the beginning of a batch file is not that ugly.
You can use something like this at the beginning of a "libbatch.cmd"
call:%*
exit/b
:func1
[do something]
exit/b
:func2
[do something else]
exit/b
Now you can call func2 from another batch with:
call libbatch.cmd func2 params1 param2 ... paramN
this also preserves the errorlevel "thrown" by func2 (exit/b hands over the current errorlevel).
With the second call instead of a goto you ensure that "%1"=="param1" and not func2.
And call will not terminate the batch file if the label does not exist, it simply sets the errorlevel to 1 and puts an error message to 2 (errorout), which could be redirected to nul.
Explanation: %* contains all parameters, so in the example the first line translates to:
call:func2 params1 param2 ... paramN
Here is a simple example of how it might be done.
The function script is called with the name of the function as the first argument, and function arguments as arg2, arg3, ...
Assuming it is called properly, the script shifts the arguments and performs GOTO to the original arg1. Then the function has its arguments starting with the new arg1. This means you can take already written routines and plop them in the utility without having to worry about adjusting the parameter numbers.
The script gives an error if the function argument is not supplied, or if the function argument does not match a valid label within the script.
#echo off
if "%~1" neq "" (
2>nul >nul findstr /rc:"^ *:%~1\>" "%~f0" && (
shift /1
goto %1
) || (
>&2 echo ERROR: routine %~1 not found
)
) else >&2 echo ERROR: missing routine
exit /b
:test1
echo executing :test1
echo arg1 = %1
exit /b
:test2
echo executing :test2
echo arg1 = %1
echo arg2 = %2
exit /b
:test3
echo executing :test3
echo arg1 = %1
echo arg2 = %2
echo arg3 = %3
exit /b
I prefer the GOTO approach that I used above. Another option is to use CALL instead, as Thomas did in his answer.
For a working example of a usefull library of batch functions that uses the CALL technique, see CHARLIB.BAT, a library of routines for processing characters and strings within a batch file. A thread showing development of the library is available here
I wrote CharLib.bat a few years ago. Were I to write it today, I would probably use GOTO instead of CALL.
The problem with introducing a CALL is that it creates issues when passing string literals as parameters. The extra CALL means that a string literal containing % must have the percents doubled an extra time. It also means unquoted poison characters like & and | would need to be escaped an extra time. Those two issues can be addressed by the caller. But the real problem is that each CALL doubles up quoted carets: "^" becomes "^^". There isn't a good way to work around the caret doubling problem.
The problems with the extra CALL don't impact CharLib.bat because string values are passed by reference (variable name) and not as string literals.
The only down side to using GOTO with SHIFT /1 is that you cannot use %0 to get the name of the currently executing routine. I could have used SHIFT without the /1, but then you wouldn't be able to use %~f0 within a routine to get the full path to the executing batch file.
You can use this format - and launch it like this:
call mybat :function4 parameternumber2 parameternumber3
this would be one way of using a library
#echo off
goto %1
:function1
REM code here - recursion and subroutines will complicate the library
REM use random names for any temp files, and check if they are in use - else pick a different random name
goto :eof
:function2
REM code here - recursion and subroutines will complicate the library
REM use random names for any temp files, and check if they are in use - else pick a different random name
goto :eof
:function3
REM code here - recursion and subroutines will complicate the library
REM use random names for any temp files, and check if they are in use - else pick a different random name
goto :eof
:function4
REM code here - recursion and subroutines will complicate the library
REM use random names for any temp files, and check if they are in use - else pick a different random name
goto :eof
You may use an interesting trick that avoids most of the problems that other methods have when they try to make the library functions available to the main program and it is much faster. The only requisites to use this trick are:
The library functions must be called from inside a code block in the main file, and
In that code block no main file functions are called.
The trick consist in "switch the context" of the running Batch file in a way that the library file becomes the running Batch file; this way, all the functions in the library file becomes available to the main code block with no additional processing. Of course, the "context" of the running Batch file must be switched back to the main file before the code block ends.
The way to "switch the context" is renaming the library file with the same name of the running main file (and renaming the main file to another name). For example:
(
rem Switch the context to the library file
ren main.bat orig-main.bat
ren library.bat main.bat
rem From this point on, any library function can be called
. . . .
rem Switch back the context to the original one
ren main.bat library.bat
ren orig-main.bat main.bat
)
EDIT: Working example added
I copied the example below from the screen. Tested in Windows 8, but I also used this method in Win XP:
C:\Users\Antonio\Documents\test
>type main.bat
#echo off
(
rem Switch the context to the library file
ren main.bat orig-main.bat
ren library.bat main.bat
rem From this point on, any library function can be called, for example:
echo I am Main, calling libFunc:
call :libFunc param1
echo Back in Main
rem Switch back the context to the original one
ren main.bat library.bat
ren orig-main.bat main.bat
)
C:\Users\Antonio\Documents\test
>type library.bat
:libFunc
echo I am libFunc function in library.bat file
echo My parameter: %1
exit /B
C:\Users\Antonio\Documents\test
>main
I am Main, calling libFunc:
I am libFunc function in library.bat file
My parameter: param1
Back in Main
I'm not sure of the context of the original question, but this might be a case where switching to something like WSH with VBScript or WPS, or any other console-capable scripting other than batch files. I will answer the original question, but first.. a little background and understanding..
The command line/console mode of DOS and Windows is usually either COMMAND.COM or CMD.EXE, which isn't well-geared towards scripting/programming logic. Rather, they're geared towards executing commands and programs, and batch files were added to commonly used sequences of commands to wrapped up in a single typed command. For example, you may have an old DOS game you play that needs the following commands every time, so it's packaged as a batch file:
#EHO OFF
#REM Load the VESA driver fix..
VESAFIX.EXE
#REM Load the joystick driver..
JOYSTICK.COM
#REM Now run the game
RUNGAME.EXE
Many people tend to view the entire batch file as one atomic unit--But it's not. The command interpreter (COMMAND.COM or CMD.EXE) will merely act like you manually typed those lines, one by one, each time you run the batch file. It really has no solid concept of lexica and scoping like a regular programming/scripting language would--that is, it doesn't maintain much extra meta-data like a call stack, et cetera. What little it does maintain is more added like it's an afterthought rather than built-in to batch file from the beginning.
Once you shift the gears in your thinking, however, you can often overcome this limitation using various tricks and techniques to emulate more powerful scripting/programming languages; But you still have to remember that batch files are still going to be limited, regardless.
Anyhow, one technique of using a batch file library is to create a batch file where the first parameter is used to indicate which function is being called:
CALL BATLIB.BAT FunctionName Parameter1 Parameter2 ...
This works well enough when the library is written with this in mind, so it'll know to skip the first argument, et cetera.
Using more modern version of CMD.EXE in Windows' systems allows the use of ":labels" in the CALL syntax, which can be useful if you want to limit the parameter scope (which allows you to use %* for "all arguments", for example), like this:
CALL :LABEL Parameter1 Paramater2 ...
(from within the same batch file or ...)
CALL BATLIB.BAT :LABEL Parameter1 Parameter2 ...
A few notes about that, though.. In the first form, the :LABEL must be already within the current batch file. It will create a new "batch context" within CMD.EXE where %*, %1, %2, et cetera are matched to the parameters. But you'll also have to provide some kind of return/exit logic to return/exit from that context back to the calling context.
In the second form, CMD.EXE does not really recognize that you are passing it a label, so your batch file library will have to expect it and handle it:
#ECHO OFF
CALL %*
This works because the command interpreter replaces the %* before it even attempts to parse the CALL command, so after variable expansion, the CALL command would see the :LABEL as if it were hard-coded. This also creates a situation where CMD.EXE creates yet another batch context, so you'll have to make sure to return/exit from that context twice: Once for the current library context, again to get back to the original CALL.
There are still other ways to do a batch file library, mixing and matching the above techniques, or using even more complex logic, using GOTO's, et cetera. This is actually such a complex topic that there are entire sections of books written on the topic, much more than I want to type in a simple answer here!
And so far, I've mostly ignored other issues you will encounter: What if the CALL label does not exist? How will it be handled? What about environment variable expansion? when does it happen? How can you prevent it from happening too soon? What about using special DOS characters in the arguments/parameters? For example, how does the interpreter see a line like: CALL :ProcessPath %PATH%? (The answer to that is that CMD.EXE replaces the entire %PATH% before it even processes the CALL command. This can create issues if your path has spaces in it which can trip up how CALL processes the entire thing, as many Windows' %PATH% variables do.. C:\Program Files.. for example..)
As you can see, things are getting complicated and messy very quickly.. And you have to stop thinking like a programmer and start thinking like COMMAND.COM/CMD.EXE, which pretty much only sees one single line at a time, not the whole batch file as an atomic unit. In fact, here's an example to help you really grasp the way it works..
Create a folder, C:\testing, and put the following batch, file called "oops.bat", in it:
#ECHO OFF
ECHO Look mom, no brain!
PAUSE
ECHO Bye mom!
Now open a console window and run it, but let it sit there at the PAUSE:
C:\testing>oops.bat
Look mom, no brain!
Press any key to continue . . .
While it's sitting at the PAUSE, open oops.bat in your text editor and change it to:
#ECHO OFF
ECHO Look mom, no brain!?
ECHO Oops!
PAUSE
ECHO Bye mom!
Save it, then switch back to your console window and press any key to continue running the batch file:
'ops!' is not recognized as an internal or external command,
operable program or batch file.
Press any key to continue . . .
Bye mom!
c:\testing>
Whoa.. see that error there? That happened because we edited the batch file while it was still being run by CMD.EXE, but our edits changed where in the batch file CMD.COM thought it was. Internally, CMD.EXE maintains a file pointer indicating the start of the next character to process, which in this case would have been the byte right after the line with the PAUSE (and the CRLF) on it. But when we edited it, it changed the location of the next command in the batch file, but CMD.EXE's pointer was still in the same place. In this case, it was pointing to the byte position right in the middle of the "ECHO Oops!" line, so it tried to process "ops!" as a command after the PAUSE.
I hope this makes it clear that COMMAND.COM/CMD.EXE will always see your batch files as a stream of bytes, not as a logical block, subroutines, et cetera, like a script language or compiler would. This is why batch file libraries are so limited. It makes it impossible to "import" a library in to a currently running batch file.
Oh, and I just had another thought.. In modern Windows' CMD.EXE, you can always create a batch file which creates a temporary batch file on the fly, then calls it:
#ECHO OFF
SET TEMPBAT=%TEMP%\TMP%RANDOM:~0,1%%RANDOM:~0,1%%RANDOM:~0,1%%RANDOM:~0,1%.BAT
ECHO #ECHO OFF > %TEMPBAT%
ECHO ECHO Hi Mom! I'm %TEMPBAT%! >> %TEMPBAT%
ECHO Hello, world, I'm %~dpnx0!
CALL %TEMPBAT%
DEL %TEMPBAT%
This effectively creates a temporary batch file in your temporary directory, named TMP####.BAT (where the #'s are replaced by random numbers; The %RANDOM:~0,1% means take the first digit of the number returned by %RANDOM%--we only wanted one single digit here, not the full number that RANDOM returns..), then ECHO's "Hello, World," followed by it's own full name (the %~dpnx0 part), CALLs the temporary batch file, which in turn ECHO's "Hi Mom!" followed by it's own [random] name, then returns to the original batch file so it can do whatever cleanup is needs, such as deleting the temporary batch file in this case.
Anyhow, as you can see by the length of this post, this topic really is not a simple one. There's dozens or more web pages out on the web with tons of batch file tips, tricks, et cetera, many of which go in to depth about how to work with them, create batch file libraries, what to watch out for, how to pass arguments by reference vs. by value, how to manage when and where variables get expanded, and so on.
Do a quick Google search for "BATCH FILE PROGRAMMING" to find many of them, and you can also check out Wiki and WikiBooks, SS64.com, robvanderwoude.com and even DMOZ's directory with more resources.
Good luck!
Here's a cmd batch script, which imports files or files in folders (recursivelly) into the main script:
#echo off
REM IMPORT - a .cmd utility for importing subroutines into the main script
REM !!! IN ORDER TO FUNCTION CORRECTLY: !!!
REM !!! IMPORT MUST BE CALLED INSIDE A DISABLED DELAYED EXPANSION BLOCK/ENVIRONMENT (DEFAULT) !!!
rem \\// Define import file mask here:
rem If mask is not defined outside "import.cmd":
if not defined mask (
set "mask=*.cmd; *.bat"
)
rem //\\ Define import file mask here:
rem Detect if script was started from command line:
call :DetectCommandLine _not_started_from_command_line
if "%~1" == "/install" (
set "import_path=%~dp0"
call :EscapePathString import_path import_path_escaped
)
if not "%~1" == "" (
if /i not "%~1" == "end" (
if "%~1" == "/?" (
call :DisplayHelp
) else (
if "%~1" == "/install" (
echo Installing
set "_first_time="
rem This should get into the Autorun registry key: path %path%;"...\import.cmd"
rem If you want, other commands can be added to the left or to the right of this command, unified as a block with a "&" command operator
REG ADD "HKCU\Software\Microsoft\Command Processor" /v AutoRun /t REG_SZ /d "path %%path%%;"""%import_path_escaped%""||(
echo ERROR: Cannot install import: cannot write to the registry^!
echo You can try to manually add the "import.cmd" path in the "PATH" variable or use pushd ^(see help^).
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
)
echo.
echo Done. The install directory was set to:
echo "%import_path%"
echo and was added to the PATH environment variable. You can add other desired programs into this directory.
echo.
echo Please note that the console needs to be closed and reopened in order for the changes to take effect^!
echo.
) else (
if not defined _first_time (
set _first_time=defined
set /a count=0
if "%_DISPLAY_WARNING%" == "true" (
echo.
echo WARNING: CMD_LIBRARY was reset to "", because previously it gave an error^!
echo.
)
echo Loading list to import...
)
REM build import files list
set /a count+=1
call set "_import_list_%%count%%=%%~1"
)
)
)
) else (
call :DisplayHelp
)
if /i "%~1" == "end" (
set "_first_time="
echo Done.
echo Analyzing...
rem set "_main_program=%~dpnx2"
if not exist "%~dpnx2" (
echo ERROR: Second parameter, after "import end", must be a valid file path - see help^!>>&2
rem Clean up
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
)
)
if /i "%~1" == "end" (
set "_main_batch_script=%~dpnx2"
rem \\// Define output filename here:
rem set "_output_filename=tmp0001_%~n2.cmd"
set "_output_filename=tmp0001.cmd"
rem //\\ Define output filename here:
)
if /i "%~1" == "end" (
rem Check all paths not to be UNC:
setlocal EnableDelayedExpansion
set "_error=false"
call :TestIfPathIsUNC _main_batch_script _result
if "!_result!" == "true" (
set "_error=true"
echo.
echo ERROR: UNC paths are not allowed: Second parameter, after "import end", must not be a UNC path^^^! Currently it is: "!_main_batch_script!">>&2
)
set "_CMD_LIBRARY_error=false"
call :TestIfPathIsUNC CMD_LIBRARY _result
if "!_result!" == "true" (
set "_error=true"
set "_CMD_LIBRARY_error=true"
echo.
echo ERROR: UNC paths are not allowed: CMD_LIBRARY variable must not contain a UNC path^^^! Currently, it is set to: "!CMD_LIBRARY!".>>&2
)
for /l %%i in (1,1,!count!) do (
call :TestIfPathIsUNC _import_list_%%i _result
if "!_result!" == "true" (
set "_error=true"
echo.
echo ERROR: UNC paths are not allowed: The import path: "!_import_list_%%i!" is a UNC path^^^!>>&2
)
)
if "!_error!" == "true" (
echo.
echo Errors were ecountered^^^!
if "!_CMD_LIBRARY_error!" == "true" (
endlocal
set "_CMD_LIBRARY_error=true"
) else (
endlocal
set "_CMD_LIBRARY_error=false"
)
rem Clean up
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
) else (
endlocal
set "_CMD_LIBRARY_error=false"
)
)
if /i "%~1" == "end" (
rem Check all paths not to contain "*" and "?" wildcards:
set "_asterisk=*"
set "_qm=?"
setlocal EnableDelayedExpansion
set "_error=false"
call :TestIfStringContains _main_batch_script _asterisk _result1
call :TestIfStringContains _main_batch_script _qm _result2
if "!_result1!" == "true" (
set "_error=true"
)
if "!_result2!" == "true" (
set "_error=true"
)
if "!_error!" == "true" (
echo.
echo ERROR: The use of "*" or "?" wildcards is not supported by import: Second parameter, after "import end", must not contain "*" or "?" wildcards^^^! Currently it is: "!_main_batch_script!". Instead, you can set the mask with a set of name and extension masks sepparated by semicolon, like: set mask="*.cmd; *.bat">>&2
)
set "_CMD_LIBRARY_error=false"
call :TestIfStringContains CMD_LIBRARY _asterisk _result1
call :TestIfStringContains CMD_LIBRARY _qm _result2
if "!_result1!" == "true" (
set "_error=true"
)
if "!_result2!" == "true" (
set "_error=true"
)
if "!_error!" == "true" (
set "_error=true"
set "_CMD_LIBRARY_error=true"
echo.
echo ERROR: The use of "*" or "?" wildcards is not supported by import: CMD_LIBRARY variable must not contain "*" or "?" wildcards^^^! Currently, it is set to: "!CMD_LIBRARY!". Instead, you can set the mask with a set of name and extension masks sepparated by semicolon, like: set mask="*.cmd; *.bat">>&2
)
for /l %%i in (1,1,!count!) do (
call :TestIfStringContains _import_list_%%i _asterisk _result1
call :TestIfStringContains _import_list_%%i _qm _result2
if "!_result1!" == "true" (
set "_error=true"
)
if "!_result2!" == "true" (
set "_error=true"
)
if "!_error!" == "true" (
set "_error=true"
echo.
echo ERROR: The use of "*" or "?" wildcards is not supported by import: The import path: "!_import_list_%%i!" must not contain "*" or "?" wildcards^^^! Instead, you can set the mask with a set of name and extension masks sepparated by semicolon, like: set mask="*.cmd; *.bat">>&2
)
)
if "!_error!" == "true" (
echo.
echo Errors were ecountered^^^!
if "!_CMD_LIBRARY_error!" == "true" (
endlocal
set "_CMD_LIBRARY_error=true"
) else (
endlocal
set "_CMD_LIBRARY_error=false"
)
rem Clean up
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
) else (
endlocal
set "_CMD_LIBRARY_error=false"
)
)
if /i "%~1" == "end" (
pushd "%~dp2"
call set "_output_dir=%%CD%%"
popd
)
if /i "%~1" == "end" (
if not defined CMD_LIBRARY (
set CMD_LIBRARY_CASE=IMPORT.CMD
set "BASE=%~dpnx0\.."
pushd "%~dpnx0"\..\
REM \\// Define CMD LIBRARY here ("." is relative to "import.cmd" parent directory):
REM if CMD_LIBRARY is not defined outside import.cmd, "." (used here) is related to import.cmd parent directory:
set "CMD_LIBRARY=."
REM //\\ Define CMD LIBRARY here ("." is relative to "import.cmd" parent directory):
) else (
set CMD_LIBRARY_CASE=MAIN.CMD
set "BASE=%~dpnx2\.."
REM if CMD_LIBRARY is defined outside the "import.cmd" script, "." (used in CMD_LIBRARY) is related to "main program" parent directory
pushd "%~dpnx2"\..
)
)
if /i "%~1" == "end" (
call :DeQuoteOnce CMD_LIBRARY CMD_LIBRARY
call set "CMD_LIBRARY_ORIGINAL=%%CMD_LIBRARY%%"
call :TestIfPathIsUNC CMD_LIBRARY_ORIGINAL _result
setlocal EnableDelayedExpansion
if "!_result!" == "true" (
set "_error=true"
echo.
echo ERROR: UNC paths are not allowed: CMD_LIBRARY variable must not contain a UNC path^^^! Currently, it is set to: "!CMD_LIBRARY_ORIGINAL!".>>&2
echo.
echo Errors were ecountered^^^!
endlocal
set "_CMD_LIBRARY_error=true"
rem Clean up
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
) else (
endlocal
set "_CMD_LIBRARY_error=false"
)
call pushd "%%CMD_LIBRARY%%" >nul 2>nul&&(
call set "CMD_LIBRARY=%%CD%%"
)||(
call echo ERROR: Could not access directory CMD_LIBRARY=^"%%CMD_LIBRARY%%^"^!>>&2
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
popd
exit /b 1
)
)
if /i "%~1" == "end" (
setlocal EnableDelayedExpansion
set _error=false
pushd "!BASE!"
echo.
if "!CMD_LIBRARY_CASE!" == "IMPORT.CMD" (
echo CMD_LIBRARY was defined as: "!CMD_LIBRARY_ORIGINAL!" in the file "import.cmd" and was expanded to: "!CMD_LIBRARY!"
) else (
if "!CMD_LIBRARY_CASE!" == "MAIN.CMD" (
echo CMD_LIBRARY was defined as: "!CMD_LIBRARY_ORIGINAL!" outside "import.cmd" file "%~nx2" and was expanded to: "!CMD_LIBRARY!"
)
)
for /l %%i in (1,1,!count!) do (
if not exist "!_import_list_%%i!" (
if not exist "!CMD_LIBRARY!\!_import_list_%%i!" (
rem if first time:
if not "!_error!" == "true" (
echo.
echo Directory of "!CMD_LIBRARY!":
)
echo.
echo ERROR: element "!_import_list_%%i!" does not exist or is not accessible as a standalone file/dir or as a file/dir in the directory contained by "CMD_LIBRARY" variable^^^!>>&2
set _error=true
)
)
)
popd
if "!_error!" == "true" (
endlocal
rem Clean up
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
) else (
endlocal
)
echo OK
echo.
)
set "_error=false"
if /i "%~1" == "end" (
echo Output file is: "%_output_dir%\%_output_filename%"
echo.
echo Importing...
echo.
(
type nul>"%_output_dir%\%_output_filename%"
) 2>nul||(
echo ERROR: Could not write to file: "%_output_dir%\%_output_filename%"^!>>&2
rem Clean up
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
)
echo Importing main script "%_main_batch_script%"
(
echo #set _import=defined
echo #REM Timestamp %date% %time%
echo.
)>>"%_output_dir%\%_output_filename%"
(
(
type "%_main_batch_script%"
)>>"%_output_dir%\%_output_filename%"
) 2>nul||(echo ERROR: Could not read file^!&set "_error=true">>&2)
(
echo.
echo.
)>>"%_output_dir%\%_output_filename%"
echo.
echo Directory of "%CMD_LIBRARY%":
if not "%CMD_LIBRARY_CASE%" == "MAIN.CMD" (
pushd "%BASE%"
)
if not defined mask (
rem If mask is not defined, import all file types:
set "mask=*"
)
for /l %%i in (1,1,%count%) do (
call set "_import_list_i=%%_import_list_%%i%%"
call :ProcedureImportCurrentFile
)
if not "%CMD_LIBRARY_CASE%" == "MAIN.CMD" (
popd
)
)
if "%~1" == "end" (
if "%_error%" == "true" (
echo.
echo Errors were ecountered^!
rem Clean up
call :CleanUp
if not "%_not_started_from_command_line%" == "0" (
call :PressAnyKey Press any key to exit...
echo.
)
exit /b 1
) else (
echo Done^!
)
call popd
popd
rem Clean up
call :CleanUp
rem Detect if script was started from command line:
call :DetectCommandLine _not_started_from_command_line
)
if "%~1" == "end" (
if "%_not_started_from_command_line%" == "0" (
set "_import="
) else (
echo.
echo Starting program...
echo.
rem Start "resulting" program:
"%_output_dir%\%_output_filename%"
)
)
goto :eof
REM \\\/// Next subroutines use jeb's syntax for working with delayed expansion: \\\///
:CleanUp
(
setlocal EnableDelayedExpansion
)
(
endlocal
if "%_CMD_LIBRARY_error%" == "true" (
set "CMD_LIBRARY="
set "_DISPLAY_WARNING=true"
) else (
set "_DISPLAY_WARNING=false"
)
set "_first_time="
for /l %%i in (1,1,%count%) do (
set "_import_list_%%i="
)
rem optional:
set "count="
set "import_path="
rem set "_output_dir="
set "_error="
set "_main_batch_script="
rem set "_output_filename="
rem set "_import="
set "mask="
exit /b
)
:GetStrLen - by jeb - adaptation
(
setlocal EnableDelayedExpansion
set "s=!%~1!#"
set "len=0"
for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
if "!s:~%%P,1!" NEQ "" (
set /a "len+=%%P"
set "s=!s:~%%P!"
)
)
)
(
endlocal
set "%~2=%len%"
exit /b
)
:EscapePathString
(
setlocal EnableDelayedExpansion
set "string=!%~1!"
call :GetStrLen string string_len
set /a string_len-=1
for /l %%i in (0,1,!string_len!) do (
rem escape "^", "(", ")", "!", "&"
if "!string:~%%i,1!" == "^" (
set "result=!result!^^^^"
) else (
if "!string:~%%i,1!" == "(" (
set "result=!result!^^^("
) else (
if "!string:~%%i,1!" == ")" (
set "result=!result!^^^)"
) else (
if "!string:~%%i,1!" == "^!" (
set "result=!result!^^^!"
) else (
if "!string:~%%i,1!" == "&" (
set "result=!result!^^^&"
) else (
if "!string:~%%i,1!" == "%%" (
set "result=!result!%%"
) else (
set "result=!result!!string:~%%i,1!"
)
)
)
)
)
)
)
)
(
endlocal
set "%~2=%result%"
exit /b
)
:PressAnyKey
set /p=%*<nul
pause>nul
goto :eof
:DeQuoteOnce
(
setlocal EnableDelayedExpansion
set "string=!%~1!"
if "!string!" == """" (
endlocal
set "%~2=%string%"
exit /b
)
rem In order to work with " we replace it with a special character like < > | that is not allowed in file paths:
set "string=!string:"=^<!"
if "!string:~0,1!" == "<" (
if "!string:~-1,1!" == "<" (
set "string=!string:~1,-1!"
)
)
rem restore " in string (replace < with "):
set "string=!string:<="!"
)
(
endlocal
set "%~2=%string%"
exit /b
)
:TestIfPathIsUNC
(
setlocal EnableDelayedExpansion
set "_current_path=!%~1!"
set "_is_unc_path=true"
if defined _current_path (
if "!_current_path:\\=!" == "!_current_path!" (
set "_is_unc_path=false"
)
) else (
set "_is_unc_path=false"
)
)
(
endlocal
set "%~2=%_is_unc_path%"
exit /b
)
:TestIfStringContains
(
setlocal EnableDelayedExpansion
echo "!%~1!"|find "!%~2!">nul 2>nul
set "_error_code=!ERRORLEVEL!"
)
(
endlocal
if "%_error_code%" == "0" (
set "%~3=true"
) else (
set "%~3=false"
)
exit /b
)
REM ///\\\ The subroutines above use jeb's syntax for working with delayed expansion: ///\\\
:DetectCommandLine
setlocal
rem Windows: XP, 7
for /f "tokens=*" %%c in ('echo "%CMDCMDLINE%"^|find "cmd /c """ /c') do (
set "_not_started_from_command_line=%%~c"
)
if "%_not_started_from_command_line%" == "0" (
rem Windows: 10
for /f "tokens=*" %%c in ('echo "%CMDCMDLINE%"^|find "cmd.exe /c """ /c') do (
set "_not_started_from_command_line=%%~c"
)
)
endlocal & (
set "%~1=%_not_started_from_command_line%"
)
goto :eof
:ProcedureImportCurrentFile
setlocal
set "cc="
if not exist "%_import_list_i%" (
set "_not_a_dir=false"
pushd "%CMD_LIBRARY%\%_import_list_i%\" 1>nul 2>&1||set "_not_a_dir=true"
call :GetStrLen CD _CD_len
)
if "%_not_a_dir%" == "false" (
setlocal EnableDelayedExpansion
if not "!CD:~-1,1!" == "\" (
endlocal
set /a _CD_len+=1
) else (
endlocal
)
popd
)
if not exist "%_import_list_i%" (
if "%_not_a_dir%" == "true" (
echo Importing file "%CMD_LIBRARY%\%_import_list_i%"
(
type "%CMD_LIBRARY%\%_import_list_i%">>"%_output_dir%\%_output_filename%"
) 2>nul||(
echo ERROR: Could not read file^!>>&2
set "_error=true"
)
(
if not "%%i" == "%count%" (
echo.
echo.
) else (
echo.
)
)>>"%_output_dir%\%_output_filename%"
) else (
echo Importing dir "%_import_list_i%"
rem
pushd "%CMD_LIBRARY%\%_import_list_i%\"
set /a cc=0
for /r %%f in (%mask%); do (
set "_current_file=%%~dpnxf"
call set "r=%%_current_file:~%_CD_len%%%"
call echo Importing subfile "%%_import_list_i%%\%%r%%"
(
(
call type "%%_current_file%%"
)>>"%_output_dir%\%_output_filename%"
) 2>nul||(
echo ERROR: Could not read file^!>>&2
set "_error=true"
)
(
echo.
echo.
)>>"%_output_dir%\%_output_filename%"
set /a cc+=1
)
popd
)
) else (
set "_not_a_dir=false"
pushd "%_import_list_i%\" 1>nul 2>&1||set "_not_a_dir=true"
call :GetStrLen CD _CD_len
)
if "%_not_a_dir%" == "false" (
setlocal EnableDelayedExpansion
if not "!CD:~-1,1!" == "\" (
endlocal
set /a _CD_len+=1
) else (
endlocal
)
popd
)
if exist "%_import_list_i%" (
if "%_not_a_dir%" == "true" (
echo Importing file "%_import_list_i%"
(
type "%_import_list_i%">>"%_output_dir%\%_output_filename%"
) 2>nul||(
echo ERROR: Could not read file^!>>&2
set "_error=true"
)
(
if not "%%i" == "%count%" (
echo.
echo.
) else (
echo.
)
)>>"%_output_dir%\%_output_filename%"
) else (
rem
echo Importing dir "%_import_list_i%"
pushd "%_import_list_i%\"
set /a cc=0
for /r %%f in (%mask%); do (
set "_current_file=%%~dpnxf"
call set "r=%%_current_file:~%_CD_len%%%"
call echo Importing subfile "%%_import_list_i%%\%%r%%"
(
(
call type "%%_current_file%%"
)>>"%_output_dir%\%_output_filename%"
) 2>nul||(
echo ERROR: Could not read file^!>>&2
set "_error=true"
)
(
echo.
echo.
)>>"%_output_dir%\%_output_filename%"
set /a cc+=1
)
popd
)
)
if "%cc%" == "0" (
echo No match^!
)
endlocal & (
set "_error=%_error%"
)
goto :eof
:DisplayHelp
echo IMPORT - a .cmd utility for importing subroutines into the main script
echo.
echo NOTES: 1. This utility assumes that command extensions are enabled (default) and that delayed expansion can be enabled;
echo ALSO IMPORT MUST BE CALLED INSIDE A DISABLED DELAYED EXPANSION BLOCK/ENVIRONMENT (DEFAULT);
echo These are necessary in order for it to function correctly.
echo 2. The use of UNC paths is not supported by import. As a workarround, you can mount a UNC path to a temporary drive using "pushd".
echo The use of "*" or "?" wildcards is not supported by import. Instead, you can set the mask with a set of name and extension masks sepparated by semicolon, like: set mask="*.cmd; *.bat"
echo When the "mask" variable is set, only the filenames having the extensions contained by it are matched at import.
echo.
echo Description:
echo import organizes your batch programs on common libraries of subroutines, that you can use in the future for other programs that you build; it also makes code editing and debugging easier.
echo.
echo Usage [1]:
echo import [flags]
echo.
echo [flags] can be:
echo /install - installs import into the registry, in the Command Processor AutoRun registry key ^(adds the current location of import into the PATH variable^).
echo /? - displays help ^(how to use import^)
echo.
echo Usage [2]:
echo What it does:
echo Concatenates ^(appends^) files content containing subroutines to the main program content using the following SYNTAX:
echo REM \\//Place this in the upper part of your script ^(main program)^ \\//:
echo.
echo #echo off
echo.
echo if not defined _import ^(
echo rem OPTIONAL ^(before the "import" calls^):
echo set "CMD_LIBRARY=^<library_directory_path^>"
echo.
echo import "[FILE_PATH1]filename1" / "DIR_PATH1"
echo ...
echo import "[FILE_PATHn]filenamen" / "DIR_PATHn"
echo import end "%%~0"
echo ^)
echo.
echo REM //\\Place this in the upper part of your script ^(main program)^ //\\:
echo.
echo "filename1" .. "filenamen" represent the filenames that contain the subroutines that the user wants to import in the current ^(main^) program. The paths of these files are relative to the directory contained in the CMD_LIBRARY variable.
echo.
echo "FILE_PATH1" .. "FILE_PATHn" represent the paths of these files.
echo.
echo "DIR_PATH1" .. "DIR_PATHn" represent directories paths in which to recursivelly search and import all the files of the type defined in the variable "mask"
echo.
echo CMD_LIBRARY is a variable that contains the directory path where your library of files ^(containing subroutines^) is found.
echo.
echo We denote the script that calls "import" as "the main script".
echo.
echo By default, if not modified in outside the import.cmd script, in the import.cmd script - CMD_LIBRARY is set to "." directory and is relative to the "import.cmd" parent directory.
echo If CMD_LIBRARY directory is modified outside the import.cmd script, CMD_LIBRARY is relative to the main script parent directory.
echo.
echo Note that only the last value of "CMD_LIBRARY" encountered before `import end "%%~0"` is taken into consideration.
echo.
echo import end "%%~0" - marks the ending of importing files and the start of building of the new batch file ^(named by default tmp0001.cmd, and located in the directory in which the main script resides^).
echo.
echo "%%~0" represents the full path of the main script.
goto :eof
To use it:
save it as import.cmd
call it with the /install flag in order to install it (does not require admin)
add a header like this at the begining of your main script that calls subroutines that are stored in other files - files that are going to be imported:
if not defined _import (
rem OPTIONAL (before the "import" calls):
set "CMD_LIBRARY=<library_directory_path>"
import "[FILE_PATH1]filename1" / "DIR_PATH1"
...
import "[FILE_PATHn]filenamen" / "DIR_PATHn"
import end "%~0"
)
To find out how to use it, simply call it with the /? flag.

Unable to echo user input values to file in Batch script

I am writing a batch file that will generate/write to a property file based on multiple user input values. However, it is not recording the values of input. The result looks like
prop1=
prop2=
I wonder if there's something I need to know with set that is preventing this from working.
Weird part is that if I run this particular script multiple times, the value output from echo seems to be always be the user input from last time.
Code:
#echo off
IF NOT EXIST data_file (
set /p prop1=Enter value:
set /p prop2=Enter value:
(echo prop1=%prop1%) > data_file
(echo prop2=%prop2%) >> data_file
)
Classic problem for inexperienced batchers :)
%prop1% is expanded when the line is parsed. Your problem is that everthing within parentheses is parsed in one pass. So the value you see is the value that existed before you entered the IF statement.
You have two simple solutions.
1) Eliminate the enclosing parens by reversing the logic and using GOTO
#echo off
IF EXIST file goto skip
set /p prop1=Enter value:
set /p prop2=Enter value:
(echo prop1=%prop1%) >file
(echo prop2=%prop2%) >>file
:skip
2) Use delayed expansion - that takes place just before each line within the parens is executed
#echo off
setlocal enableDelayedExpansion
IF NOT EXIST file (
set /p prop1=Enter value:
set /p prop2=Enter value:
(echo prop1=!prop1!)>file
(echo prop2=!prop2!)>>file
)
You need to expand the variables using SETLOCAL ENABLEDELAYEDEXPANSION or use CALL.
#echo off
IF NOT EXIST data_file (
set /p prop1=Enter value:
set /p prop2=Enter value:
(
Call echo prop1=%%prop1%%
Call echo prop2=%%prop2%%
) > data_file
)

What is wrong with this batch script?

I need a batch that reads a number from a file, increments it and saves it back into this file... This is what I came up with:
#ECHO OFF
SETLOCAL EnableDelayedExpansion
IF EXIST script\BUILDVERSION (
SET /p input = <script\BUILDVERSION
SET /a result=%input%+1
ECHO %result% > script\BUILDVERSION
) ELSE (
ECHO 0 > script\BUILDVERSION
)
At first it worked in a strange way, the result from reading the number from the file seemed to be a small random number, the result of the sum seemed random too... I don't know what I did, but now it doesn't even read the number from file into the variable...
Thanks in advance for help!
Instead of %input% and %result%, try using !input! and !result!. This seems to work better when using delayed expansion. Also, make sure you don't have any unnecessary spaces when reading from the file. You'll end up with:
#ECHO OFF
SETLOCAL EnableDelayedExpansion
IF EXIST script\BUILDVERSION (
SET /p input=<script\BUILDVERSION
SET /a result=!input!+1
ECHO !result! > script\BUILDVERSION
) ELSE (
ECHO 0 > script\BUILDVERSION
)

Resources