Batch File Copy User Profiles With an Age Limitations - windows

I thought this would be easy but I'm stuck. I have a batch file that will let me back up all 'Actual' (not system) user profiles which is fine & working good. However, an age limitation to the profile folders being backed up has to be applied. Only backing up all the user profiles folder with a last modified date of 60 days is allowed, rather just blind copying the whole lot (time & disk space etc.)
I thought adding the /MAXAGE switch to the robocopy line would suffice but seems even if the profile is recently active (within the date copy criteria) important files older than 60 days within the folder structures are left out - which is not good!
I had a look into Forfiles which seemed would be useable but doesn't deal with folder dates, just files (it seems?). Also the Dir /T:W switch but outputs syntax I can't blend into the current batch file.
Here is the code...
#echo off
pushd %~dp0
color 5F
for /F "Skip=1 Tokens=* Delims=\" %%G in (
'"WMIc Path Win32_UserProfile Where (Special!='True') Get LocalPath"'
) Do for /F "delims= " %%H in ("%%G") do (
for /F "Tokens=3,4 delims=\" %%I in ("%%H") do (
robocopy "%%H" "C:\IT\%%I" /XD AppData /XD "Application Data" /XD "Local Settings" /XD *Drive* /MAXAGE:60 /E /R:0 /XA:SH
)
)
)
pause
So my question is, how can I make it check the last modified date of a user profile folder & back it up (including subfolders/files that maybe older than 60 days) as long as the root user profile folder has modified attribute within the last 60 days, else older, dont back up.
Hope that makes sense, thanks for any info / tips anyone may advise.
C:\Users\Admin Last Mod 12/12/2020 (>backup)
C:\Users\Dez.Smith Last Mod 19/11/2020 (>backup)
c:\Users\Smiley.Pippet Last Mod 16/09/2020 (<dont backup)
etc....
New Code...
#echo off
color 5F
for /f "tokens=2 delims==;." %%a in ('"wmic path Win32_UserProfile where (Special!='True' and LastUseTime is not null) get LastUseTime,LocalPath /VALUE | find "LastUseTime""') Do (
for /f "tokens=2 delims==" %%b in ('"wmic path Win32_UserProfile where (Special!='True' and LastUseTime is not null) get LastUseTime,LocalPath /VALUE | find "LocalPath""') Do (
echo %%a
pause
if %%a GTR 20200109230000 echo %%b
)
)
)
pause
From the output of the command I get
LastUseTime=20201212194437.654000+000
LocalPath=C:\Users\Admin
I was thinking I can strip that into what I need. So have trimmed the date to just 20201212194437 & also just C:\Users\Admin.
If I set a number to 60 days or what ever 20200109230000 (%%a) (1st Sept 11:00pm 00 secs) I thought I could use the GTR switch against it to then robocopy %%b
but it doesn't echo back...Not sure whats happening as if I echo %%a & %%b they resolve the snippets I want...cheers

The following script might accomplish what you want (see the rem-comments in the code):
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_TARGET=D:\Backup\Profiles" & rem // (destination directory of the backup)
set /A "_MAXAGE=60" & rem // (maximum age in days of user profile)
rem // Retrieve current date/time:
for /F "delims=" %%U in ('
wmic path Win32_OperatingSystem get LocalDateTime /VALUE
') do for /F "delims=" %%V in ("%%U") do set "%%V"
rem // Loop through non-special non-roaming user profiles:
for /F "delims=" %%U in ('
wmic path Win32_UserProfile where "Special=FALSE and RoamingConfigured=FALSE and LastUseTime is not null and LocalPath is not null" ^
get LastUseTime^,LocalPath /VALUE
') do for /F "tokens=1* delims==" %%V in ("%%U") do (
rem // Store current item, which can be the last usage date or the source path:
set "%%V=%%W" & set "Name=%%~nxW" & setlocal EnableDelayedExpansion
rem // Check what the current item is:
if "%%V"=="LastUseTime" (
endlocal
rem // Current item is last usage date, so get difference to current date:
call set /A "RelUseDate=(((%LocalDateTime:~,4%*12+%LocalDateTime:~4,2%)*36525+%LocalDateTime:~6,2%00)-((%%LastUseTime:~,4%%*12+%%LastUseTime:~4,2%%)*36525+%%LastUseTime:~6,2%%00))/100"
) else if !RelUseDate! leq %_MAXAGE% (
rem // Current item is source path, so copy when last usage data is not too long ago:
ECHO robocopy "!LocalPath!" "!_TARGET!\!Name!" *.* /PURGE /XD "AppData" /XJ
endlocal
) else endlocal
)
endlocal
exit /B

Related

Batch How to avoid a same 2nd FOR loop?

I have created a batch script to backup some subfolders from a path A to a path B (Z:\Folder1\… > U:\Backup\…).
The script lists the subfolders inside path A and increments a number for each of them.
Then, I just have to enter the number(s) of the subfolder(s) I want to backup and xcopy does the rest.
The problem is that sometimes I have thousands of subfolders in path A and only a few to backup (10 or 15).
What I would like is that once I enter the number of these folders, it will go straight to the backup without having to loop all the subfolders inside path A AGAIN (which take time).
Here is my batch :
#echo off
setlocal EnableDelayedExpansion
rem Script for backuping some subfolders from path A to path B
set BackupLocation=U:\Backup
set subfolder_no=1
FOR /F "delims=" %%a IN ('dir /b "Z:\Folder1\*"') DO (
set subfolder=%%a
if defined subfolder (
echo !subfolder_no! !subfolder!
set /a subfolder_no+=1
)
)
set /a subfolder_no=%subfolder_no%-1
set /a index=0
set /a choice=-1
echo.
set /p choice=Enter the number(s) of the subfolder(s) you want to backup:
FOR /F "delims=" %%a IN ('dir /b "Z:\Folder1\*"') DO (
set subfolder=%%a
if defined subfolder (
set /a index+=1
)
FOR %%f IN (%choice%) DO if %%f==!index! (
echo.
echo Backuping subfolder !subfolder!
xcopy "Z:\Folder1\!subfolder!" "%BackupLocation%\!subfolder!\" /e /i /y
)
)
echo.
pause
exit
How can I do this ? Is it possible to get the subfolders' name from their matching number and store them in variables or something ?
Thanks a lot for your help !
Here's an example which only enumerates the directories once.
Please note, whilst it technically answers your question, and performs the task you require of it, this version is designed to work as intended on Windows 10. The specific part you asked about works in other versions too, but the :BackUp labelled section uses a new and undocumented Windows 10 feature of the sort command, to return only unique items from the selection list. Without that, your end user could tehnically provide the same number multiple times, and thus trigger multiple backups of the same directory. As this part of the code is technically outside of the scope of your question, I will leave it up to you to modify the code section yourself, should you be deploying this on older Operating Systems.
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Rem Script for backing up some subdirectories of a Source path to a Backup path.
Set "SourceLocation=Z:\Folder1"
Set "BackupLocation=U:\Backup"
If Not Exist "%Sourcelocation%\." Exit /B
If Not Exist "%Backuplocation%\." Exit /B
:ShowSet
For /F "Delims==" %%G In ('"(Set subdirectory[) 2>NUL"') Do Set "%%G="
Set "index=0"
For /F "EOL=? Delims=" %%G In ('Dir /B /A:D /O:N "%SourceLocation%" 2^>NUL'
) Do (Set /A index += 1
Set "subdirectory=%%G"
SetLocal EnableDelayedExpansion
Echo !index! !subdirectory!
For /F "Tokens=1,*" %%H In ("!index! !subdirectory!") Do (EndLocal
Set "subdirectory[%%H]=%%I"))
If Not Defined subdirectory[1] Exit /B
:Select
Echo(
Echo Please type the number(s) for the subdirectories you want to backup,
Echo(
Echo For multiple selections please separate each with spaces e.g. 1 3 6
Echo For none please type 0 or press [ENTER].
Set "selection=0"
Set /P "selection=>"
Set "selection=%selection:"=%"
Set "selection=%selection:)=%"
If Not Defined selection GoTo Select
If "%selection%" == "0" GoTo :EOF
(Set selection) | "%SystemRoot%\System32\findstr.exe"^
/X /R /C:"selection=[0123456789 ][0123456789 ]*" 1>NUL || GoTo Select
Set "selection=%selection% "
:BackUp
For /F %%G In (
'"(Echo(%selection: =^&Echo(%) | "%SystemRoot%\System32\sort.exe" /Unique"'
) Do If Not Defined subdirectory[%%G] (Echo Selection %%G was not valid) Else (
SetLocal EnableDelayedExpansion
Echo(&Echo Backing up subdirectory %%G !subdirectory[%%G]!
"%SystemRoot%\System32\Robocopy.exe" ^
"%SourceLocation%\!subdirectory[%%G]!" ^
"%BackupLocation%\!subdirectory[%%G]!" /E /NC /NDL /NJH /NJS /NS ^
| %SystemRoot%\System32\find.exe /V " (0x00000005) "
EndLocal)
Pause
In the :BackUp section, I have used Robocopy.exe instead of the deprecated xcopy.exe utility you had used.
If you wish to still use xcopy.exe, replace:
"%SystemRoot%\System32\Robocopy.exe" ^
"%SourceLocation%\!subdirectory[%%G]!" ^
"%BackupLocation%\!subdirectory[%%G]!" /E /NC /NDL /NJH /NJS /NS ^
| %SystemRoot%\System32\find.exe /V " (0x00000005) "
with:
"%SystemRoot%\System32\xcopy.exe" ^
"%SourceLocation%\!subdirectory[%%G]!" ^
"%BackupLocation%\!subdirectory[%%G]!\" /E /I /Y

How to compare today's date with a file's last modification date without regional settings influence?

It is possible to get the current date on a Windows system and the last modification date of a file, like thise:
Current date : date /T
Last modification date : ±echo %~tI% (where %I is the file within a FOR loop)
Both however depend on regional settings, so they need to be replaced by something else:
Current date : wmic os get localdatetime
Last modification date : ???
Does somebody know how to fill in the question marks?
Background information, the idea is to get a result like the following:
Current date : 20170323115047.782000+060
Last modification date : 20170323120513.0123
Chop off the first eight characters (in order to get the day)
Current date : 20170323
Last modification date : 20170323
This makes it possible to see that the file has indeed been modified today.
I want to avoid regional settings in order to be sure of the amount of characters I need to chop off.
For timestamp comparisons, you could use forfiles to find files modified today. This solution is more graceful than chopping substrings off of WMI query results I think.
#echo off & setlocal
set "file=notes.txt"
forfiles /d +0 /m "%file%" >NUL 2>NUL && (
echo %file% was modified today.
) || (
for %%I in ("%file%") do echo %%~I was last modified %%~tI
)
forfiles /? in a cmd console for more info. The forfiles command exits 0 on match, 1 on no match, allowing the use of conditional execution. Also, see this related question.
The following batch output it's own lastmodified datetime via wmic.
#Echo off&SetLocal
set File=%~f0
:: need to escape \ with another one \\
set File=%File:\=\\%
for /f %%a in (
'wmic DataFile where "Name='%File%'" get lastmodified ^| findstr "^[0-9]" '
) do Echo File:%File% lastmodified:%%a
Sample output
> 12-16.cmd
File:Q:\\Test\\2017-03\\23\\12-16.cmd lastmodified:20170323123900.908030+060
LotPings' script gives you the idea how to start but there's a little bit more on complete check. Try this:
#echo off
setlocal
::change to the needed file or to the command line argument
set fileLoc=%~f0
set fileLoc=%fileLoc:\=\\%
for /f "tokens=* delims=" %%# in ('wmic os get localdatetime /format:value') do (
for /f "tokens=* delims=" %%a in ("%%#") do set "%%a"
)
::echo %LocalDateTime%
set today=%LocalDateTime:~0,8%
echo today: %today%
for /f "tokens=* delims=" %%# in ('wmic DataFile where "Name='%fileLoc%'" get lastmodified /format:Value ') do (
for /f "tokens=* delims=" %%a in ("%%#") do set "%%a"
)
echo %lastmodified%
set lmodified=%lastmodified:~0,8%
if "%lmodified%" equ "%lmodified%" (
echo modified today
) else (
echo hasnt been modified today
)
endlocal
Here's substring is explained : https://ss64.com/nt/syntax-substring.html
Rojo is correct that FORFILES can be used to list files that have been modified today. But it is S.L..O...W.....
ROBOCOPY is much faster :-)
The following will list recently modified files in the current directory.
robocopy . . /l /is /maxage:1 /njh /njs /nc /ns /ndl /fp
You can specify a specific path if you want
robocopy "c:\your\path" "c:\your\path" /l /is /maxage:1 /njh /njs /nc /ns /ndl /fp
You can add the /S to recursively search all child folders
robocopy "c:\your\path" "c:\your\path" /l /is /maxage:1 /njh /njs /nc /ns /ndl /fp /s
Although date /T/%DATE% and %%~tI return the locale-dependent date formats, they can still be used for (non-)equality comparisons as the formats are the same, given that you split off the time portion of %%~tI at the first SPACE, and that the SPACE is not used as date separator (which is very unlikely and uncommon):
for %%I in ("C:\TEMP\test.txt") do set "AGE=%%~tI"
set "AGE=%AGE: =" & rem "%"
if "%AGE%"=="%DATE%" echo File has been modified today.
After quite some work, I've found a way to get everything up and running (based on Lotping's answer):
set Filename=F:\\DIR\\Filename.exe
for /f %%a in ('wmic datafile where "Name='!Filename!'" get "lastmodified"^|findstr [0-9]') do set Filename_DATE=%%a
echo Filename_DATE=!Filename_DATE!
set FilenameFinalDATE=!Filename_DATE:~0,8!
echo !FilenameFinalDATE!
(Beware, everything is important here (double slashes, double percentages, single and double quotes, ...))

Batch file that counts the number of files in EVERY folder in a directory, and outputs results to a text file

What I'm trying to do is make a batch file that'll recursively go into every folder and count the number of files in each folder. However, I've spent the last hour trying various things and it isn't working.
I want the output to look like:
X:Y where X is the folder name and Y is the # of files in X.
setlocal EnableDelayedExpansion
set current=blank
FOR /D %%G in ("*") DO set current=%%G && call:count
:count
set count=0
for %%A in (*) do set /a count+=1
echo !current!:!count!>>"D:\User\Some\Directory\count.txt"
But this doesn't work. The output is giving the same number for each folder. The number it's outputting is the number of files in the directory itself, which I think is the problem.
Specifically, if I'm in C:\User\Example and it has three folders, A, B, and C, I want the number of files in C:\User\Example\A and so on, but it's giving me the number of files in C:\User\Example. Hope that makes sense.
Note: In my use case, the folders will not contain sub-directories.
A little different approach.
#echo off
FOR /D %%G in ("*") DO (
PUSHD "%%G"
FOR /F "delims=" %%H in ('dir /a-d /b * ^|find /C /V ""') DO echo %%G %%H>>"..\count.txt"
POPD
)
this works:
note that you have to collate set current=%%G&& call:count or current value is "A " (trailing space, not that you want)
And when you scan the subdir, you have to prefix * by your current dir
#echo off
del count.txt
setlocal EnableDelayedExpansion
set current=blank
FOR /D %%G in ("*") DO set current=%%G&& call:count
goto end
:count
set count=0
for %%A in (!current!\*) do set /a count+=1
echo !current!:!count!>>"count.txt"
:end
The answers to this question looks like a collection of different ways to do the same thing! Here it is another one:
#echo off
setlocal EnableDelayedExpansion
for /F "tokens=1,2 delims=\" %%a in ('findstr /M /S "^" *.*') do (
if "%%b" neq "" (
set "name=%%a"
set /A "count[!name: =_!]+=1"
)
)
(for /F "tokens=2,3 delims=[]=" %%a in ('set count[') do (
set "name=%%a"
echo !name:_= !:%%b
)) > count.txt
This method look complicated because the management of spaces in the folders names. If folders don't have spaces, 6 lines (and Delayed Expansion) could be removed; that is:
#echo off
setlocal
for /F "tokens=1,2 delims=\" %%a in ('findstr /M /S "^" *.*') do (
if "%%b" neq "" set /A "count[%%a]+=1"
)
(for /F "tokens=2,3 delims=[]=" %%a in ('set count[') do echo %%a:%%b) > count.txt
Here is a PowerShell script to handle it:
Get-ChildItem -Path . -Recurse -ErrorAction SilentlyContinue -Force
| Where-Object { $_.Attributes -eq "Directory"}
| ForEach-Object {Write-Host $_.Name, $_.GetFiles().Count }
The prints to the screen, but writing to a file is simple.
The scans all the way down. If you just want the first layer of folders, remove the "-Recurse"
The advantage is that you are doing real actions on real objects, instead of trying to trick the OS into doing things it's not really intended for, by parsing text.
Here, it should be relatively simple to figure out what this is doing, even if you have little knowledge of PowerShell (Get a list of children, from this path, which are directories, and for each of them, print it's name, and the count of files in it.)

Copy all files except the last modified file to a new directory

I need to move files from c:\prueba1 to c:\prueba99 but I don't know how to make a comparison betweeen all files in the source directory (c:\prueba99) to move all files in the directory with the exception of the last modified file in the directory. I know there is a wmic command with get InstallDate, LastModified, but I don't know the ms-dos syntaxis to asign a variable and compare it to know that one file readed is the last modified
I found an example:
for /f "delims=" %%A in ('wmic datafile where "drive = 'c:' and path='\\windows\\'"
get LastModified^,Name /format:table^|find ":"^|sort /r') do #echo %%A
And tried to modify it with no result because it appears to just list the datafile names but not the files themselves.
This is my modified version:
for /f "skip=1 delims=" %%A in ('wmic datafile where "drive = 'c:' and path='\\prueba1\\'"
get LastModified^,Name /format:table^|find ":"^| sort/r') do move (%%A) c:\prueba99
for /f "skip=1 delims=" %%a in ('dir /b /tw /o-d /a-d c:\prueba1\*.*'
) do move "c:\prueba1\%%a" "c:\prueba99"
dir command get the files in descending creation date order, so the first is the latest. for command iterates over this list, skipping the first one, moving the files to the target folder.
A way to ge the last modified file :
#echo off
set $path=c:\prueba99
for /f %%a in ('dir/b/a-d/o-d "%$path%"') do (
set $Last=%%a
goto:next)
:next
echo Last modified : [ %$Last% ]
This should work for you and it also allows you to discard as many NEWEST files as you want (for your case I've taken 1):
#echo off
setlocal
:: change the next two statements to match what you want
set srcdir=C:\prueba1
set tgtdir=C:\prueba99
if not exist %tgtdir%\*.* md %tgtdir%
set ctr=0
for /f "tokens=*" %%a in ('dir "%srcdir%" /o-d /b') do call :maybemove "%%a"
set /a ctr-=3
echo %~n0: %ctr% files were moved from %srcdir% to %tgtdir%
endlocal
goto :eof
::--------------------
:maybemove
:: increment counter and bypass the ONE newest file
set /a ctr+=1
if %ctr% leq 1 goto :eof
:: remove the double-quotes from the front and back of the filename
set fn=%~1
:: perform the move
move /y "%srcdir%\%fn%" "%tgtdir%"
goto :eof

Display file name and creation time ONLY of all files in a directory tree

Host: Win 7
I have a product that I updated today (patched if you will). I would like to see specifically which files were added to the base installation directory tree.
The expected result would ONLY display creation timestamp and full file path/name. I do not want to see directory summary information or any other breaks in the data. The output would be a very straight forward looking listing like this:
08/06/2012 11:02 AM c:\my_product_install_path\folder3\new_file_3.xml
08/06/2012 11:01 AM c:\my_product_install_path\folder2\new_file_2.c
08/06/2012 11:01 AM c:\my_product_install_path\new_file_1.h
...
...
I think from this top-down listing of the full install directory tree, I can easily see the new files from the old files by scrolling down and looking for the first file with time older than X.
How can I do this either at the Windows command shell (or in a cygwin shell)?
"dir /s /b" is very close but it does not display the creation time stamp.
You might think that "dir /s /b /T:C" would work since /T:C is for displaying the creation time but no. The /T:C option does not "overide" the /b option to display the timestamp So, somehow I need to get the benefits of the /b option with the timestamp added in.
This is a cumbersome native solution for the command line, but it works :-)
for /d /r %F in (.) do #pushd "%F"&(for /f "eol= delims=" %S in ('2^>nul dir /tc /a-d *') do #for /f "tokens=1-4*" %A in ("%S") do #echo %A %B %C %~fE)&popd
It looks better when formatted across multiple lines in a batch file.
#echo off
for /d /r %%F in (.) do (
pushd "%%F"
for /f "eol= delims=" %%S in ('2^>nul dir /tc /a-d *') do (
for /f "tokens=1-4*" %%A in ("%%S") do #echo %%A %%B %%C %%~fE
)
popd
)
You might want to sort the list in reverse chronological order so that all the new files are clustered at the top. It is trivial to do a chronological sort within each directory using the DIR /ODN option. But it is quite tricky to sort chronologically across all the directories. That requires parsing the time stamp info and reformatting it in a way that allows SORT to produce a chronological listing. Unfortunately, parsing the time stamp is very locale dependent.
Here is a batch solution that works with the OP's locale setting. It sorts first chronologically by creation timestamp, then alphabetically by full file path.
#echo off
setlocal
set "tempFile=%temp%\listCreateTime%random%.txt"
>"%tempFile%" (
for /d /r %%F in (.) do (
pushd "%%F"
for /f "eol= delims=" %%S in ('2^>nul dir /tc /a-d *') do (
for /f "tokens=1-6* delims=/ " %%A in ("%%S") do (
if "%%D"=="12:00" (
echo %%C%%A%%B%%E00:00%%~fG*%%A/%%B/%%C %%D %%E %%~fG
) else (
echo %%C%%A%%B%%E%%D%%~fG*%%A/%%B/%%C %%D %%E %%~fG
)
)
)
popd
)
)
for /f "tokens=2 delims=*" %%A in ('sort /r "%tempFile%"') do echo %%A
del "%tempFile%"
It is also possible to use WMIC to get and sort the information. It is much simpler because the timestamp is already formatted in a way that allows SORT to sort it chronologically, so there is no need to parse. It is also locale independent - it should work on any Windows machine in the world that supports WMIC. But this method is slower, and the ouput is not as easy to read. Of course extra coding could be added to parse out substrings from the timestamp and reformat to a more familiar form.
#echo off
setlocal disableDelayedExpansion
set "tempFile=%temp%\listCreateTime%random%.txt"
>"%tempFile%" (
for /d /r %%F in (.) do (
set "folder=%%~pnxF\"
set "drive=%%~dF"
setlocal enableDelayedExpansion
2>nul wmic datafile where "drive='!drive!' and path='!folder:\=\\!'" get name, creationDate|findstr /brc:[0-9]
endlocal
)
)
sort /r "%tempFile%"
del "%tempFile%"
The timestamp is in YYYYMMDDhhmmss.ddddddzzzz
YYYY = year
MM = month
DD = day
hh = hour in 24 hour format
mm = minutes
ss = seconds
dddddd = microseconds
zzzz = timezone info expressed as number of minutes difference from GMT

Resources