Check if file exist and check the first line in the file - batch-file

I have an batch file called cmd.dll. I want to check in my second batch file, if cmd.dll exist. The cmd.dll file is an "security" file.
For example:
# cmd.dll # REM SECURITY! completeLOGIN[198293]
second.bat # // CONVERTED TO .exe
#ECHO OFF GOTO :ifExist
:ifExist
if EXIST "C:\Users\%computername%\AppData\Roaming\cmd.dll" GOTO
:nextCode if NOT EXIST
"C:\Users\%computername%\AppData\Roaming\cmd.dll" GOTO :NOT
:NOT EXIST

This file verification could be done for example with following batch file:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "SecurityFile=%APPDATA%\cmd.dll"
if exist "%SecurityFile%" if not exist "%SecurityFile%\" goto ValidateFile
echo File "%SecurityFile%" does not exist.
rem Insert here more code to handle this use case of not existing file.
goto EndBatch
:ValidateFile
(for /F usebackq^ delims^=^ eol^= %%I in ("%SecurityFile%") do (
if "%%I" == "# cmd.dll # REM SECURITY! completeLOGIN[198293]" goto FileValid
goto FileInvalid
)) 2>nul
:FileInvalid
echo File "%SecurityFile%" exists,
echo but first non-empty line is not as expected.
rem Insert here more code to handle this use case of invalid content.
goto EndBatch
:FileValid
echo File "%SecurityFile%" exists
echo and contains the expected line as first non-empty line.
:EndBatch
echo/
endlocal
pause
The batch file first enables command extensions and disables delayed environment variable expansion as required for this task. This environment is the default on Windows, but batch file execution security comes first.
Next an environment variable is defined with full qualified name of the file to validate.
The first IF conditions checks if there is a file (or folder) with defined name and extension in defined directory and the second IF condition if found directory entry is really a file and not a folder.
A FOR loop is executed to read the first non-empty line from existing file. The command FOR fails and outputs an error message redirected with 2>nul to device NUL to suppress it if opening of the defined file for reading its content fails resulting in a fall through to code below label FileInvalid.
The command FOR on having success opening the text file with read access would read one line after the other, would skip empty lines and lines starting with a semicolon, would split up each line into substrings (tokens) using normal space and horizontal tab as delimiters and would assign for each line first space/tab separated string to specified loop variable I. This default behavior is not wanted here. For that reason an empty list of delimiters and no end of line character are defined in a not double quoted argument string requiring escaping equal sign and space with caret character ^ to be interpreted as literal characters and not as argument separators to get any non-empty line assigned as is to specified loop variable I.
A case-sensitive string comparison is used to validate the first non-empty line in defined file with specified string. On equal strings the file is valid and batch execution continues on line below label FileValid. Otherwise the FOR loop is exited with a jump to line below label FileInvalid because of the remaining lines in defined file are of no interest.
The batch file restores initial environment after showing an appropriate message for all three use cases and outputting an empty line.
Finally the execution of the batch file is halted to give the user the possibility to read the message.
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.
echo /?
endlocal /?
for /?
goto /?
if /?
pause /?
rem /?
setlocal /?

Related

How to insert a line into a text file after the first line?

I play Fallout 4 VR with Mod Organizer 2 (mo2), and most mods require
*Fallout4_VR.esm
at the top of the file plugins.txt, but mo2 keeps removing it.
So I downloaded a batch file which adds that line at the top of the file on execution.
But the problem is that mo2 has this at the top:
# This file was automatically generated by Mod Organizer.
*DLCRobot.esm
*DLCworks...
etc.
This is the code in the batch file:
#echo off
cls
setlocal enabledelayedexpansion
TITLE FO4VR Launch Codes
REM Find plugins.txt
set "file=C:\Modding\MO2\profiles\Default\plugins.txt"
if not exist "%file%" (
echo ERROR - Could not find %file%
echo.
goto FAIL
) else (
findstr /b /l /i /n "*Fallout4_VR.esm" %file%
if !errorlevel! == 0 (
echo VR ESM entry already exists. Good to go.
echo.
) else (
REM needs to add
(echo *Fallout4_VR.esm) >plugins.txt.new
type %file% >>plugins.txt.new
move /y plugins.txt.new %file%
echo VR ESM entry prepended to %file%.
echo.
)
)
echo.
pause
What do I need to edit so *Fallout4_VR.esm is below the whole line with generated by Mod Organizer instead of top of the file?
The file plugins.txt should be finally:
# This file was automatically generated by Mod Organizer.
*Fallout4_VR.esm
*DLCRobot.esm
*DLCworks...
etc.
The task to add at top the line with *Fallout4_VR.esm to contents of the file plugins.txt below the comment line(s) could be done with the following code:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
cls
title FO4VR Launch Codes
rem Find plugins.txt
set "PluginsFile=C:\Modding\MO2\profiles\Default\plugins.txt"
rem Use the line below instead of the line above if the file
rem plugins.txt is always in the same directory as the batch file.
rem set "PluginsFile=%~dp0plugins.txt"
if not exist "%PluginsFile%" echo ERROR: Could not find: "%PluginsFile%"& goto EndBatch
%SystemRoot%\System32\findstr.exe /B /I /L /C:"*Fallout4_VR.esm" "%PluginsFile%" >nul
if not errorlevel 1 echo VR ESM entry already exists. Good to go.& goto EndBatch
set "LineInsert=1"
(for /F usebackq^ delims^=^ eol^= %%I in ("%PluginsFile%") do (
if defined LineInsert (
set "CommentLine=1"
for /F "eol=#" %%J in ("%%~I") do set "CommentLine="
if not defined CommentLine (
echo *Fallout4_VR.esm
set "LineInsert="
)
echo(%%I
) else echo(%%I
))>"%PluginsFile%.tmp"
if not exist "%PluginsFile%.tmp" echo ERROR: Could not create temporary file: "%PluginsFile%.tmp"& goto EndBatch
%SystemRoot%\System32\attrib.exe -r "%PluginsFile%"
move /Y "%PluginsFile%.tmp" "%PluginsFile%" >nul 2>nul
if not errorlevel 1 echo VR ESM entry added to: "%PluginsFile%"& goto EndBatch
del "%PluginsFile%.tmp"
echo ERROR: Could not update: "%PluginsFile%"
:EndBatch
echo/
if /I not "%~1" == "/N" pause
endlocal
ATTENTION: The result is only correct if
plugins.txt is not a Unicode encoded text file with encoding UTF-16
and has DOS/Windows line endings (carriage return + line-feed).
It is advisable to avoid command blocks starting with ( and ending with a matching ) because that makes it possible to do the task without usage of delayed variable expansion and therefore the batch file works also with plugins.txt stored in a directory with a path like C:\Temp\Development & Test(!) 100%. It is of course necessary to use command blocks for the for /F loop processing the lines in the text file to modify.
The outer FOR loop processes the lines in the text file with skipping empty lines and assigning each non-empty line completely to the specified loop variable I. The option usebackq instructs FOR to interpret the string in double quotes as file name of which lines to process and not as string to process. The option delims= defines an empty list of delimiters to prevent splitting the lines up on normal spaces and horizontal tabs. The option eol= defines no character as end of line character. The unusual syntax without " around the three options must be used in this special case which requires escaping the spaces and the equal signs with caret character ^ to get usebackq delims= eol= interpreted as one argument string with the three options for command FOR.
The first IF condition is true as long as the line with *Fallout4_VR.esm is not output. In this case there is first defined the environment variable CommentLine with a value whereby the value itself does not matter.
The inner for /F processes the current line as string with ignoring the line on starting with # after zero or more leading spaces/tabs. So if the current line is a comment line, the environment variable CommentLine is still defined after execution of the inner for /F while this environment variable is deleted on current line is not a comment line.
If the current line is not a comment line, there is output *Fallout4_VR.esm to insert that as line into the temporary file and the environment variable LineInsert is deleted before next is output in any case the current line.
All other lines of the text file are just output after inserting the line with *Fallout4_VR.esm detected by environment variable InsertLine no longer existing.
Everything output during execution of the outer FOR loop is written by cmd.exe into a temporary file in same directory as the text file to update which should work as long as the directory is not write-protected for the user or there is already a directory with the name of the temporary file (very unlikely) or a read-only file with that name (also very unlikely).
The read-only file attribute is removed from the file to update if it would have the read-only file attribute set.
Next the temporary file is moved over the existing file to update which can fail like on write access to file is denied because of the file is currently opened by an application which denies the shared file write access.
The batch file can be started with the option /N to avoid the user prompt with command pause at end of the batch file.
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.
attrib /?
call /? ... for %~dp0 ... drive and path of argument 0 ... batch file path
cls /?
copy /?
del /?
echo /?
endlocal /?
findstr /?
for /?
goto /?
if /?
move /?
pause /?
rem /?
set /?
setlocal /?
title /?
See also:
DosTips forum topic: ECHO. FAILS to give text or blank line - Instead use ECHO/
Microsoft documentation about Using command redirection operators
Single line with multiple commands using Windows batch file

How to copy a file with an incremented version number in file name depending on existing files?

I have the batch file below:
FOR /F "delims=|" %%I IN ('DIR "%C:\TeamCity\buildAgent\work\53bba593f5d69be\public\uploads\*.xlsx" /B /O:D') DO SET NewestFile=%%I
FOR /F "delims=" %%a IN ('wmic OS Get localdatetime ^| find "."') DO SET DateTime=%%a
set Yr=%DateTime:~0,4%
set Mon=%DateTime:~4,2%
set Day=%DateTime:~6,2%
setlocal enableDelayedExpansion
set "baseName=InventoryReport%Yr%-%Mon%-%Day% V1.%n%"
set "n=0"
FOR /f "delims=" %%F in (
'DIR /b /ad "%baseName%*"^|findstr /xri "\\192.168.0.141\Medisun\28 - Business Development\30 - Product Inventory\InventoryReport\"%baseName%[0-9]*""'
) do (
set "name=%%F"
set "name=!name:*%baseName%=!"
if !name! gtr !n! set "n=!name!"
)
set /a n+=1
md "%baseName%%n%"
copy "%C:\TeamCity\buildAgent\work\53bba593f5d69be\public\uploads\%NewestFile%" "\\192.168.0.141\Medisun\28 - Business Development\30 - Product Inventory\InventoryReport\%baseName%%n%.xlsx"
cmd /k
I cannot get it to find the greatest version number of previously copied file between V1. and file extension .xlsx in file name and increment it but one. The batch file finds the file V1.1, but overwrites it instead of copying newest file with V1.2 in target file name.
How can I get the previous file version first and increment that number?
The file copying task can be done with following batch file:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "SourceFolder=C:\TeamCity\buildAgent\work\53bba593f5d69be\public\uploads"
set "TargetFolder=\\192.168.0.141\Medisun\28 - Business Development\30 - Product Inventory\InventoryReport"
for /F "eol=| delims=" %%I in ('dir "%SourceFolder%\*.xlsx" /A-D /B /O-D 2^>nul') do set "NewestFile=%%I" & goto CheckTarget
echo ERROR: Found no *.xlsx file in the folder:
echo "%SourceFolder%"
exit /B 1
:CheckTarget
if not exist "%TargetFolder%\" md "%TargetFolder%\" 2>nul
if exist "%TargetFolder%\" goto GetDateTime
echo ERROR: Failed to access or create the folder:
echo "%TargetFolder%"
exit /B 2
:GetDateTime
for /F "tokens=2 delims==." %%I in ('%SystemRoot%\System32\wbem\wmic.exe OS GET LocalDateTime /VALUE') do set "DateTime=%%I"
set "BaseName=InventoryReport%DateTime:~0,4%-%DateTime:~4,2%-%DateTime:~6,2% V1"
set "FileNumber=-1"
setlocal EnableDelayedExpansion
for /F "tokens=2 delims=." %%I in ('dir "!TargetFolder!\!BaseName!.*.xlsx" /A-D /B 2^>nul ^| %SystemRoot%\System32\findstr.exe /I /R /X /C:"!BaseName!\.[0123456789][0123456789]*\.xlsx"') do if %%I GTR !FileNumber! set "FileNumber=%%I"
endlocal & set "FileNumber=%FileNumber%"
set /A FileNumber+=1
copy /B /V "%SourceFolder%\%NewestFile%" "%TargetFolder%\%BaseName%.%FileNumber%.xlsx" >nul || exit /B 3
endlocal
The first FOR loop runs in background one more command process with %ComSpec% /c and the command line between the round brackets appended as additional arguments. So executed is with Windows installed to C:\Windows in background:
C:\Windows\System32\cmd.exe /c dir "C:\TeamCity\buildAgent\work\53bba593f5d69be\public\uploads\*.xlsx" /A-D /B /O-D 2>nul
The background command process executes internal command DIR which
searches in the specified directory
just for file names because of option /A-D (attribute not directory)
matching the wildcard pattern *.xlsx and
outputs them in bare format with just file name + extension because of option /B
ordered reverse by last modification date because of option /O-D which means the file name of newest file is output first and the file name of the oldest file is output last.
It is possible that either the source directory does not exist at all or the source directory does not contain any file matching these criteria. DIR would output in this case an error message to handle STDERR of background command process which would be redirected by the command process processing the batch file to own handle STDERR and so displayed most likely in console window. This error message is not wanted as there is a better one output below the FOR loop if there is not found any file for copying. For that reason the error message is redirected already by background command process to device NUL to suppress it.
Read the Microsoft documentation about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir command line with using a separate command process started in background.
FOR captures everything written to handle STDOUT of background command process and processes this captured output line by line after the executed background cmd.exe terminated itself.
FOR with option /F ignores always empty lines which do not occur in this case. Every other line would be first split up into substrings using normal space and horizontal tab character as delimiters. The line would be ignored if the first space/tab delimited string starts with default end of line character ; (semicolon). Otherwise just the first space/tab delimited string would be assigned to loop variable I and the command respectively command block would be executed next.
A *.xlsx file name can contain one or more spaces. For that reason the FOR option delims= is used to define an empty list of string delimiters to disable line splitting completely. It is unusual, but nevertheless possible, that a file name starts with a semicolon. Therefore FOR option eol=| is also used to define the vertical bar as end of line character which no file name can contain as described by Microsoft in the documentation about Naming Files, Paths, and Namespaces. So the result is that every file name output by DIR in background command process is assigned one after the other completely to the loop variable I.
The file name of the newest file is output first and so its name is assigned to environment variable NewestFile. Then the first FOR loop is exited with using command GOTO to jump to the first line below label CheckTarget as processing the other file names would be a waste of time and CPU power.
There is a meaningful error message output on no *.xlsx file found to copy and batch file processing is exited with exit code 1 to indicate an error condition to parent process starting this batch file.
Next, with having file name of newest file in source folder, an existence check of target folder is done with creating the target folder if not already existing. A meaningful error message is output if the target folder is still not existing because of other computer or storage device is not running or is not reachable at all or the creation of the target folder failed for whatever reason.
The next two command lines get the current date/time in a region independent format and define the base file name for target file using the current date. For a full description of these two lines see my answer on Time is set incorrectly after midnight.
Then the file number is defined with value -1 and delayed expansion is enabled as required for the number comparison done by the next FOR loop.
The third FOR loop is similar to first FOR loop. There is additionally the output of command DIR redirected to handle STDIN of FINDSTR to be filtered for verification if the file name of found file contains really just one or more digits between the dot after V1 and the dot of the file extension, i.e. this part of the file name is a valid number. It can be assumed that FINDSTR outputs the same lines as output by DIR on target folder not used for something different than the Excel files with the defined pattern for the file name. The two dots in name of each file must be escaped with a backslash in case-insensitive interpreted regular expression search string on which the space character is interpreted as literal character because of using /C: and /R and not as OR expression as on omitting /C:. For 100% safety on processing later only correct file names /X is additionally used to output only file names on which entire file name is matched by the search expression.
This time the FOR loop should not assign the entire file name to loop variable I. There is of interest only the string between the first dot after V1 and the file extension .xlsx. For that reason the FOR option delims=. is used to split the file names on dots and option tokens=2 is used to instruct command FOR to assign the second dot delimited string to loop variable I which is the incremented file number.
A simple integer comparison is done to determine if the file number of current file name is greater than file number assigned currently to environment variable FileNumber in which case this greater file number is assigned to the environment variable FileNumber.
The local environment with enabled delayed expansion is no longer needed after knowing the greatest file number of the existing files if there is one at all. So this environment is destroyed which would mean the environment variable FileNumber would have again the number -1 as assigned to the environment variable in initial environment. Please read this answer for details about the commands SETLOCAL and ENDLOCAL. So to pass the current value of FileNumber in current environment to FileNumber in previous environment the command line with endlocal contains additionally the command set "FileNumber=%FileNumber%" which is processed by cmd.exe, for example, to set "FileNumber=12" before executing the command ENDLOCAL. That simple trick is used to pass the greatest file number value to FileNumber in previous environment.
See also:
How does the Windows Command Interpreter (CMD.EXE) parse scripts?
Single line with multiple commands using Windows batch file
The greatest file number of an existing file or -1 is incremented by one before copying the newest file in source folder with this number and current date in file name to the target folder with verification that the file data were really correct written on target storage media.
The batch file is exited with exit code 3 in case of file copying failed for whatever reason.
Finally the batch file processing ends with explicitly restoring initial execution environment. The last command ENDLOCAL would be not really necessary because of Windows command processor runs it implicit on exiting processing of this batch file as done for example on execution of one of the three commands exit /B.
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.
copy /?
dir /?
echo /?
endlocal /?
exit /?
findstr /?
for /?
goto /?
set /?
setlocal /?
wmic /?
wmic os /?
wmic os get /?
wmic os get localdatetime /?
PS: The greatest possible file number is 2147483647. But a day has only 86400 seconds and more than 65535 files in one directory would be a real problem, too. So the maximum file number 2147483647 should be never reached if no user renames a file in target folder to exceed that maximum number.

How to add time stamp to log lines in log file from batch file output?

I want to add time stamp to log lines from batch output.
Here is my batch file:
#Echo off
SET LOGFILE=MyLogFile.log
call :Logit >> %LOGFILE%
exit /b 0
:Logit
set "affix=%date%_%time%"
set "affix=%affix::=%"
set "affix=%affix:/=%"
xcopy "I:\DF\AB\Data.xlsx" "D:\TL\BACKUP\Data_%affix%.xlsx"*
Output of log file:
I:\DF\AB\Data.xlsx
1 File(s) copied
I want output log file looking like this:
I:\DF\AB\Data.xlsx
20180831_124500 : 1 File(s) copied
How could this be achieved?
Some more information:
The asterisk at end of target argument string is required for copying the file without prompt. There would be a prompt asking if target is a file or a directory if * would not be used at end of target file name.
xcopy is used because copied is a file from a network drive to local drive.
The output result is as below after running the batch file:
I:\DF\AB\Data.xlsx
08312018_163959.07 :I:\DF\AB\Data.xlsx
1 File(s) copied
May it be as below?
I:\DF\AB\Data.xlsx
08312018_163959.07 1 File(s) copied
So the region dependent date format is MM/DD/YYY and time format is HH:mm:ss.ms.
You're only XCopying one file, so you know that your last line of output on success will be the language dependent string 1 File(s) copied.As you've already limited the script to using a locale dependent %DATE% and %TIME%, I have assumed that language dependency for this task is fine.
Here therefore is a ridiculous looking example script:
#Echo Off
Set "srcfile=I:\DF\AB\Data.xlsx"
Set "destdir=D:\TL\BACKUP"
Set "logfile=MyLogFile.log"
For %%A In ("%srcfile%") Do Set "dstname=%%~nA" & Set "destext=%%~xA"
For /F "Tokens=1-2 Delims=|" %%A In ('
Echo F^|XCopy "%srcfile%" "|%DATE:/=%_%TIME::=%|" /L 2^>Nul ^&^
Echo F^|Xcopy "%srcfile%" "%destdir%\%dstname%_%DATE:/=%_%TIME::=%%destext%" /Y ^>Nul 2^>^&1
') Do (If Not "%%B"=="" Set "_=%%B"
If Defined _ If /I "%%A"=="%srcfile%" ((
Echo %%A&Call Echo %%_%% 1 File(s^) copied)>"%logfile%"))
You should change nothing other than the values for the variables on lines 2-4.However should you be using an existing logfile, you may wish to change > on the last line to >>
You can use echo| set /p=%affix% to eliminate the newline at echo time as:
#Echo off
SET LOGFILE=MyLogFile.log
call :Logit >> %LOGFILE%
exit /b 0
:Logit
set "affix=%date%_%time%"
set "affix=%affix::=%"
set "affix=%affix:/=%"
echo|set /p=%affix% :
xcopy "I:\DF\AB\Data.xlsx" "D:\TL\BACKUP\Data_%affix%.xlsx"*
Result:
I:\DF\AB\Data.xlsx
2018-08-31_124900 : 1 file(s) copied.
powershell -command "(New-TimeSpan -Start (Get-Date "01/01/1970") -End (Get-
Date)).TotalSeconds">LOG.TXT
Although this is not the format you suggested, this format is called epoch time.
The good thing about this format is that it is always a float value.
LOG.TXT will be the name of the log, make sure you are in the right directory.
I suggest following code producing exactly the initially wanted output in log file:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "LOGFILE=MyLogFile.log"
del "%LOGFILE%" 2>nul
call :Logit >>"%LOGFILE%"
endlocal
exit /B 0
:Logit
set "FileDate=%DATE:~-4%%DATE:~-10,2%%DATE:~-7,2%_%TIME:~0,2%%TIME:~3,2%%TIME:~6,2%"
for /F "tokens=1* delims=:" %%I in ('%SystemRoot%\System32\xcopy.exe "I:\DF\AB\Data.xlsx" "D:\TL\BACKUP\Data_%FileDate%.xlsx*" /C /V /Y 2^>nul') do (
if not "%%J" == "" (
echo %%I:%%J
) else (
echo %FileDate% : %%I
)
)
goto :EOF
The region dependent date and time is reformatted to yyyyMMdd_HHmmss by using string substitutions of the dynamic environment variables DATE and TIME as explained in detail for example by the answer on the question: What does %date:~-4,4%%date:~-10,2%%date:~-7,2%_%time:~0,2%%time:~3,2% mean? For a much slower, but region independent solution, to get date/time in a specific format see for example the answer on: Why does %date% produce a different result in batch file executed as scheduled task?
The current date and time in format yyyyMMdd_HHmmss is assigned to the environment variable FileDate used twice on the next line, once in name of target file and once more in output of last line of reformatted output of command XCOPY.
The XCOPY command line used here is for example:
C:\Windows\System32\xcopy.exe "I:\DF\AB\Data.xlsx" "D:\TL\BACKUP\Data_20180831_163959.xlsx*" /C /R /V /Y 2>nul
This command line is executed by FOR in a separate command process started by FOR with cmd.exe /C in background. FOR captures all lines written to handle STDOUT of this command process before processing the captured lines.
XCOPY outputs to handle STDOUT the names of the copied files with full path and as last line a summary information. Errors on file copying are written to handle STDERR which are suppressed by redirecting them to device NUL.
Read also the Microsoft article about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded xcopy command line with using a separate command process started in background.
The asterisk * at end of target file name should be within the double quotes of second argument string and not outside because otherwise cmd.exe respectively xcopy.exe has to correct this wrong syntax.
Please note that the trick with * at end of target file name works here by chance because source and target file have same file extension and the source file name is always shorter than the target file name. Otherwise the command would fail or target file gets an unwanted name being a concatenation of target file name + the characters of source file name after n characters of target file name.
In general there are better methods to avoid a halt on prompt which XCOPY requests in case of a single file is copied with a new file name. The letter to answer the prompt can be output first to STDOUT redirected to handle STDIN of XCOPY command as demonstrated language independent in answer on batch file asks for file or folder.
The captured output of XCOPY is processed by FOR line by line with skipping empty lines and lines starting with a semicolon ; as being the default end of line character of option eol= not used here.
The goal here is to output all lines with a full qualified file output by XCOPY in background command process also in this command process, but output the last line with the summary information different by prepending it with the date/time in wanted format, a space, a colon and one more space.
For that reason the default line splitting behavior on spaces/tabs with assigning only first substring (token) to specified loop variable I is modified here by the options tokens=1* delims=:. FOR splits up a line on colons now.
Only the lines with a full qualified file name starting with a drive letter and a colon contain a colon at all. On such lines the drive letter is assigned to specified loop variable I as specified by tokens=1. The rest of a file name line after first colon is assigned without any further splitting to next loop variable according to ASCII table to loop variable J which is here everything after the colon after drive letter.
The summary information line does not contain a colon. For that reason FOR assigns the entire summary information to loop variable I and J holds an empty string.
The loop variable J is never empty on a line with a file name starting with a drive letter and a colon. This fact is used here to determine if the line from XCOPY should be output as is with inserting the removed colon between drive letter and file path + file name + file extension or output the summary information with date/time at beginning.
Please note that this method works only on copying files from a drive with a drive letter. A different method would be necessary for source files with a UNC path.
In fact copying a single file can be done much easier with command COPY instead of XCOPY even from/to a network drive or when source/target file name is specified with a UNC path. COPY has also the options /V and /Y and even /Z like XCOPY. COPY does not create the target directory structure like XCOPY, but this can be done with command MD before. COPY can't overwrite a read-only file as XCOPY can do on using option /R, but this limitation of COPY is most likely not relevant here. And COPY does not copy a file with hidden attribute set. However, in general copying a single file is nevertheless best done with command COPY instead of XCOPY.
So here is one more solution with using command COPY which is faster than the XCOPY solution as there is no reason for executing the file copy in a separate command process, capture any line, split them and output them concatenated again or modified.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "LOGFILE=MyLogFile.log"
md "D:\TL\BACKUP" 2>nul
del "%LOGFILE%" 2>nul
call :Logit >>"%LOGFILE%"
endlocal
exit /B 0
:Logit
set "FileDate=%DATE:~-4%%DATE:~-10,2%%DATE:~-7,2%_%TIME:~0,2%%TIME:~3,2%%TIME:~6,2%"
echo I:\DF\AB\Data.xlsx
copy /B /V /Y "I:\DF\AB\Data.xlsx" "D:\TL\BACKUP\Data_%FileDate%.xlsx" >nul 2>nul && echo %FileDate% : 1 File(s) copied|| echo %FileDate% : 0 File(s) copied
goto :EOF
This solution has also the advantage that the line output on success or error can be fully customized. COPY exits with a value greater 0 on an error like source file not available or target file/directory is write-protected currently or permanently.
Example of a better output for a single copied file on success or error (subroutine only):
:Logit
set "FileDate=%DATE:~-4%%DATE:~-10,2%%DATE:~-7,2%_%TIME:~0,2%%TIME:~3,2%%TIME:~6,2%"
copy /B /V /Y "I:\DF\AB\Data.xlsx" "D:\TL\BACKUP\Data_%FileDate%.xlsx" >nul 2>nul
if not errorlevel 1 (
echo %FileDate% : Copied successfully I:\DF\AB\Data.xlsx
) else (
echo %FileDate% : Failed to copy file I:\DF\AB\Data.xlsx
)
goto :EOF
It is of course also possible to use the command line
set "FileDate=%DATE:/=%_%TIME::=%"
in the batch file to get the date and time in format MMddyyyy_HHmmss.ms if that is really wanted now. I don't recommend this date/time format as it is not good on alphabetical list of all Data_*.xlsx files in directory D:\TL\BACKUP. The list of files sorted by name is with the date/time format yyyyMMdd_HHmmss automatically also sorted by date/time.
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 /?
copy /?
del /?
echo /?
endlocal /?
exit /?
for /?
goto /?
if /?
md /?
set /?
setlocal /?
xcopy /?
See also:
Where does GOTO :EOF return to?
Single line with multiple commands using Windows batch file for an explanation of the operators && and ||.

Adding an iteration to a text file in a batch file

I am trying to add an iteration to a text file to save the output number instead of it being overwritten to keep the iterations.
I am able to write the first iteration number but it will not add the number after.
I have a text file with just the iteration number as 1.
iteration.txt
1
My batch code looks like this so far.
REM Run model outputs
set "iter="
for /F "delims=" %%i in (iteration.txt) do if not defined iter set "iter=%%i"
copy .\run1_Diagnostics.csv storefilesfolder\%itera%_Diagnostics.csv
echo %iter%+1 >iteration.txt
The problem is it either just overwrite the text file to +1 or +1 +1 +1 +1 and doesn't add the number.
I suggest to use this code:
REM Run model outputs
if not exist iteration.txt set "iter=1" & goto CopyFile
set "iter="
for /F "delims=" %%i in (iteration.txt) do if not defined iter set "iter=%%i"
set /A iter+=1
:CopyFile
copy run1_Diagnostics.csv storefilesfolder\%iter%_Diagnostics.csv
>iteration.txt echo %iter%
There is also another method to read the first line of a text file and assign it to an environment variable:
REM Run model outputs
if not exist iteration.txt set "iter=1" & goto CopyFile
set "iter="
set /P iter=<iteration.txt
set /A iter+=1
:CopyFile
echo copy run1_Diagnostics.csv storefilesfolder\%iter%_Diagnostics.csv
>iteration.txt echo %iter%
Both batch files first check if the file iteration.txt exists at all in current directory.
The environment variable iter is defined with value 1 if the file does not exist.
Otherwise the first line is read from the file and assigned to environment variable iter using command FOR or an input redirection from file on prompting for value of the environment variable using command SET with option /P.
The value read from file is incremented by one using command SET with option /A which means the new value of environment variable iter is the result of an arithmetic expression. set /A is the only method supported by Windows command line interpreter to do simple mathematical or binary operations on 32-bit signed integer values. The value of iter is 1 if the string read from file is not a valid 32-bit signed integer.
Next the file is copied with using initial value 1 or the value read from file iteration.txt incremented by one.
.\ is not really necessary to specify the current directory. It is safe to omit those two characters if the file or folder is in current directory.
The last command line outputs the current iteration value to handle STDOUT which is redirected to file being created or overwritten in case of existing already.
The redirection operator and the file name is specified here left to command ECHO which outputs the number because of echo %iter%>iteration.txt would not work for the numbers 1 to 9. And using echo %iter% >iteration.txt writes the space character between number and redirection operator > also into the file as trailing space which should be avoided here.
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.
copy /?
echo /?
for /?
goto /?
if /?
rem /?
set /?
Read also the Microsoft article about Using Command Redirection Operators for an explanation of redirection operators < and > and single line with multiple commands using Windows batch file for an explanation of operator &.

Convert configuration file into variables and convert forward slash to backslash

I have a configuration file which I need for my bash script which has a layout:
A=C:/Example1/A
B=C:/Example2/B
C=C:/Example3/C
I want to use the same configuration file for my windows batch file. I need to convert the above file into variables which I have done using:
for /f "delims=" %%x in (test.txt) do (set "%%x")
How do I go about converting this file into variables while also converting all the forward slashes into backslashes?
Thanks!
add after your for line,
for /f "delims==" %%x in (q888.txt) do call set "%%x=%%%%x:/=\%%"
or, as a replacement for your existing for,
for /f "tokens=1*delims==" %%x in (q888.txt) do set "%%x=%%y"&call set "%%x=%%%%x:/=\%%"
(I used a file called q888.txt for testing)
The first smply executes a substitution, using a parsing trick. The second combines the set and substitution into one cascaded command by tokenising on = into %%x and %%y
This could be done with the following batch code:
#echo off
if not exist "test.txt" goto :EOF
setlocal EnableDelayedExpansion
for /F "usebackq tokens=1* delims==" %%I in ("test.txt") do (
if not "%%~J" == "" (
set "Value=%%~J"
set "Value=!Value:/=\!"
set "_%~n0_%%~I=!Value!"
)
)
echo The variables set from file are:
echo/
set "_%~n0_"
echo/
pause
endlocal
The batch file first checks if the file to process exists in current directory at all. The batch file processing is exited with a jump to predefined label EOF (end of file, requires enabled extensions which are enabled by default) in case of the file test.txt does not exist at all.
Next the file is read line by line with skipping empty lines and lines starting with a semicolon by command FOR which splits each line up into two strings.
The first string left of first equal sign is assigned to loop variable I. Everything right of first equal sign is assigned next loop variable J according to ASCII table.
The IF condition in the loop checks if a value is also defined for a variable. The value is assigned to an environment variable on which a string substitution is executed using delayed expansion to replace all / by \.
Then the modified value is assigned to an environment variable with a name starting with _, the name of the batch file, one more underscore and the string assigned to loop variable I read from the file.
For demonstration the variables with their values are finally output before the local variables are discarded on execution of last command ENDLOCAL.
I strongly recommend not assigning the values read from the file directly to environment variables whose name is completely also read from the file as this makes the batch file easy to manipulate by just modifying the contents of the text file. For example path=C:\Temp in text file would otherwise result in set "Path=C:\Temp" and from this point of batch file execution the running Windows command process would not find anymore any standard executable in directories defined by default in environment variable PATH like %SystemRoot%\System32.
A second variant which incorporates answer posted by Magoo with above batch code:
#echo off
if not exist "test.txt" goto :EOF
setlocal DisableDelayedExpansion
for /F "usebackq tokens=1* delims==" %%I in ("test.txt") do if not "%%~J" == "" set "_%~n0_%%~I=%%~J" & call set "_%~n0_%%~I=%%_%~n0_%%~I:/=\%%"
echo The variables set from file are:
echo/
set "_%~n0_"
echo/
pause
endlocal
The advantage of this variant is that delayed expansion is not needed for this solution which makes it possible to correct process also lines from file containing 1 or more exclamation marks on which first variant fails. And it is also a little bit faster, not noticeable faster for a human, but nevertheless a bit faster.
In both batch code blocks _%~n0_ can be replaced by (nearly) anything including also nothing although that is not recommended. Using just an underscore would be also possible as there are no environment variables defined by default by Windows which start with an underscore.
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 /? ... explains %~n0 (name of argument 0 - the batch file name - without path and without file extension).
echo /?
endlocal /?
for /?
goto /?
if /?
pause /?
set /?
setlocal /?
The simplest solution is to let the ~f FOR variable modifier put the full path in canonical form (including conversion of forward slashes to back slashes). I use the DELIMS and TOKENS options to split each line into the variable name and path so that I can apply the ~f to the path. It is important to use tokens=1* instead of tokens=1,2 just in case the path includes a = character.
for /f "delims== tokens=1*" %%A in (test.txt) do (set "%%A=%%~fB")
Note, however, that this strategy only works if your "test.txt" already contains full, absolute paths. If the file contains relative paths, then the ~f modifier will add drive and or folder values from the current directory to turn the relative path into an absolute path.

Resources