Bat file to delete files only when younger files are present - batch-file

Our backup system creates .bak files everyday which we can use to restore files if we ever run into issues. If left alone these would fill up our storage so I found a batch file that I can run to delete the old batch files everyday after new ones are created.
forfiles -p "c:\xxx\yyy" -s -m *.bak /D -2 /C "cmd /c del #path"
This works fine but I want to create a safety net so that if for whatever reason our backup system fails and the new .bak files aren't created the old .bak files will stay there instead of being deleted, otherwise we would be left with no backup files in the event of an incident. So ideally I want something that will check for .bak files younger than one day and if those files are not present it won't run the above line but if those younger files are present it will run the above line and delete the older files. Not sure if this is possible with batch files or not. Thanks in advance for your help on this.
EDIT: Some more info on what I need. Everyday at around 10pm around 50 backup .bak files are created and put into folder c:\xxx\yyy
These files are quite large so I have set up a batch file to run automatically every day that removes all .bak files that are older than 1 day. This is fine for everyday use but the scenario I have in my head is what if the backup system doesnt create the .bak files for whatever reason. I want the batch file to check to make sure the new .bak files have been created before it deletes the old ones. Basically using a batch file is there a way to check if there is a certain file type in a folder that is newer than 1 day old and can we change what the batch file does depending on the outcome.
These are examples of the files created for the 18th and 19th.
2004 Apr_backup_2017_12_18_210001_2986007.bak
2004 Apr_backup_2017_12_19_210001_3168635.bak
Subscribers_backup_2017_12_19_210003_3012893.bak
model_backup_2017_12_19_210003_2544131.bak
They all seem to follow the below format:
[DESC]_backup_[YEAR]_[MONTH]_[DAY]_21000[1/2/3]_[7 DIGIT NO.].bak

I think, an unknown list of [DESC] strings in all the backup file names is most difficult to handle in batch file. The code could be very simple on knowing this list as it can be seen below, or at least on knowing if those strings do not contain characters being critical on batch file processing like !%=.
But the coding challenge for unknown list of [DESCR] strings with special characters in file names was interesting for me and so I developed first following commented batch file:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "BackupFolder=C:\xxx\yyy"
rem Search for files matching the wildcard pattern *_backup_*.bak in backup
rem folder, assign each file name without file extension to environment
rem variable FileName and call the subroutine GetUniqueDescs to get the
rem file description at beginning of each file name into a list in memory.
for /F "delims=" %%I in ('dir "%BackupFolder%\*_backup_*.bak" /A-D /B /ON 2^>nul') do (
set "FileName=%%~nI"
call :GetUniqueDescs
)
rem Run command SET with FileDesc: to output all environment variables
rem starting with that string in name and sorted by name and process
rem this list whereby each line ends with =1 as value 1 is assigned
rem to each of these environment variables.
rem For each unique file description in output list assign the file
rem description with =1 appended to environment variable FileDesc
rem and run subroutine DeleteFiles.
for /F "tokens=2 delims=:" %%I in ('set FileDesc: 2^>nul') do (
set "FileDesc=%%I"
call :DeleteFiles
)
rem Restore initial environment on starting this batch file and exit it.
endlocal
goto :EOF
rem The subroutine GetUniqueDescs first runs a string substitution which
rem gets the backup pattern part from file name, i.e. everything in file
rem name from _backup_ to end of file name.
rem Then another string substitution is used to remove this string from
rem current file name to get just the description and define an environment
rem variable of which name starts with FileDesc: and ends with the file
rem description. The value assigned to this environment variable is 1.
:GetUniqueDescs
set "BackupPart=%FileName:*_backup_=_backup_%"
call set "FileDesc:%%FileName:%BackupPart%=%%=1"
goto :EOF
rem The subroutine DeleteFiles removes first from passed file description
rem the last two characters being always =1 from list of environment
rem variables starting with FileDesc: and appends the backup wildcard
rem pattern.
rem Command DIR is used to find all files in backup folder starting
rem with current file description and _backup_ and output the found
rem files sorted by last modification date with newest modified file
rem first and oldest modified file last.
rem The command FOR processing this list skips the first file name
rem output by DIR which means the newest file. All other, older
rem files perhaps also found by DIR are deleted one after the other.
:DeleteFiles
set "FilePattern=%FileDesc:~0,-2%_backup_*.bak"
for /F "skip=1 delims=" %%J in ('dir "%BackupFolder%\%FilePattern%" /A-D /B /O-D /TW') do ECHO del "%BackupFolder%\%%J"
goto :EOF
The command ECHO in last but one line before command del results in just getting displayed which files would be deleted instead of really deleting them.
The option skip=1 in last but one line determines how many backup files are always kept.
For example using skip=5 results in keeping the newest five files according to last modification date being usually on backup files also the creation date and deleting all others.
The advantage of such a backup deletion strategy is that it does not matter:
how often a specific backup is created – daily, weekly or monthly;
if last backup creation was successful at all;
if some or even all backup files were deleted manually;
how old each individual backup file is;
how often the batch file for backup files deletion is executed.
What really matters on deletion of backups is the storage size needed for each backup and how much free storage space remains after deletion process. The file date of a backup file is not limiting the free storage size. The file sizes of all remaining backup files and the total storage size on backup media are the factors which really matter. That's why I do not understand all those "delete older than" questions. Who has to care about age of a file as long as there is enough free space for new files?
The file creation date could be also used by using /TC instead of /TW in last but one line. But the file creation date is the date on which the file was created in that directory and not on which the file itself was created. For that reason the file creation date is only useful when the file was never copied or moved to another directory since first time creation.
I tested this batch file on following files:
C:\xxx\yyy\2004 !Apr_backup_2017_12_18_210001_2986007.bak
C:\xxx\yyy\2004 !Apr_backup_2017_12_19_210001_3168635.bak
C:\xxx\yyy\model%_backup_2017_12_19_210003_2544131.bak
C:\xxx\yyy\model%_backup_2017_12_20_210003_2544131.bak
C:\xxx\yyy\Subscribers=_backup_2017_12_19_210003_3012893.bak
C:\xxx\yyy\Subscribers=_backup_2017_12_20_210003_3012893.bak
The last modification date of each file matched the date in file name.
The output of the batch file was:
del "C:\xxx\yyy\2004 !Apr_backup_2017_12_18_210001_2986007.bak"
del "C:\xxx\yyy\model%_backup_2017_12_19_210003_2544131.bak"
del "C:\xxx\yyy\Subscribers=_backup_2017_12_19_210003_3012893.bak"
That is the expected result. The older file of each file pair would be deleted.
Then I thought getting [DESC] part of file name could be done easier as the remaining part of file name without file extension has a fixed length of 33 characters.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "BackupFolder=C:\xxx\yyy"
rem Search for files matching the long wildcard pattern
rem *_backup_????_??_??_??????_???????.bak
rem in backup folder and assign each file name without
rem file extension to environment variable.
rem The last 33 characters are removed from each file name to get the
rem file description part at beginning of each file name. Then define
rem an environment variable of which name starts with FileDesc: and
rem ends with the file description. The value assigned to this
rem environment variable is 1.
for /F "delims=" %%I in ('dir "%BackupFolder%\*_backup_????_??_??_??????_???????.bak" /A-D /B /ON 2^>nul') do (
set "FileName=%%~nI"
call set "FileDesc:%%FileName:~0,-33%%=1"
)
rem Run command SET with FileDesc: to output all environment variables
rem starting with that string in name and sorted by name and process
rem this list whereby each line ends with =1 as value 1 is assigned
rem to each of these environment variables.
rem For each unique file description in output list assign the file
rem description with =1 appended to environment variable FileDesc
rem and run subroutine DeleteFiles.
for /F "tokens=2 delims=:" %%I in ('set FileDesc: 2^>nul') do (
set "FileDesc=%%I"
call :DeleteFiles
)
rem Restore initial environment on starting this batch file and exit it.
endlocal
goto :EOF
rem The subroutine DeleteFiles removes first from passed file description
rem the last two characters being always =1 from list of environment
rem variables starting with FileDesc: and appends the backup wildcard
rem pattern.
rem Command DIR is used to find all files in backup folder starting
rem with current file description and _backup_ and output the found
rem files sorted by last modification date with newest modified file
rem first and oldest modified file last.
rem The command FOR processing this list skips the first file name
rem output by DIR which means the newest file. All other, older
rem files perhaps also found by DIR are deleted one after the other.
:DeleteFiles
set "FilePattern=%FileDesc:~0,-2%_backup_*.bak"
for /F "skip=1 delims=" %%J in ('dir "%BackupFolder%\%FilePattern%" /A-D /B /O-D /TW') do ECHO del "%BackupFolder%\%%J"
goto :EOF
That batch file containing also ECHO left to command del in last but one line produces the same result on the six files in the backup folder.
I don't know if the batch file could be even more optimized without knowing which characters could exist in [DESC] part of the file names. I did not think about a possible further optimization.
Let us assume the list of unique [DESC] strings is well known and can be hard coded in the batch file, for example 2004 !Apr, model% and Subscribers= for the six files in my test case:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "BackupFolder=C:\xxx\yyy"
for %%I in ("2004 !Apr" "model%%" "Subscribers=") do for /F "skip=1 delims=" %%J in ('dir "%BackupFolder%\%%~I*_backup_*.bak" /A-D /B /O-D /TW 2^>nul') do del "%BackupFolder%\%%J"
endlocal
This batch file really deletes files because there is no ECHO in last but one line.
Oh yes, knowing the individual backup file names makes everything much easier.
The batch file can be even optimized to a single command line:
#for %%I in ("2004 !Apr" "model%%" "Subscribers=") do #for /F "skip=1 delims=" %%J in ('dir "C:\xxx\yyy\%%~I*_backup_*.bak" /A-D /B /O-D /TW 2^>nul') do #del "C:\xxx\yyy\%%J"
Best and simple backup deletion concept
On a backup storage media is created:
a backup of an entire machine with file name ComputerName_backup_YYYY_MM.tib every three months which is huge as taking 200 GiB and where it is enough to have only last backup on the backup storage media;
a backup of a folder with files not often updated with file name Folder_backup_YYYY_MM_DD.zip every Saturday which takes about 400 MiB on storage media where it is enough to be able to restore the last four weeks;
a backup of a database file with file name Database_backup_YYYY_MM_DD.bak every day which takes at the moment 20 MiB per backup, but is growing more or less constant as typical for database files and on where it should be possible to restore data entries of the last seven days.
The required minimum storage media size is:
(1+1) × 200 GiB + (4+1) × 400 MiB + (7+1) × (20×3) MiB
A storage media size of 1 TiB is really enough for approximately the next three years depending on growing rate of database backup on which an increase by a factor of three is included already in calculation.
It would be best to delete all backup files no longer needed on creating the daily database backup to keep the backup files management simple by using a single and simple batch file.
#echo off
set "BackupFolder=C:\xxx\yyy"
call :DeleteBackups 1 "ComputerName"
call :DeleteBackups 4 "Folder"
call :DeleteBackups 7 "Database"
goto :EOF
:DeleteBackups
for /F "skip=%1 delims=" %%I in ('dir "%BackupFolder%\%~2*_backup_*" /A-D /B /O-D /TW 2^>nul') do del "%BackupFolder%\%%I"
goto :EOF
Deletion of no longer needed backups can be really so easy on thinking about right strategy.
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 /?
dir /?
echo /?
endlocal /?
for /?
goto /?
rem /?
set /?
setlocal /?
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 lines 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.

Here's an untested example script which should work as long as you don't have file names where [DESC] contains _, = or other problematic characters.
#Echo Off
SetLocal DisableDelayedExpansion
For /F "Delims==" %%A In ('Set _[ 2^>Nul') Do Set "%%A="
If /I Not "%CD%"=="C:\xxx\yyy" (Set "_[:]=T"
PushD "C:\xxx\yyy" 2>Nul||Exit /B)
For /F "Tokens=1* Delims=_" %%A In ('Dir /B /O-N *_backup_*_*_*_*_*.bak'
) Do If Defined _[%%A] (Del /A /F "%%A_%%B") Else Set "_[%%A]=T"
If Defined _[:] PopD
EndLocal
Exit /B

Related

Batch: Preserve (creation-)date on copying files to another (flatten) folder structure, incl. renaming files to avoid doublettes

this is my first question, so I apologize beforehand if I write not as you are used to...
fact:
I've a deep folder structure with tons of files (images,videos and so on) so I want to copy that files to a flat structure for a better overview.
I want to keep (at least) the original date attributes: creation-date and last-modified date.
Problem 1) there are files with same name 00001.jpg in different folders which I want to have in same folder, so I want to add creation date/time to filename on copy process.
00001.jpg becomes 2015-11-17_11-23-35_00001.jpg
So far so good. Or not good...
Copy and XCopy doesn't give me an option to do that, without loosing at least the creation date information (I didn`t find a solution with both).
Now I try to copy the files (file by file) with robocopy to new folder and use ren on the copied file to "pre-set" the date/time information before the filename.
Here is a simple test.bat example:
setlocal enableextensions enabledelayedexpansion
robocopy . ./zzz original.txt /copy:DATSO
pause
rem :: formatted creation date of original file will be here, in real life
set "myDate=2015-11-17-_11-23-35"
rem rename "./zzz/original.txt" "!myDate!_renamed.txt" ::doesnt work: why? relative path??
rem :: this will do what I want - original creation date is kept on copy file
FOR %%A IN (zzz/original.txt) DO REN "%%~fA" "!myDate!_%%~nxA"
[possibly] Problem2) Is there a better way to do this, or could I run into thread problems (asynchronous execution). Could it be, that I try to rename a file before the robocopy has finished the copy process (e.g. for large files)?
Sorry I'm a totally batch newbie (also as poster in SO ;).
ThanX in advance for each tip and also for critics on my solution approach. Maybe I have the horse-blinkers on my head and dont see the easy solution?!
[edit: formatting of post]
[edit: content of post -> date/time in front of filename for better sorting]
It is possible to use command DIR to get recursive listed all files in the specified folder and its subfolders with creation date/time.
The format of the date/time depends on Windows Region and Language settings.
Example output for F:\Temp\Test on my machine with English Windows 7 and region is configured to Germany on running the command line dir F:\Temp\Test\* /A-D /S /TC:
Volume in drive F is TEMP
Volume Serial Number is 1911-26A4
Directory of F:\Temp\Test
25.09.2017 17:26 465.950 SimpleFile.ccl
1 File(s) 465.950 bytes
Directory of F:\Temp\Test\Test Folder
25.09.2017 17:26 360.546 Test File.tmp
1 File(s) 360.546 bytes
Total Files Listed:
2 File(s) 826.496 bytes
0 Dir(s) 58.279.460.864 bytes free
This output is filtered with findstr /R /C:"^ Directory of" /C:"^[0123][0123456789]" to get only lines starting with  Directory of (note the space at begin) or with a number in range 00 to 39.
Directory of F:\Temp\Test
25.09.2017 17:26 465.950 SimpleFile.ccl
Directory of F:\Temp\Test\Test Folder
25.09.2017 17:26 360.546 Test File.tmp
And this filtered output is processed by FOR loop and the commands executed by FOR.
#echo off
for /F "tokens=1-2*" %%A in ('dir "F:\Temp\Test\*" /A-D /S /TC ^| %SystemRoot%\System32\findstr.exe /R /C:"^ Directory of" /C:"^[0123][0123456789]" 2^>nul') do (
if "%%A %%B" == "Directory of" (
set "FilePath=%%C"
) else (
set "CreationDate=%%A"
set "CreationTime=%%B"
for /F "tokens=1*" %%I in ("%%C") do set "FileName=%%J"
call :RenameFile
)
)
goto :EOF
:RenameFile
set "NewName=%CreationDate:~-4%-%CreationDate:~3,2%-%CreationDate:~0,2%_%CreationTime:~0,2%-%CreationTime:~3,2%_%FileName%"
ren "%FilePath%\%FileName%" "%NewName%"
goto :EOF
It would be advisable to put command echo before command ren in last but one line to first verify the expected new file names.
ren "F:\Temp\Test\SimpleFile.ccl" "2017-09-25_17-26_SimpleFile.ccl"
ren "F:\Temp\Test\Test Folder\Test File.tmp" "2017-09-25_17-26_Test File.tmp"
Note: The batch file must be in a folder not processed by this batch file as otherwise the batch file itself would be renamed while running which breaks the processing 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.
call /?
dir /?
echo /?
for /?
goto /?
if /?
ren /?
set /?
By the way: WinRAR can add files into a RAR archive with creation and last access time in addition to last modification time and extract the files to a different directory with restoring also creation and last access time using SetFileTime function of Windows kernel.
currently I use Locale independent date. I use tokens from that for currrent date/time.
for /f "tokens=2 delims==" %%I in ('wmic os get localdatetime /format:list') do set datetime=%%I
rem :: format it to YYYY-MM-DD_hh-mm-ss
set myDateTime=%datetime:~0,4%-%datetime:~4,2%-%datetime:~6,2%_%datetime:~8,2%-%datetime:~10,2%-%datetime:~12,2%
Thats not the problem.
To clarify:
The listing is also not the problem.
I loop throw all related files without a prob (except batch files and output dir and its sub tree ;).
My problem is:
I call robocopy for each file, then I rename the file to save the original creation date. My fear is that it makes problems (Multi-Threading?) for large files and for the number of calls (many thousend times)
Is batch execution really serialized? Is the process waiting for robocopy, that it has finished, before I try to rename file. Could I run into dead-locks for vry large files? I'll test it with some fake gigabyte-files.
Your suggestion, to use winrar sounds interesting.
If I could add all that files to a big archive (with structure) and at the end extract it to target dir... I'll try it ;)
If it doesn't work I will program it in java!
There I know what to do, thats my playground :D
I thought it would be easy to write a simple batch file, to do this for me, but it seems it's not as easy as I thought!
ThanX anyway

Batch file to copy files with unique timestamps measured to the minute

Like the question says, I have a folder full of photos but many of the photos are duplicates. The pictures are in groups of 10-15 in the same minute and then the next group has a timestamp of a few minutes later. I want to copy 1 photo from each group to a new folder. I found some code that I think might mostly work, but the command copies and excludes based on file name, not timestamp. Any help would be greatly appreciated.
For %%F In ("G:\Bad\*.*") Do If Not Exist "G:\Good\%%~nxF" Copy "%%F" "C:\Good\%%~nxF"
This file copying task could be done with following batch file:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "LastFileTime="
for %%I in ("C:\Path To\Source Folder\*") do (
if "%%~tI" NEQ "!LastFileTime!" (
set "LastFileTime=%%~tI"
copy /B /Y "%%I" "C:\Path To\Destination Folder\" >nul
)
)
endlocal
Command FOR runs for each non hidden file in specified source folder the IF comparison which compares case-sensitive last modification time of current file with the file time last assigned to environment variable LastFileTime using delayed expansion as required here because the environment variable value is changed and referenced in same command block.
On a difference the last modification file time of current file is assigned to the environment variable and the file is copied to destination folder which must already exist on running this batch file.
Note: On an NTFS drive the list of file names processed by FOR is sorted by file name and not by file time. On a FAT16, FAT32 and ExFAT drive the list of file names is not sorted at all. This means this simple approach works only reliable on an NTFS drive with all files with same last modification date have nearly the same file name.
So it would be better to process the list of files sorted by last modification time for example using following code:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "LastFileTime="
cd /D "C:\Path To\Source Folder"
for /F %%I in ('dir * /A-D /B /OD /TW 2^>nul') do (
if "%%~tI" NEQ "!LastFileTime!" (
set "LastFileTime=%%~tI"
copy /B /Y "%%I" "C:\Path To\Destination Folder\"
)
)
endlocal
DIR outputs all directory entries
matching the wildcard pattern * (*.* is interpreted as * by Windows),
not having attribute directory set because of /A-D which means only the names of files including hidden and system files,
in bare format because of /B which means just file name without any additional information,
with the output list ordered by date/time because of /OD with oldest first and newest last
whereby the date/time to use for sort is the last modification (write) date because of /TW which would not be necessary as the last modification date is the default.
The help output by running dir /? in a command prompt window explains also all these options. And running just dir * and next dir * /A-D /B /OD /TW in a command prompt window in a directory with files and comparing the two output lists makes the difference very clear.
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.
cd /?
copy /?
dir /?
echo /?
endlocal /?
for /?
if /?
set /?
setlocal /?
Read also the Microsoft article about Using Command Redirection Operators to understand >nul used here to suppress the message about number of copied files by redirecting it to device NUL.
2>nul suppresses the error message output by DIR if there is no file in source directory by redirecting the error message output to handle STDERR to device NUL whereby the redirection operator > needs to be escaped here with caret character ^ for being parsed as literal character on parsing the FOR command line by Windows command interpreter and as redirection operator later on execution of DIR command line in a separate command process.

How to create folder based on first and second part of file name and move files into the folder?

I created the following batch script to create a folder based on today's date and then group files into folders based on the file name.
For example the files
JIM_BRICKMAN_QPS.avi
JIM_BRICKMAN_Slice.avi
JIM_BRICKMAN_Slice.jpg
are moved to the folder BRICKMAN.
This works fine, however, attempts to modify the batch file to move the newly created folders into the newly created date folder fail or overwrite the folders when going through the loop.
for /F "tokens=1-4 delims=/" %%A in ('date /t') do (
set DateDay=%%A
set DateMonth=%%B
set DateYear=%%C
)
set CurrentDate=%DateDay%-%DateMonth%-%DateYear%
if not exist "%CurrentDate%" md %CurrentDate%
for %%A in (*.avi *.jpg) do (
for /f "tokens=1,* delims=_" %%D in ("%%~nA") do (
md "%%D" 2>nul
echo Moving file %%A to folder %%D
move "%%A" "%%D" >nul
)
)
echo Finished
Additionally, I can't seem to get the token to ignore the first delimiter so that the folder is titled JIM_BRICKMAN and not just BRICKMAN.
EDIT:
I rewrote the batch file after the suggestions in the comments:
set "CurrentDate=%DATE:~-10,2%-%DATE:~-7,2%-%DATE:~-4%"
if not exist "%CurrentDate%" md %CurrentDate%
for %%A in (*.avi *.jpg) do (
for /f "tokens=1,2 delims=_" %%D_%%E in ("%%~nA") do (
md "%%D_%%E" 2>nul
move "%%A" "%%D_%%E" >nul
)
)
But the script seems to bomb out. I tried to capture the error, but it closes despite me putting PAUSE in the script.
Double clicking on a batch file in development is no good idea because this results in starting
%SystemRoot%\System32\cmd.exe /c "batch file name with full path and extension"
As it can be read on running cmd /? from within a command prompt window, the option /C means close the command process and its console window immediately after execution of command, executable or script finished independent on the reason for ending the execution.
For debugging a batch file in development it is much better to
open a command prompt window,
change the current directory with command CD to directory of batch file and
run the batch file by typing its name and hitting key RETURN or ENTER.
For batch files which should work independent on which directory is the current directory, it is advisable to omit point 2 and run the batch file with entering its full path, file name and file extension enclosed in double quotes with current directory being not the directory of the batch file.
A batch file is executed from within a command prompt window with:
%SystemRoot%\System32\cmd.exe /K BatchFileNameAsTyped
The option /K means keep command process running which results in keeping also command prompt window opened after execution of command/executable/script which makes it possible to read error messages.
The keys UP ARROW and DOWN ARROW can be used to reload command lines once entered in command prompt window making it easy to run the batch file once again after making a modification in GUI text editor.
And with having #echo off removed from first line of batch file, or changed to #echo ON, or commented out this line with command REM or with :: (invalid label) at beginning, it is also possible to see which lines Windows command interpreter really executes after applying immediate environment variable expansion and where an error occurs in case of a syntax error.
Wrong on second batch code is the line:
for /f "tokens=1,2 delims=_" %%D_%%E in ("%%~nA") do (
Specified as loop variable must be always 1 character. Right would be:
for /f "tokens=1,2 delims=_" %%D in ("%%~nA") do (
The command echo %DATE% outputs on my computer with my account and my region settings today the date 01.04.2017.
The command echo %DATE:~-10,2%-%DATE:~-7,2%-%DATE:~-4% outputs 01-04-2017.
So this part of the script works.
Hint: A list of directories in format YYYY-MM-DD is better than in format DD-MM-YYYY. The list of directories with format YYYY-MM-DD sorted alphabetically as by default is automatically with this date format also sorted from oldest to newest. Date format DD-MM-YYYY results in a weird list of the directories on being sorted alphabetically as by default.
A batch file for this task could be:
#echo off
set "CurrentDate=%DATE:~-10,2%-%DATE:~-7,2%-%DATE:~-4%"
for %%A in (*.avi *.jpg) do (
for /F "tokens=1,2 delims=_" %%D in ("%%~nA") do (
if not "%%E" == "" (
md "%CurrentDate%\%%D_%%E" 2>nul
move /Y "%%A" "%CurrentDate%\%%D_%%E\"
) else (
md "%CurrentDate%\%%D" 2>nul
move /Y "%%A" "%CurrentDate%\%%D\"
)
)
)
set "CurrentDate="
How the inner loop works is most interesting for this task.
for /F and "%%~nA" means the command FOR should process just the file name of the *.avi or *.jpg file without file extension found by outer FOR loop.
delims=_ means the FOR command should split up the string into multiple parts (tokens) using underscore as delimiter. The first file name is JIM_BRICKMAN_QPS which would be split up to:
JIM assigned to loop variable D being specified in FOR command line,
BRICKMAN assigned to loop variable E which is the next character in ASCII table after D and
QPS assigned to loop variable F.
This string split feature is the reason why loop variables are interpreted case-sensitive while environment variables are interpreted not case-sensitive.
With tokens=1,2 is specified that just first and second string parts are of interest. So inner FOR can stop string splitting after having already determined the first two underscore delimited strings and having assigned them to the loop variables D and E.
FOR executes the command block if it could determine at least 1 string delimited by an underscore. So it is possible that loop variable D has a string value, but loop variable E is an empty string, for example if the file name does not contain any underscore. That is the reason for the IF condition.
The command MD creates with command extensions enabled as by default the entire directory tree. Therefore it is not necessary to create the date subdirectory explicitly before searching for *.avi and *.jpg files. That is good as it avoids creating empty date directories when there are no *.avi and *.jpg files in current directory.
As the *.avi and *.jpg files in current directory should be moved to DD-MM-YYYY\Token1_Token2 it is of course necessary to specify also the environment variable with todays date string on creating the directory and moving the file.
The error message output by MD if the directory exists (or when it fails to create the directory because of missing permissions) to handle STDERR is redirected with 2>nul to device NUL to suppress it.
The MOVE command is used with option /Y to move the file to target folder even if the current file exists already in target folder.
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 /?
for /?
if /?
md /?
move /?
set /?
Read also the Microsoft article about Using Command Redirection Operators.

How to check if zip or rar file contains 1 or more files?

For the purposes of saving space and organizing, I'm zipping bunch of files in my local and networked folders. They are mainly CAD files, like stp, igs, etc.
There are already existing zip files and some are extracted by other users, but the zip file still exists on the folders, which eats up space.
Is there a command line zip, rar, 7z. etc. to find out if an archive file contains only 1 file?
I'd like to figure this out as I'll extract the archives with single files in to the current directory whilst extracting archives with 1+ files to \archivename\ folder. Otherwise one folder with 30 STP files, will suddenly have 30 folders and 30 files extracted in them which I don't want.
I currently use a batch file with WinRAR to extract and another program to check for duplicates, then WinRAR batch to re-zip them based on file extension. (Reason: people use different archive methods and there are duplicates of files all over.)
Sample batch files:
for /F "delims=," %%f in ('dir *.stp /B' ) do (%path% a -afzip -r- -m5 -ed -ep -or -tl -y -df "%%f".zip "%%f")
for /F "delims=;" %%f in ('dir *.7z /B /S' ) do (%path% x -OR -ilogC:\Users\XXXX\Desktop\myLog.txt "%%f" "%%~dpf"\"%%~nf"\)
Once I can check for number of files in a zip, I'll add a recursive function.
I can use NTFS compression, but I also want to organize the folders, some folder have 1000 files in them, I surely want to reduce that to 1. These are mainly for archiving purposes.
Any help or thought would be appreciated.
I suggest the following commented batch file for this task:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Extract all 7-Zip, RAR and ZIP archives in current directory into
rem subdirectories with name of archive file as name for subdirectory (-ad)
rem with running WinRAR for extraction in background (-ibck) which means
rem minimized to system tray with restoring also last access time (-tsa)
rem and creation time (-tsc) if existing in archive file and with skipping
rem files on extraction perhaps already present in the subdirectory with
rem same last modification time (-u), but overwriting automatically older
rem files in subdirectory if archive file contains an existing file with
rem a newer last modification time (-y) ignoring all errors (also -y).
for %%I in (7z rar zip) do "%ProgramFiles%\WinRAR\WinRAR.exe" x -ad -ibck -tsa -tsc -u -y *.%%I
rem If a subdirectory contains only 1 file, move that file to the current
rem directory with overwriting a perhaps already existing file with same
rem name in current directory and then remove the subdirectory.
for /D %%I in (*) do call :CheckSubDir "%%I"
rem Exit processing of the batch file without fall through to subroutine.
endlocal
goto :EOF
rem The subroutine CheckSubDir first checks for directories in directory
rem passed as parameter to the subroutine. A directory containing at
rem least one subdirectory is kept without any further processing.
rem If the directory does not contain a subdirectory, it searches for files
rem in the directory. If there are at least 2 files, the directory is kept
rem without any further processing.
rem But if the directory contains only 1 file, this file is moved to
rem current directory. Then the empty directory is deleted before exiting
rem the subroutine and continue batch file processing in calling loop.
rem Each directory containing no subdirectory and no file is removed, too.
:CheckSubDir
for /F "delims=" %%D in ('dir /AD /B "%~1\*" 2^>nul') do goto :EOF
setlocal EnableDelayedExpansion
set FileCount=0
for /F "delims=" %%F in ('dir /A-D /B "%~1\*" 2^>nul') do (
set /A FileCount+=1
if !FileCount! == 2 endlocal & goto :EOF
set "FileName=%%F"
)
if %FileCount% == 1 move /Y "%~1\%FileName%" "%FileName%"
rd "%~1"
endlocal
goto :EOF
Please read the comments for details what this batch file does on execution using WinRAR.
The batch file contains much more comment lines than real command lines.
2>nul in the last two FOR loops redirects the error message output by command DIR to handle STDERR in case of no directory or no file found to device NUL to suppress it. The redirection operator > must be escaped here with character caret ^ to be interpreted as redirection operator on execution of DIR command line and not already on parsing the FOR command line.
WinRAR supports many archive types on extraction. But WinRAR.exe is a GUI application and therefore does not support listing the contents of an archive file to console as Rar.exe supports.
The console version Rar.exe as well as free console application UnRAR.exe support both listing the archive file contents to handle STDOUT in various formats.
This difference on supported commands between WinRAR.exe and Rar.exe/UnRAR.exe can be seen by opening in WinRAR the help by clicking in menu Help on menu item Help topics, opening on help tab Contents the list item Command line mode, opening the list item Commands, clicking on list item Alphabetic commands list and comparing this list with the commands listed and described in text file Rar.txt in program files folder of WinRAR which is the manual for the console version.
Rar.txt lists and describes:
l[t[a],b] ... List archive contents [technical [all], bare]
v[t[a],b] ... Verbosely list archive contents [technical [all], bare].
Help of WinRAR does whether contain command l nor command v.
It would be of course also possible to run Rar.exe or UnRAR.exe on each *.rar file with command lb, count the number of lines output as done in above batch file to count the files and extract the *.rar archive file depending on the line count to current directory (1 line only) or to a subdirectory.
But it should be taken into account that on using bare list format and only 1 line output this line can be the name of an archived file or the name of an archived empty folder. The solution would be using standard list command and more analyze the attributes as well because a directory has attribute D while file does not have this attribute.
And for *.7z and *.zip files the same must be coded using 7z.exe or 7za.exe. The help of 7-Zip describes also the available commands and switches like the help of WinRAR.
But all those efforts do not make much sense in comparison to posted solution as the archive file has to be extracted at all and moving a file is done very fast as just the entry in file allocation table is modified and no data are copied or moved at all.
Running 7-Zip or Rar separately for first just listing each archive file contents, analyzing the list, and running 7-Zip or Rar once again on archive file for extraction is much slower than running WinRAR just 3 times (or less) to extract all archives and then move some files and remove some directories.
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 /?
dir /?
echo /?
endlocal /?
for /?
goto /?
move /?
rd /?
set /?
setlocal /?
See also the Microsoft TechNet article Using command redirection operators.
Taking the question literal the following batch uses 7z.exe (has to be reachable via the Path) list (l)-option to get the number of files included in the archive by filtering the last line.
#Echo off
Set Base=q:\Test
Pushd "%Base%"
For /f "delims=" %%A in (
'Dir /B/S/A-D *.zip *.7z *.rar'
) Do For /f "tokens=5" %%B in (
' 7z.exe l "%%~A" ^| findstr "files$" '
) Do If %%B equ 1 (
Echo Archive %%A contains 1 file
) else (
Echo Archive %%A contains %%B files
)
Popd
Sample Output:
Archive q:\Test\archiv.7z contains 135 files
Archive q:\Test\PoSh\powershellitunes\PowerScript-itunes.7z contains 1 file
Archive q:\Test\PoSh\_pdf_itextsharp\extract_pdf_pages_into_new_323689.zip contains 3 files
Archive q:\Test\_StackOverflow\Noodles\Filter0.8.zip contains 4 files
Archive q:\Test\2016\12\16\Path.rar contains 7 files
Archive q:\Test\_AllHelp.Win\allhelp.zip contains 7 files
Archive q:\Test\2017-02\pkzipc_40.rar contains 10 files

How to archive files older than 7 days with 7-ZIP by creating one archive for all files with same date

There is a CLOSE solution to my problem, and everything is described in this question:
How to archive files older than 7 days with creating one archive for all files with same date?
The thing is, i need another solution similar to this but working for 7-Zip, this has to be coded in the bat file i believe since there is no -to7d switch in 7-Zip like there is in Winrar.
The Code right now ( Credit goes to #Mofi for creating this ):
#echo off
setlocal EnableExtensions EnableDelayedExpansion
rem Define the directories to use for backup task.
set "LogDirectory=D:\tet\Web3811\Web3811\log"
set "BakDirectory=D:\tet\Web3811\Web3811\LogsBackup"
rem Get all file names in log directory into a list file sorted by last
rem modification date with oldest file at top and newest at bottom.
rem Note: /S is important to get the file names with complete path.
dir "%LogDirectory%\*" /A-D /B /OD /S /TW 1>"%BakDirectory%\LogFiles.lst" 2>nul
rem Jump to clean up stage if no file found in the log directory.
if errorlevel 1 goto :CleanUp
rem Delete list file for all files with same day if file exists
rem for example from a previous execution of this batch file
rem which was terminated manually by a user during execution.
if exist "%BakDirectory%\DayFiles.lst" del "%BakDirectory%\DayFiles.lst"
set LastDate=none
for /F "usebackq delims=" %%F in ( "%BakDirectory%\LogFiles.lst" ) do (
set FileTime=%%~tF
rem Get just file date from file time in format DD-MM-YYYY.
rem The file time string format depends on date and time
rem format definition in Windows language settings.
rem Therefore the line below must be adapted if date format
rem is whether DD.MM.YYYY nor DD-MM-YYYY nor DD/MM/YYYY.
set FileDate=!FileTime:~6,4!-!FileTime:~3,2!-!FileTime:~0,2!
rem Is the last modification date of this file different
rem to last modification date of the previous file?
if not "!FileDate!"=="!LastDate!" (
rem Nothing to archive on first difference.
if not "!LastDate!"=="none" call :ArchiveLogs
rem Exit loop if RAR has not archived any file which means
rem all other files are modified within the last 7 days.
if "!LastDate!"=="ExitLoop" goto CleanUp
rem Start creating a new list.
set LastDate=!FileDate!
)
rem Append name of this file with path to current day list.
echo %%F>>"%BakDirectory%\DayFiles.lst"
)
rem Jump to clean up stage if no list file with files to archive.
if not exist "%BakDirectory%\DayFiles.lst" goto CleanUp
rem Otherwise with no log file created or modified within
rem the last 7 days, but at least one older file exists
rem nevertheless, archive all those files in list file.
call :ArchiveLogs
:CleanUp
del "%BakDirectory%\LogFiles.lst"
endlocal
goto :EOF
:ArchiveLogs
"C:\Program Files (x86)\7-Zip\7z" -sdel a -mmt -mx3 -tzip "%BakDirectory%\!LastDate!_Logs.zip" "#%BakDirectory%\DayFiles.lst"
if errorlevel 10 set LastDate=ExitLoop
del "%BakDirectory%\DayFiles.lst"
Basically there just needs to be added a check that check if Modified date = older than 7 days.
i'm not sure how to implement this, since once you run the :ArchiveLogs 7-Zip will just take EVERYTHING in that folder and as far as i can see there are no switches to check for that.
I believe we have to do something like this:
Code to check if file has a modification date older than 7 days & save that file name in a value.
:ZipOnlyafter7days
For:
"C:\Program Files (x86)\7-Zip\7z" -sdel a -mmt -mx3 -tzip "%BakDirectory%!LastDate!_Logs.zip" "#%BakDirectory%\ Filename_Value "
You should use robocopy with the /maxage:7 option to copy relevant files to a temp folder, and then compress all the files using 7zip.
The below works for files with a number of days old from a batch file
forfiles /P (SOURCE_folder) /S /M *.* /D -(number of date old) /C "cmd /C 7Z.exe a "Archive.ZIP #file"
I am just working to try and get the date correct.

Resources