FORFILES no longer recursive when used with FOR loop - windows

I have this code below in a batch file (Windows). It returns a list of paths for each .jpg file in a directory. Then passes that list to the ImageMagick Convert function (not the Windows convert) to create a pdf.
echo moving CMD to drive location at %~dp1
CD /d %~dp1
echo getting the list of file names
FORFILES /p %~dp1 /s /m "*.jpg" /C "cmd /c echo #path" > files.txt
echo creating the pdf using ImageMagick
convert #files.txt test.pdf
This works fine for returning the jpg image data or another single file type. I need it to search for multiple image file types and I have seen this example solution where you can put the FORFILES into a FOR loop.
for %%G in ( .jpg , .tif ) do FORFILES /p %~dp1 /s /m "*.jpg" /C "cmd /c echo #path" > files.txt
But if I do that the program is no longer recursive. The /s in forfiles no longer works.
I have tried FOR /R but it doesn't handle folder names with spaces which I need to be able to do.
Any thoughts on how to keep this recursive and not have issues with folder names with spaces?

#Lưu Vĩnh Phúc
thank you for responding. I tried what you said but For /F was throwing an error where it thought .jpg was a file and it couldnt find it.
You did force me to try new things and in doing so I think it was just my syntax that was the issue. I changed the code to this:
FOR %%G in (.jpg, .tif) do FORFILES /p %~dp1 /s /m *%%G /C "cmd /c echo #path" >> files.txt
Where I removed the /r or /f and added the variable from the for loop to the forfiles loop.
This keeps its recursive nature and returns only the file types listed in the for loop.

Related

When I put a batch program in the task scheduler, it didn't finish the whole task, so why did this happen?

This problem is from the scheduler task, batch program, or the files in a folder that I want to delete it using the batch program?
and this is the batch program:
forfiles /p "D:\nameOfFolder" /s /m *.* /d -7 /c "cmd /c del #path"
Replace /M *.* by /M * to even include files without extension, because forfiles treats wildcards differently than most other commands. /M * may even be omitted since this is the default setting anyway.
Regard that forfiles iterates both files and directories, so you need to exclude the latter, because del may try to delete its contents then. Therefore, implement a condition based on the forfiles-specific variable #isdir.
forfiles /S /P "D:\nameOfFolder" /M * /D -7 /C "cmd /D /C if #isdir==FALSE del #path"
As a side note, never append a trailing backslash to a (quoted) path, because something like /P "D:\" would let the command fail since \" constitutes an escaped quotation mark for forfiles, ruining the command line. You may however specify /P "D:\.".
Are you sure you even have such files?
I tried the following (very similar) command:
forfiles /p "C:\Temp_Folder" /s /m . /d -7 /c "cmd /c echo #path"
ERROR: No files found with the specified search criteria.
One thing I noticed, is that the name of the directory should not end with a backslash:
forfiles /p "C:\Temp_Folder" is working fine.
forfiles /p "C:\Temp_Folder\" is not working.
. on its own is not a valid searchmask to supply to /m.
You ned to use *.* (all) *.ext (the supplied extension) *string*.* (files with names containing the string).
? is also supported as a searchmask to match any character. example: *.?a* would match any file with an extension type with a as the second character

Why is Forfiles recursing even though I'm not using the /S parameter?

In my Windows batch file on Windows Server 2008 R2 Standard, I'm trying to use the Forfiles command without recursing through my files, but it still recurses even though I am not using the /S parameter.
How can I get it to stop recursing
I've tried it with both #path and #file and even with the /s. When I use the /S, the amount of recursion is ridiculous!
ForFiles /p "C:\temp" /d -30 /c "cmd /c dir #path" >temp.txt
In the code above, I expect the temp.txt to only show files in the specified folder, not any of it's subfolders.
Let's consider what you are asking the system to do:
Forfiles /p "C:\temp" /d -30
The above command does pretty much what you intended, find any file/folder older than 30 days.
/c "cmd /c dir #path"
This does not do what you think. You are effectively asking cmd to dir each match found by forfiles and do the full dir of each #path, including folders. So let's say you have a dir older than 30 days:
C:\Temp\oldInstalldir\
You are telling cmd.exe to do:
dir "C:\Temp\oldinstalldir"
as well as dir for each file in c:\temp again. So what you really want is to list the files you found older than 30 days which forfiles already found for you, so a working solution is to just echo them.
ForFiles /p "C:\temp" /d -30 /c "cmd /c echo #path">temp.txt
Or by filename only (no path):
ForFiles /p "C:\temp" /d -30 /c "cmd /c echo #file">temp.txt
I have a temporary solution you could try. It is kinda hacky but it might work for what you are trying to do.
Add /M "\*.\*" to your command and it will skip the directories unless they have . in the name which most usually do not. I didn't see it recursing the subdirectories but there was no way to mask the directories with that command. I tried a few attempts using
dir /b /a-d
This gets a list of all the not directories but I couldn't pipe the rusults into the forfiles command to get it to use that as the input to do the date masking. Sorry for the
partial solution.

ForFiles to batch move & rename file(s) with timestamp

I have a bunch of *.mp4 & *.jpg files created on an hourly/daily basis, which use the file structure below:
-- before:
c:\video\2017-10-15\21hour\jpg\12.13.15[M][0#0][0].jpg
c:\video\2017-10-15\21hour\mp4\12.13.01-12.14.32[M][0#0][0].mp4
c:\video\2017-10-18\16hour\jpg\21.42.31[M][0#0][0].jpg
c:\video\2017-10-18\16hour\mp4\21.42.31-21.45.38[M][0#0][0].mp4
I want all the *.jpg & *.mp4 files to get moved to c:\video\ & also completely ditch the old name for 'date_time' + extension, similar to below:
-- after:
c:\video\2017-10-15_12.13.15.jpg
c:\video\2017-10-15_12.13.01-12.14.32.mp4
c:\video\2017-10-18_21.42.31.jpg
c:\video\2017-10-18_21.42.31-21.45.38.mp4
I discovered ForFiles, and was able to use the /s to recursively search all subfolders & move the *.mp4 & *.jpg to a single location:
forfiles /p c:\video\ /s /m *.mp4 /c "cmd /c move #PATH c:\video\"
forfiles /p c:\video\ /s /m *.jpg /c "cmd /c move #PATH c:\video\"
Now for the renaming, at this point I can almost taste victory as this gives me the desired filename output:
forfiles /p c:\video\ /s /m *.mp4 /c "cmd /c echo #FDATE_#FTIME.mp4"
forfiles /p c:\video\ /s /m *.jpg /c "cmd /c echo #FDATE_#FTIME.jpg"
But when I replace echo with either ren/rename/move, I get errors:
The system cannot find the path specified.
The syntax of the command is incorrect.
But the error only happens when I'm using #FDATE and/or #FTIME. These variables all work fine: #FNAME, #ISDIR, #FSIZE
forfiles /p c:\video\ /m *.mp4 /c "cmd /c ren #FILE #ISDIR.#EXT"
forfiles /p c:\video\ /m *.mp4 /c "cmd /c rename #FILE #FSIZE.#EXT"
forfiles /p c:\video\ /m *.mp4 /c "cmd /c move #FILE #EXT.#EXT"
Once I get the renaming part to work, I will combine both the move & rename into one command... But I can't figure out why #FDATE & #FTIME won't take, is there something wrong with my syntax, or is this just something ForFiles doesn't allow?
The problem explained
Firstly, replacing echo with just move or ren alone will not work because it will only detect one argument being passed to it. This could explain the syntax error.
Syntax of ren from Microsoft Docs
ren [<Drive>:][<Path>]<FileName1> <FileName2>
rename [<Drive>:][<Path>]<FileName1> <FileName2>
Syntax of move from Microsoft Docs
move [{/y | /-y}] [<Source>] [<Target>]
Secondly, the variables #FDATE and #FTIME are locale-dependent. Their format will depend on your device's regional formatting, e.g. #FDATE's format could contain / and #FTIME's format contains :. These are reserved characters and could result in an error if misused. This could explain the path error.
A solution using forfiles
If you want to do this with forfiles you will have to use the for command, like this. You can change the date format by extracting substrings like in this answer. You can change the time format by replacing the colon : with a dot ., explained here.
You could use /m once for .jpg and again for .mp4, but you could also try it like this for loop. This means you would have to nest the for in the example in another for loop.
According to the move /?, if you move just one file you can rename it as well.
Example:
setlocal EnableDelayedExpansion
for %%F in (.jpg, .mp4) do (
for /f "tokens=1,2,3,4 delims=|" %%A in (
'forfiles /p "C:\video" /s /m "*%%F" /c "cmd /c echo #PATH^|#EXT^|#FDATE^|#FTIME"'
) do (
set filepath=%%~A
set extension=%%~B
set datestamp=%%~C
set timestamp=%%~D
set parseddate=!datestamp:~6,4!-!datestamp:~3,2!-!datestamp:~0,2!
move "!filepath!" "C:\video\!parseddate!_!timestamp::=.!.!extension!"
)
)
endlocal
If the file count is large, it will take a little while for batch to finish. forfiles has to read the files first and the for loop has to also go through that file list. Also, forfiles is locale-dependent. If the regional date format of your device is 2020/02/10 then the indices of the substring in my example are not correct.
A solution using PowerShell
.NET Framework's LastWriteTime property is locale-independent, as in it does not dependent on your device's regional formatting. There is also a LastWriteTimeUtc property if you prefer to use UTC. You will still have to use a for loop inside a batch file though.
PowerShell's Get-ChildItem returns all children of the parent folder specified by the -Path parameter and with the -Recurse parameter you can do so recursively. If you want the result to include just .jpg and .mp4 files you should use -Include *.jpg, *.mp4. Normally when using -Include you would need to append \* to the path, but since -Recurse is used it's not necessary.
With Get-ItemProperty and the -Name parameter you can list the Fullname, LastWriteTime and Extension properties. To make it more readable for the for loop they have to be called like $var.Property and concatenated. ToString() must be used to convert LastWriteTime to string and change its format.
Example:
for /f "tokens=1,2 usebackq delims=|" %%A in (
`powershell -Command "& {Get-ChildItem -Path 'C:\video\*' -Recurse -Include *.jpg, *.mp4 | ForEach {Get-ItemProperty -Path $_.FullName -Name FullName, LastWriteTime, Extension} | ForEach {$_.FullName+'|'+$_.LastWriteTime.ToString('yyyy-MM-dd_HH.mm.ss')+$_.Extension}}"`
) do (
move "%%~A" "C:\video\%%~B"
)
Note: You can leave out -Path, but then the first argument must be the path.

Duplicate error when renaming files using FORFILES in BATCH with a search mask

I'm running a batch script using FORFILES with the search mask /M *.log.* on files such as these:
a.log.1
a.log.2
a.log.3
I want to rename them by appending the current date and move them to the destination folder. But I get an error stating:
Duplicate file names
Only the file a.log.1 is moved to its destination and renamed.
This is my code:
for /f "delims=" %%G in ( 'forfiles /s /c "cmd /c echo #path" /d -7 / m *.log.*' ) do ren "%%~G" "%%~nG-%Ret%"
The only thing that differentiates the three files is the extension (.1, .2, or .3). But your target name uses the base name only, without the extension, so of course you get duplicate names. You could change your target name to include the extension using "%%~nxG-%Ret%". (I assume you have defined Ret elsewhere.)
But I don't see any need for your FOR loop - the FORFILES can do the rename directly:
forfiles /s /d -7 /m *.log.* /c "cmd /c ren #path #file0x22-%Ret%0x22"
I don't know what your date format in %Ret% looks like, so I included quotes (0x22) around it just to be safe. It looks weird, but a name like "name.ext""date" works just fine - the quotes are stripped out, but they protect against spaces and special characters.

forfiles error while deleting folders

I am trying to write a script that deletes items in the TEMP folder(s) in Windows 7. I only want it to delete files that are 30 days or older. I'm doing testing in a folder which I have set up in the system's environmental variables as TESTTEMP.
I have the script as follows:
forfiles /p %TESTTEMP% /s /d -30 /c "cmd /c IF #ISDIR==FALSE del #FILE /q"
forfiles /p %TESTTEMP% /s /c "cmd /c IF #ISDIR==TRUE rmdir #FILE"
My logic behind this is that the process should first delete all files within the TESTTEMP directory if the file is older than 30 days, and to check in all the subdirectories. Then I check through the remaining files and if it is an empty directory, remove it.
This script works perfectly - all the files I want to delete are deleted, and those that are supposed to remain, remain. However, I noticed that when I run this batch file, I get the error The system cannot find the file specified. I believe it's having some problem with the rmdir command and not being able to find the directory it just deleted...
Is this something I should be worried about, since the script appears to do what I want it to do?
Better yet, is there a way to display which file is not being found so I can try to figure out what's happening on my own?
Thanks for any help!
(For reference, here is the folder structure before and after the batch file is run, assuming all files are older than 30 days:)
Before:
-TestTemp
-More Test
testfile1.txt
testfile2.txt
testfile3.txt
testfile1.txt
testfile2.txt
testfile3.txt
After:
-TestTemp
You can display files and folders:
forfiles /p "%TESTTEMP%" /s /c "cmd /c IF #ISDIR==TRUE echo rmdir #FILE"
forfiles /p "%TESTTEMP%" /s /d -30 /c "cmd /c IF #ISDIR==FALSE echo del #FILE /q"
If a folder is not empty then it will return a harmless error message. The 2>nul will remove the error message.
forfiles /p %TESTTEMP% /s /c "cmd /c IF #ISDIR==TRUE rmdir #FILE 2>nul"
I was getting this "The system cannot find the file specified" error too, but it went away when I removed the "/s" from the ForFiles call. I did not actually need the /s, but it looks like the poster here did. If you need the recursive delete and can't live with the error (or don't want to swallow it with 2>nul), maybe you could nest a non-recursive ForFiles within a recursive one? Just a thought.

Resources