I've inherited a batch file, and in it, I've got the following command -
forfiles /s /c "cmd /c IF #ISDIR==TRUE rmdir #FILE /q" >> C:\Apps\%dt%\%dt%.log 2>&1
In the log, I get the following error:
ERROR: The system cannot find the file specified.
How can I put an else condition so that it outputs the name of the file not found, instead the Error Message?
Ther error comes from forfiles when it is executed in a directory with no content (no files and no directories). Remember that forfiles searches the current working directory if no path is specified explicitly by the /P switch, and the search mask defaults to * if switch /M is not given, hence matching all items. So the error you encounter can only occur with an empty working directory.
The error has got nothing to do with the if #isdir==TRUE query.
If you want a specific message to appear in your log, you could check the ErrorLevel state after forfiles has been executed, which is 1 in case no items match the search criteria, and 0 if at least one item matches:
forfiles /S /C "cmd /C if #isdir==TRUE rmdir #file" || echo "%CD%" is empty. >> "C:\Apps\%dt%\%dt%.log" 2> nul
This writes the message "%CD%" is empty into the log with %CD% replaced by the current directory.
The 2> nul suppresses the error message ERROR: The system cannot find the file specified..
The || operator is conditionally concatenates commands and lets the right one execute only if the left one has been completed successfully, meaning that ErrorLevel is 0.
Related
I want to create Batch file which runs from Task Scheduler
Requirements:
less than 30 days old files should move from main folder to Archive folder.
then the moved archive files should be deleted.
it should store all the log entries.
I have scripted steps 1 & 2, (from web searches), but inserting log file command is quite difficult for me, as I have no knowledge on batch scripts.
Script for steps 1 & 2:
forfiles /p "D:\Test" /m *.* /s /d -30 /c "cmd /c move #file E:\Test1"
forfiles /p "E:\Test1" /c "cmd /c del #path"
Can someone please show me how I can create log files using the above 2 commands.
The log file should store all files name which it moved & deleted, date and time, and success or failed.
It makes absolutely no sense to first move files from drive D: to drive E: which requires copying the data of each file, and then delete each file on drive E:. It is much faster to delete the files older than 29 days directly on drive D:.
The batch file below can be used for the file deletion task with logging success or error of each file deletion.
#echo off
if "%~1" == "" goto RunForFiles
del /A /F %1 2>nul
(if not exist %1 (echo %DATE% %TIME% Deleted successfully: %1) else echo %DATE% %TIME% Failed to delete file: %1)>>"E:\LogStoragePath\OldFileDeletionLog.txt"
exit /B
:RunForFiles
md "E:\LogStoragePath" 2>nul
del "E:\LogStoragePath\OldFileDeletionLog.txt" 2>nul
%SystemRoot%\System32\forfiles.exe /P "D:\Test" /M *.* /S /D -30 /C "%SystemRoot%\System32\cmd.exe /D /S /C \"if #isdir == FALSE \"%~f0\" #path\""
The batch file first checks if being call without any argument which is the case on batch file being started by the scheduled task. In this case processing the batch file continues with the lines below the label RunForFiles.
There is first created the directory (tree) to the log file without checking if that could be done successfully at all. The error message output by command MD on log file directory existing already is suppressed by redirecting it from handle STDERR to device NUL. 2>nul suppresses also the error message output on creation of directory (tree) due to other reasons like missing permissions on storage media to do that.
Next the log file existing perhaps from a previous execution is deleted with suppressing with 2>nul the error message output by DEL if the log file does not exist at all or the deletion could not be done because of missing permissions to do that (NTFS permissions, read-only attribute, log file currently opened in an application).
Then FORFILES is executed to search in
directory D:\Test specified with path option /P
and all its subdirectories because of option /S
for file system entries (directories, files and reparse points)
matching the wildcard pattern *.* (any) specified with mask option /M
with a last modification date less than or equal 30 days
to execute the command specified with option /C.
The command results in running one more Windows Command Processor with the options /D to ignore the AutoRun registry value and /S to interpret everything after next option /C as one argument string with the command line to execute and option /C to execute the command line specified next and close itself after finishing the execution.
The started Windows Command Processor process first checks with a case-sensitive string comparison if the current file system entry found by FORFILES with a last modification date older than 29 days is not a directory. A directory is ignored and results in an immediate close of started cmd.exe.
For a file is executed next by started cmd.exe the currently processed batch file with the full file name enclosed in " as argument. So the batch file processed by cmd.exe runs forfiles.exe which starts one more cmd.exe to process the same batch file, but this time with a file name as argument for the batch file.
This time the batch file processing continues on third command line which uses the command DEL with option /A (all attributes) to overrule the implicit default /A-H (all attributes except hidden attribute) to delete also a file with hidden attribute set and option /F to force a deletion of a file with read-only attribute set.
The file deletion is in most cases most likely successful. But an application having currently opened a file older than 29 days or special NTFS permissions can result in a failed file deletion.
Therefore an IF condition is used to verify if the file really does not exist anymore in which case a success message is output to handle STDOUT using command ECHO with the dynamic variables DATE and TIME. Otherwise on failed deletion of the file a failed message is output with ECHO with DATE and TIME to handle STDOUT. The output message is redirected to the specified log file and being appended on this log file which is opened, modified and closed for each file to delete.
Last the execution of the batch file to delete an old file is exited with command EXIT using option /B to just exit the batch file processing and not entire cmd.exe processing this batch file. cmd.exe started by forfiles.exe has nothing more to do and closes which results in forfiles.exe searching for next file system entry matching the specified criteria.
The entire file deletion task is horrible slow using this batch file. It would be definitely much better to use for this task a small PowerShell script processed by Windows PowerShell which would do this file deletion task much faster.
I recommend to read also the chapter Best and simple backup deletion concept in my answer on Bat file to delete files only when younger files are present. It is in general not advisable to simply delete all files older than X days without checking if younger files are present, especially if the files to delete are backup files or log files of a periodically executed task. The solution posted in referenced answer using a different deletion strategy than just last modification date is also much faster than the solution posted here.
Here is also a variant of above with first moving each old file from D:\Test and its subdirectories to E:\Test1 without replicating the directory tree in destination directory with logging success or failure of file movement and finally deleting all files in E:\Test1 recording also success or failure of file deletion. File deletion failure is very unlikely.
#echo off
if "%~1" == "" goto RunForFiles
move /Y %1 "E:\Test1\" >nul 2>nul
(if not exist %1 (echo %DATE% %TIME% Moved successfully: %1) else echo %DATE% %TIME% Failed to move file: %1)>>"E:\LogStoragePath\OldFileMoveLog.txt"
exit /B
:RunForFiles
md "E:\Test1" 2>nul
md "E:\LogStoragePath" 2>nul
del "E:\LogStoragePath\OldFileMoveLog.txt" 2>nul
%SystemRoot%\System32\forfiles.exe /P "D:\Test" /M *.* /S /D -30 /C "%SystemRoot%\System32\cmd.exe /D /S /C \"if #isdir == FALSE \"%~f0\" #path\""
set "ProcessedFile="
(echo %DATE% %TIME%
for %%I in ("E:\Test1\*") do (
set "ProcessedFile=1"
del /A /F "%%I" 2>nul
if not exist "%%I" (echo Deleted successfully: %%~nxI) else Failed to delete file: %%~nxI
))>"E:\LogStoragePath\OldFileDeletionLog.txt"
if exist "E:\LogStoragePath\OldFileDeletionLog.txt" if not defined ProcessedFile del "E:\LogStoragePath\OldFileDeletionLog.txt"
The last command line deletes the file deletion log file on being created but containing just date and time because of no file was moved before and so no file needed to be deleted at all.
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.
cmd /?
del /?
echo /?
exit /?
for /?
forfiles /?
goto /?
if /?
md /?
move /?
set /?
See also the Microsoft documentation about Using command redirection operators.
I want to create a batch file that loops through a folder containing xml files, then call msxsl to modify them and after modify the xml file, copying to another folder with original filename.
I tried this:
forfiles /p C:\Users\mae\Documents\Testing\MSXSL\In /m *.xml /c "cmd /c C:\Users\mae\Documents\Testing\MSXSL\msxsl.exe #file pre-process_add_filename.xsl -o C:\Users\mae\Documents\Testing\MSXSL\Out\#file"
But that gives me this error:
Error occurred while creating file 'C:\Users\mae\Documents\Testing\MSXSL\Out\"bk_OIOUBLInvoice_TEST.xml"'.
Code: 0x8007007b
The filename, directory name, or volume label syntax is incorrect.
This is because of the double quotes around the output filname. How do I get around this?
As already suggested by others in comments, you should use a standard for loop for your task rather than forfiles:
for %%I in ("%UserProfile%\Documents\Testing\MSXSL\In\*.xml") do (
"%UserProfile%\Documents\Testing\MSXSL\msxsl.exe" "%%I" "pre-process_add_filename.xsl" -o "%UserProfile%\Documents\Testing\MSXSL\Out\%%~nxI"
)
But if you do insist on forfiles you could use the following code:
forfiles /P "%UserProfile%\Documents\Testing\MSXSL\In" /M "*.xml" /C "cmd /C for %%I in (#file) do 0x22%UserProfile%\Documents\Testing\MSXSL\msxsl.exe0x22 #file 0x22pre-process_add_filename.xsl0x22 -o 0x22%UserProfile%\Documents\Testing\MSXSL\Out\%%~I0x22"
The inner for loop together with the ~-modifier is used to get rid of the additional quotation marks around the file name returned by #file. The term 0x22 is forfiles-specific and marks a literal quotation mark.
I need a CMD batch file to delete all the log files.
My company makes plugins for their product and the path is something as follows:
C:/Program Files/product/../plugins/../plugin_Path/pluginOne/audit/log
C:/Program Files/product/../plugins/../plugin_Path/pluginOne/audit/log-archive
C:/Program Files/product/../Root/plugins/../plugin_Path/pluginTwo/audit/log
C:/Program Files/product/../Root/plugins/../plugin_Path/pluginTwo/audit/log-archive
Now I need to delete all the log and log-archive folders with its contains.
Currently I wrote a samll program like this:
#echo off
color 02
for %%A in (
"C:/Program Files/product/plugins/plugin_Path/pluginOne/audit/log"
"C:/Program Files/product/plugins/plugin_Path/pluginOne/audit/log-archive"
"C:/Program Files/product/plugins/plugin_Path/pluginTwo/audit/log"
"C:/Program Files/product/plugins/plugin_Path/pluginTwo/audit/log-archive"
) do (
del /Q %%A
echo Deleted %%A
)
echo All files deleted
pause
echo Program ended
But here I need to insert all the log paths manually.
I am looking for a solution where I could point the parent folder (say Program Files/Company) and it could traverse all the files inside and will delete all the log and log-archival folders with its contains.
I am a QA person have good QA experience but no experience on batch programming and I dont have much time and support team is not present. [Need help]. There are more than 1K log files are present.
First, as explained by the Microsoft documentation Naming Files, Paths, and Namespaces, the directory separator on Windows is \ and not / as on Linux/Mac. / is used on Windows for options as you can see on your code for example on /Q. So use in future \ in file/folder paths. The Windows file system accessing kernel functions automatically replace all forward slashes by backslashes before accessing the file systems, but writing code depending on automatic error correction is never a good idea.
The task to delete all folders with name log or log-archive in a specified folder and all its subfolders can be done with a single command line.
#for /F "delims=" %%I in ('dir "%ProgramFiles%\product\plugins\plugin_Path\log*" /AD /B /S 2^>nul ^| %SystemRoot%\System32\findstr.exe /E /I /R "\\log \\log-archive"') do #rd /Q /S "%%I" 2>nul
FOR with option /F runs in a separate command process started with cmd.exe /C (more precise with %ComSpec% /C) in background the command line in '... ' which is here:
dir "C:\Program Files\product\plugins\plugin_Path\log*" /AD /B /S 2>nul | C:\Windows\System32\findstr.exe /E /I /R "\\log \\log-archive"
The command DIR outputs to handle STDOUT
in bare format because of option /B
just directories because of option /AD (attribute directory)
directory names matching the wildcard pattern log*
in specified directory C:\Program Files\product\plugins\plugin_Path
and all its subdirectories because of option /S
with full path also because of option /S.
It could be that DIR does not find any file system entry matching these criteria. In this case an error message is output by DIR to handle STDERR. This error output is redirected with 2>nul to device NUL to suppress it.
The standard output of DIR is redirected with | to handle STDIN of FINDSTR which runs
because of option /I a case-insensitive
regular expression find explicitly requested with option /R
for string \log or \log-archive (space is interpreted as OR)
which must be found at end of a line because of option /E.
All lines matching these search criteria are output by FINDSTR to handle STDOUT of background command process. This filtering of output of DIR with FINDSTR is necessary to avoid the deletion of a directory which is named for example LogToKeep also found and output by DIR.
Read the Microsoft article about Using Command Redirection Operators for an explanation of 2>nul and |. The redirection operators > and | must be escaped with caret character ^ on FOR command line to be interpreted as literal characters when Windows command interpreter processes this command line before executing command FOR which executes the embedded command line with using a separate command process started in background.
FOR with option /F captures output to handle STDOUT of started command process and processes this output line by line after started cmd.exe terminated itself. Empty lines are always ignored by FOR which do not occur here. Lines starting with a semicolon are also ignored by default because of eol=; is the default definition for end of line option. But a full qualified folder path cannot contain a semicolon at beginning because the folder path starts either with a drive letter or with a backslash in case of a UNC path. So default end of line option can be kept in this case. FOR would split up by default every line into substrings with using normal space and horizontal tab as string delimiters and would assign just first space/tab separated string to specified loop variable. This line splitting behavior is not wanted here as the folder path contains definitely a space character and the entire folder path is needed and not just the string up to first space. For that reason delims= is used to specify an empty list of delimiters which disables line splitting behavior.
FOR executes for every directory output by DIR passing FINDSTR filter with full path the command RD to remove the directory quietly because of option /Q and with all files and subdirectories because of /S.
The deletion of the directory could fail because of missing NTFS permissions, or the directory to delete or one of its subdirectories is current directory of a running process, or a file in the directory to delete is currently opened by a running process in a manner which denies deletion of the file while being opened, or the directory to delete does not exist anymore because it was deleted already before in FOR loop. The error message output by command RD to handle STDERR is in this case redirected to device NUL to suppress it.
Please note that command RD deletes all log and log-archives directories and not just the files and subdirectories in these directories. It is unclear from your question what exactly should be deleted by the batch file.
It is of course also possible to replace rd /Q /S "%%I" by del /A /F /Q "%%I\*" to delete just all files including hidden and read-only files quietly in the directory assigned with full path to loop variable I.
# left to command FOR and command RD just suppress the output of those commands before execution by Windows command processor cmd.exe. Both # are not needed if this single command line is used in a batch file containing before #echo off.
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.
del /?
dir /?
findstr /?
for /?
rd /?
If you're wanting to remove the contents of the log and log-archive directories. This means the easiest solution would probably be FORFILES.
This will delete all the files under log and log-archive directories found within any subfolder of "C:\Program Files\product\plugins". The folders MUST be named exactly log or log-archive. It will not remove directories.
FORFILES /P "C:\Program Files\product\plugins" /M log /C "cmd /c if #isdir==TRUE DEL /s /q #path\*"
FORFILES /P "C:\Program Files\product\plugins" /M log-archive /C "cmd /c if #isdir==TRUE DEL /s /q #path\*"
You could also add a /D switch to only delete applicable files if they are older than a specific number of days. This will delete all the log and log-archive files under "C:\Program Files\product\plugins" that are older than 90 days:
FORFILES /D -90 /P "C:\Program Files\product\plugins" /M log /C "cmd /c if #isdir==TRUE DEL /s /q #path\*"
FORFILES /D -90 /P "C:\Program Files\product\plugins" /M log-archive /C "cmd /c if #isdir==TRUE DEL /s /q #path\*"
I'm trying to find recursively all "MyApp.exe" apps in "C:\Builds" folder and run the apps with "createdatabase closeimmediately" arguments/parameters.
What I search so far:ForFiles Microsoft docs
Here is the forfiles pattern:
forfiles [/p <Path>] [/m <SearchMask>] [/s] [/c "<Command>"] [/d [{+|-}][{|}]]
Here is what I have:
forfiles /p c:\Builds /s /m MyApp.exe /c "cmd /c start #path" "createdatabase closeimmediately"
If I run above script, it is showing error:
ERROR: Invalid argument/option - 'createdatabase closeimmediately'.Type "FORFILES /?" for usage.
If I run without parameteres, it finds apps correctly and runs, but I need to run with parameters:
forfiles /p c:\Builds /s /m MyApp.exe /c "cmd /c start #path"
How can I run apps with parameters in ForFiles?
I know this was mentioned in the comments, but the comments are becoming too long for me to post a decent comment still, so here is an answer. This should do exactly what you want, it will recursively search for the file and execute if exists.
#echo off
for /r "c:\Builds" %%i in (myapp.exe) do if exist "%%i" "%%i" createdatabase closeimmediately
a slightly different way, find all executables, and launch if the name matches myapp.exe:
for /r "c:\Builds" %%i in (*.exe) do if /I "%%~nxi" == "myapp.exe" "%%I" createdatabase closeimmediately
There are multiple methods possible to search for MyApp.exe in C:\Build and all subfolders and execute the found executable with the two parameters createdatabase and closeimmediately.
The first solution uses command FOR to search for any file matching the wildcard pattern MyApp*.exe in C:\Build and any non-hidden subfolder.
For usage in a batch file:
for /R "C:\Build" %%I in ("MyApp*.exe") do if /I "%%~nxI" == "MyApp.exe" "%%I" createdatabase closeimmediately
For usage in command prompt window:
for /R "C:\Build" %I in ("MyApp*.exe") do #if /I "%~nxI" == "MyApp.exe" "%I" createdatabase closeimmediately
It is necessary that the string inside the round brackets contains at least one * or ? to define a wildcard pattern. Otherwise FOR would not search for files with name MyApp.exe on using just "MyApp.exe" in C:\Build and all its subfolders. It would simply append the string "MyApp.exe" (with the double quotes) to folder path of every folder found in C:\Build folder structure and would assign folder path + "MyApp.exe" to loop variable I and execute the command line referencing the loop variable.
The IF condition is used to make sure that only MyApp.exe is executed and not for example MyAppOther.exe found by chance also by FOR with wildcard pattern MyApp*.exe. The string comparison is done case-insensitive because of /I.
It would be also possible to use a different wildcard pattern like MyApp.exe*. This could reduce the number of false positives. But for security the IF condition should be nevertheless used.
The second solution is using just MyApp.exe and check if a file with that name really exists in the given folder path before executing it.
For usage in a batch file:
for /R "C:\Build" %%I in (MyApp.exe) do if exist "%%I" "%%I" createdatabase closeimmediately
For usage in command prompt window:
for /R "C:\Build" %I in (MyApp.exe) do #if exist "%I" "%I" createdatabase closeimmediately
MyApp.exe is specified in round brackets without being enclosed in " as otherwise the string assigned to loop variable I would be for example C:\Build\"MyApp.exe" and not C:\Build\MyApp.exe. By automatic error correction the string value C:\Build\"MyApp.exe" might also work depending on which string is really used instead of MyApp.exe. But this is not really a safe method and does not work if the string MyApp.exe contains a space, comma, semicolon, or other characters like &()[]{}^=;!'+,`~.
The third solution is using the command DIR for searching for MyApp.exe without a wildcard pattern to find only files with exactly that name and let FOR execute the found executables with that name.
For usage in a batch file:
for /F "delims=" %%I in ('dir "C:\Build\MyApp.exe" /A-D-H /B /S 2^>nul') do "%%I" createdatabase closeimmediately
For usage in command prompt window:
for /F "delims=" %I in ('dir "C:\Build\MyApp.exe" /A-D-H /B /S 2^>nul') do #"%I" createdatabase closeimmediately
In comparison to FOR the command DIR really searches for files with name MyApp.exe even on argument string not containing a wildcard character like * or ?.
FOR executes the DIR command line in a separate command process started with cmd.exe /C in background and captures everything written to handle STDOUT of this command process.
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 dir command line with using a separate command process started in background.
2>nul is used to suppress the error message output by DIR to handle STDERR by redirecting it to device NUL if no file MyApp.exe could be found in C:\Build or its subdirectories.
DIR outputs because of /B and /S just the full qualified file name, i.e. file path + file name + file extension, of every found MyApp.exe line by line.
FOR processes the captured output line by line with skipping empty lines and lines starting with a semicolon. Such lines are surely not output by DIR with the used options.
FOR would also split up each line into substrings (tokens) on spaces/tabs and would assign only first substring to loop variable I. This string splitting behavior is not wanted here as a folder name could contain one or more spaces. For that reason FOR option delims= is used to define an empty list of delimiters which disables the line splitting behavior.
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.
dir /?
for /?
if /?
I'm trying to use forfiles to check a folder for an updated .xlsx file (less than 1 day old) and then run another bat file depending on whether there was an updated file or not.
Initially I had a script that sort of did this, but some days there were multiple new .xlsx files, so the script "looped" and would run the success.bat file multiple times.
If no updated file is found, cmd will exit with an error, so I made use of that to run the fail.bat file.
This is what I have at the moment:
#echo on
set found=0
forfiles /p C:\ /m *.xlsx /d 0 /c "cmd /k set found=1"
if /i %found%==1 (
Call Success.bat
) else (
if /i %found%==0 (
Call Fail.bat
)
PAUSE
Now this works fine for when there are updated files.. i.e. it gets rid of the loop problem.
BUT I now have the problem with cmd exiting if no updated file is found and using
if ERRORLEVEL 1 call fail.bat
doesn't seem to work for me anymore...
I'm just no good with this if/else stuff with .bat files
forfiles /p c:\ /m *.xlsx /d 0 2>nul && call Success.bat || call Fail.bat
2>nul will suppress error messages, you can also add >nul to suppress standard output (any files found by forfiles). If no files exist with the extension then an errorlevel will be set - the commands following || will only occur if an error level is set.
Therefore, if no files are found with the specified extension this will call Fail.bat, and goto the end of the script. If files are found, it will call Success.bat.