Copy folder structure, but latest file only - powershell

I have many numbered directories, each containin a set of files:
Folder_abc
index.xml
000001.doc
000002.doc
000003.doc
000004.doc
Folder_bdf
index.xml
000001.xls
000002.xls
Folder_...
DESIRED RESULT (latest version only)
Folder_abc
index.xml
000004.doc
Folder_bdf
index.xml
000002.xls
Folder_...
There is an index.xml in each directory, this of course could be copied with robocopy, seperately if necessary.
I am looking for a solution in powershell or batch-file, perfect would be to use robocopy, though I don't see an approach.

This is basically the code from my comment as an answer with some explanatory remarks and a minor correction, namely excluding index.xml from the files processed by the inner loop:
#echo off
rem // Define constants here:
set "ROOT=D:\Path\To\Source"
set "DEST=D:\Path\To\Destination"
set "NAME=Folder_*" & rem // (name/mask of source sub-directories)
set "FILE=index.xml" & rem // (name of file that must always be copied)
rem // Loop over all sub-directories in the root source directory:
for /D %%D in ("%ROOT%\%NAME%") do (
rem // Reset variable that will hold name of file to copy later:
set "LAST="
rem /* Get all files in sorted manner, except the one that must always be copied;
rem store the name in a variable, which will hold the last file name finally;
rem `/O:D` regards the last modification date; add `/T:C` for the creation date;
rem to sort by name rather than by date, replace `/O:D` by `/O:N` or `/O:NE`: */
for /F "delims= eol=|" %%F in ('
2^> nul dir /B /A:-D /O:D "%%~D\*.*" ^| findstr /I /V /C:"%FILE%"
') do set "LAST=%%F"
rem /* Create destination sub-directory with the same name as the source sub-directory;
rem potential error message in case the sub-directory already exists are suppressed: */
2> nul md "%DEST%\%%~nxD"
rem // Copy the file that always needs to be copied, if there is such:
if exist "%%~D\%FILE%" copy /-Y "%%~D\%FILE%" "%DEST%\%%~nxD\"
rem // Copy the previously determined last file, if any has been found:
if defined LAST call copy /-Y "%%~D\%%LAST%%" "%DEST%\%%~nxD\"
)

I am assuming that the most recenr file couldbe an arbitrarynumber of days old? Is that true?
If all of your directories are only 1 layer deep on a single root folder, then:
#(
SetLocal EnableDelayedExpansion
Echo Off
)
SET "_Root=C:\Some\Folder\Path"
SET "_NewRoot=C:\Some\New\Folder"
For /D %%A In ("_Root") Do (
MD "%_NewRoot%\%%A"
Copy "%_Root%\%%A\index.xml" "%_NewRoot%\%%A\index.xml"
For /F "Tokens=*" %%a IN ('DIR /A-D /B /OD "_Root\%%A\" ^| FindStr /I /V /C:"index\.xml$"') Do (
SET "_File=%%a"
)
Copy "%_Root%\%%A\!_File!" "%_NewRoot%\%%A\!_File!"
)
So, essentially, we need to loop the directory structure and set the value od the _File variable to the files found and, if we order them by date in ascending order, the last value found will he the newest folder, so when we kick out of that loop the variable has been populated with the value you need.
since index is always needed I create the folders and copy index right away and do the second file copy only after a match is found we do ignore "Index.xml" as its already copied and not an appropriate newest file to match anyway

Related

How to create directory from URL path separator (forward slash) when that is present in FileName coded %2f or %2F

As I mentioned in Q-title, I have setup a sublime settings auto-syncing process to Github Gist of my choice when I have passed correct Access Token.
Now this process syncs any file that is present inside a folder(s), considering the relative paths, as DirName%2FFileName.extension where DirName and/or FileName may contain singular spaces(%20) between words(if >2 words in either).
Now when I download that gist thro' DownloadZIP button, it ofcourse downloads the whole gist with multiple files as single zip, which after download can be extracted into it's folder in local system(Windows 10/11 OS).
So, the question finally: When I have downloaded such zip and extracted into folder that contains one/multiple files that have %2f or %2F in their names, how do I create a directory from those filenames and move those files into those directory thro' a batch file if possible, or a PowerShell command, but runnable from Batch script, and then also rename those files to remove all DirName%2f parts from their names ? I know about mv and md commands but how do I create directory from URL coded character group and do those operations ?
Note: DirName%2f parts could be occurring in multiples like DirName1%2fDirName2%2f... if the file that is being synced from local is inside mainfolder/subfolders... relatively.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "#name1=DirName%%2f"
SET "#name2=DirName1%%2fDirName2%%2f"
SET "#name3=DirName1%%2FDirName2%%2F"
SET "#2f=%%2f"
SET "#subs=ABC"
SET #
FOR /f "delims=" %%e IN ("%#2f%") DO (
FOR /f "delims=" %%w IN ("!#name1:%#2f%=%#subs%!") DO ECHO REN "%#name1%" "%%w"
FOR /f "delims=" %%w IN ("!#name2:%#2f%=%#subs%!") DO ECHO REN "%#name2%" "%%w"
FOR /f "delims=" %%w IN ("!#name3:%#2f%=%#subs%!") DO ECHO REN "%#name3%" "%%w"
)
ECHO ===================
FOR %%b IN ("%#name1%" "%#name2%" "%#name3%") DO (
SET "#name=%%~b"
FOR /f "delims=" %%e IN ("%#2f%") DO (
FOR /f "delims=" %%w IN ("!#name:%#2f%=%#subs%!") DO ECHO REN %%b "%%w"
)
)
GOTO :EOF
This demo shows how in batch.
You can try :
[System.Web.HttpUtility]::UrlDecode("DirName%2FFileName.extension")
it gives :
DirName/FileName.extension
Then you can use the CmdLets with noun "Path" to manage your file names.
Get-Command -Noun path
If it exists some / in place of \, Powershel should manage them.
The to work with files and folders is given with CmdLet with noun Item :
Get-Command -Noun item
For example to create a directory :
New-Item -name toto -ItemType Directory

How to take output of dir search and modify specified file

I'm trying to use this script to assist technicians with renaming files that are found on an end users computer within their %appdata% folder. The script works up until this point, but I can't figure out how to take the output of the DIR search to use it as the current directory so I can modify the destination folder.
Basically, I need to make changes to this folder:
C:\users\bob\appdata\local\apps\2.0\'7'\'7'\time...exe_bfe88f94fc69adaa_0005.0011_none_b883acbb6e8d0075
The two 7's or wildcards are always different folder directory names, so that's why I can't use a static path to make changes to these folders. Anyways, here is the script so far, it works just fine in locating the folder I need, but I cannot seem to use the output to specify it as the target so I can make changes to it. Thank you for your help.
cd %appdata%
cd ..
cd local\apps\2.0
dir "time...exe_bfe88f94fc69adaa_0005.0011_none_39f58db4ac6311ec" /ad /s
I've tried using the pipeline argument (|) and the '&' and then using a rename command or a removedir, but it cannot find the file specified.
Thank you for your help!
Here's a batch-file which is a little more direct than just recursing the entire tree. It only steps over the two 'unknown' directory names, (%%~nxG\%%~nxH), and checks there for the named directory, (which you should edit as needed on line 3):
#Echo Off & SetLocal EnableExtensions
Set "DirName=time...exe_bfe88f94fc69adaa_0005.0011_none_39f58db4ac6311ec"
Set "BaseDir=%LocalAppData%\apps\2.0"
Set "AppPath="
For /D %%G In ("%BaseDir%\*")Do For /D %%H In ("%%G\*")Do For %%I In (
"%%H\%DirName%")Do If "%%~aI" GEq "d" Set "AppPath=%%~I"
If Not Defined AppPath Echo %DirName% Not Found & Pause & Exit /B 1
Echo %%AppPath%% = %AppPath% & Pause
Nothing else should be modified except for the last line which I added just to provide some output, (you/your technicians would use "%AppPath%" to reference the target directory from that point forward).
If you were looking for something in powershell, then perhaps this will push you in the right direction:
$DirName = "time...exe_bfe88f94fc69adaa_0005.0011_none_39f58db4ac6311ec"
$AppPath = (RvPa "$Env:LocalAppData\apps\2.0\*\*\$DirName").Path
$AppPath
Once again the last line is just to provide output and show you the variable you'll need to reference your target directory, and the first line will need editing as needed.
This code uses a FOR loop to find directory names that match. There is an ECHO inside the loop since I do not know if multiple directories could be found.
#ECHO OFF
CD "%APPDATA%\.."
FOR /F "delims=" %%A IN ('DIR /S /B /A:D "time...exe_bfe88f94fc69adaa_0005.0011_none_39f58db4ac6311ec"') DO (
ECHO Found directory "%%~A"
SET THEDIR=%%~A
)
DIR "%THEDIR%"
REN "%THEDIR%\file1.txt" "file2.txt"

Trying to rename files based on file size

I am trying to rename some MP4 files based on file size of mp4 files in another directory. I want to name all files with identical sizes to same name. Meaning if the file size of the source file matches the size of file in the comparison directory, the source file is renamed to whatever the compared file is named. Because both directories need to be read recursively I'm thinking it would be easier to make a list for comparison with the info in it in 2 columns by using the DIR /s /b echo %%~zs>>filesizelist.txt command giving me a list like
123456789 movie.mp4
987654321 movie2.mp4
Then pass all source mp4s to the batch file and if %%~za matches a value in first column then ren the file to the
corresponding filename. Is this the best path? I tried to script it to work on the fly and that was both a no-go and the source of my 3 day headache(plus the reference list rarely changes and is obviously easily updated). Can someone please assist me with the script?
I do some test with my mp4, and the code works, and for you perform your test, you w´ll need change/put this 2 line above with the path to your folder/directory (one to keep and another to compare), by replacing in the line code is like this:
`set "_target_to_keeped=C:\Users\ecker\Videos\Target"`
`set "_target_to_rename=C:\Users\ecker\Videos\Ren_it"`
You need add the folder where are files to keep and files to rename (if size+name match) on same lines where are the 2 lines code up in this test (sorry not explain in good English, my English is not help me). By now, is late 01:53, i need sleep... yep! so, have nice code!
#echo off && setlocal enableextensions enabledelayedexpansion
cd /d "%~dp0"
set /a _cnt_in_looping= 1 - 1
set /a _cnt_files_size= 1 - 1
set "_target_to_keeped=C:\Users\ecker\Videos\Target"
set "_target_to_rename=C:\Users\ecker\Videos\Ren_it"
cd /d "!_target_to_keeped!"
for /f "tokens=* delims=^ " %%i in ('^<nul dir /o-d /on /b "*.mp4" 2^> nul ^| find "" /v /c') do set _cnt_in_looping=%%i
for /f "tokens=* delims=^ " %%i in ('^<nul dir /o-d /on /b "*.mp4"') do (
set "_file_now_keep=%%i"
set "_file_now_keeped=%%~zi %%i"
call :_to_compare_:
)
set /a _total_files_renamed=!_cnt_in_looping! - !_cnt_files_size!
set /a _total_files_n_chang=!_total_files_renamed! - !_cnt_in_looping! * -1
echo/Total of files renamed = !_total_files_renamed!
echo/Total of files n chang = !_total_files_n_chang!
endlocal
goto :_end_of_file_:
:_to_compare_:
if not exist "!_file_now_keep!" exit /b
for /f "tokens=*" %%I in ('^<nul dir /o-d /on /b "!_file_now_keep!"') do (
set "_file_now_compare=%%~zI %%I"
set "_path_now_compare=%%~dpI"
if "!_file_now_compare!" == "!_file_now_keeped!" (
rename "!_path_now_compare!\%%I" "%%~zI %%I"
echo/ rename "%%~I" "%%~zI %%I"
if ["!errorlevel!"]==["0"] call set /a _cnt_files_size=!_cnt_files_size! + 1
timeout /t 10
)
)
exit /b
:_end_of_file_:

PowerShell/Batch script to rename each file to include original name + folder name

I have the following folder structure:
April reports
├─01-04-2018
│ ├─approved123.pdf
│ ├─approved123_V2.pdf
│ └─unapproved123.pdf
│
├─02-04-2018
│ ├─approved123.pdf
│ └─unapproved123.pdf
╎
╎
└─30-04-2018
├─approved123.pdf
└─unapproved123.pdf
Each folder for each day in April contains an approved and an unapproved report with the same name ("approved123" or "unapproved123"). Some contain a V2.
I want to rename each one so that the "123" is removed and the folder name (the date) is included in the filename e.g. "approved_01-04-2018". I don't want to completely replace the filename with the name of the folder, I just want it added on at the end separated by an underscore if possible.
Once I've done that, I'd like to delete all the files that contain "unapproved" in the name and delete any "approved" files where there is an "approved_V2" in the same folder (version 2 supersedes the original).
For the first step I have tried writing a batch script (complete beginner in writing batch or powershell scripts) but it's not working or it's adding the folder name to each file multiple times:
#echo off
From the folder "April Reports"
rem Process each date
for /D %%d in (*) do (
cd "%%d"
rem rename all files
for /d %%a in (*) do for %%b in ("%%~dpa\.") do ren "%%~a" "%%~nxb_%%~nxa"
)
The first solution works only on NTFS drives and expects that the batch file is in the directory containing April reports as subdirectory.
#echo off
for /D %%I in ("%~dp0April reports\??-??-????") do (
for %%J in ("%%I\approved*.pdf") do (
if exist "%%I\un%%~nxJ" del /F "%%I\un%%~nxJ"
if exist "%%I\%%~nJ_V*.pdf" ( del "%%J" ) else ren "%%J" "%%~nJ_%%~nI.pdf"
)
)
On NTFS drives the result is:
April reports
01-04-2018
approved123_V2_01-04-2018.pdf
02-04-2018
approved123_02-04-2018.pdf
30-04-2018
approved123_30-04-2018.pdf
But this batch file does not work as expected on FAT32 or exFAT drives because of file April reports\01-04-2018\approved123_V2.pdf is processed three times resulting in having finally the name approved123_V2_01-04-2018_01-04-2018_01-04-2018.pdf because of list of file entries in file allocation table matching the pattern approved*.pdf changes while running the inner loop.
The solution for FAT32 and exFAT drives is using for inner loop a captured list of file names.
#echo off
for /D %%I in ("%~dp0April reports\??-??-????") do (
for /F "delims=" %%J in ('dir "%%I\approved*.pdf" /A-D-H /B 2^>nul') do (
if exist "%%I\un%%~nxJ" del /F "%%I\un%%~nxJ"
if exist "%%I\%%~nJ_V*.pdf" ( del /F "%%I\%%J" ) else ren "%%I\%%J" "%%~nJ_%%~nI.pdf"
)
)
Now the result is the same as with first batch file also on FAT32 and exFAT drives.
The command line dir "%%I\approved*.pdf" /A-D-H /B 2>nul is executed by FOR with using a separate command process started in background with cmd /C. DIR outputs just the file names without path which requires to reference the path with %%I where full qualified file name (file path + file name + file extension) is needed on the other command lines. The file names output by DIR to handle STDOUT of background command process are captured by FOR and then processed line by line. So the changes on file allocation table during running the commands DEL and REN do not matter anymore.
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.
But let us assume the directory structure is as follows according to question Move files up one folder level:
April reports
01-04-2018
dayreports
approved123.pdf
approved123_V2.pdf
unapproved123.pdf
01-04-2018
dayreports
approved123.pdf
unapproved123.pdf
30-04-2018
dayreports
approved123.pdf
unapproved123.pdf
By making a small modification to second batch file the same result can be achieved as posted above.
#echo off
for /D %%I in ("%~dp0April reports\??-??-????") do (
for /F "delims=" %%J in ('dir "%%I\dayreports\approved*.pdf" /A-D-H /B 2^>nul') do (
if exist "%%I\dayreports\un%%~nxJ" del /F "%%I\dayreports\un%%~nxJ"
if exist "%%I\dayreports\%%~nJ_V*.pdf" ( del /F "%%I\dayreports\%%J" ) else move /Y "%%I\dayreports\%%J" "%%I\%%~nJ_%%~nI.pdf" >nul
)
rd "%%I\dayreports" 2>nul
)
dayreports is additionally added to file paths and command MOVE is used instead of command REN to move the remaining file up one level in folder hierarchy with new file name. Command RD is used to delete the folder dayreports if being finally empty.
Here is one more variant which simply processes all approved*.pdf in entire directory tree of the directory containing the batch file working independent on file system and for both directory structures with either the PDF files in directory with date in directory name or in a subdirectory dayreports. It avoids processing approved*.pdf more than once by checking if a file to move/rename contains already the folder name (date) and so can be executed multiple times on entire directory tree.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "delims=" %%I in ('dir "%~dp0approved*.pdf" /A-D-H /B /S 2^>nul') do (
if exist "%%~dpI\un%%~nxI" del /F "%%~dpI\un%%~nxI"
if exist "%%~dpI\%%~nI_V*%%~xI" ( del /F "%%I" ) else call :MoveFile "%%I"
)
endlocal
rem Avoid a fall through to the subroutine.
goto :EOF
rem The subroutine MoveFile assigns path of passed file name ending with
rem a backslash to environment variable FilePath. Next the backslash is
rem removed from file path. Then is checked if the file path ends with
rem folder name "dayreports" in which case also this folder is removed
rem from file path. The file path is processed by command FOR to get
rem the string after last backslash referenced with %%~nxJ which is the
rem name of the folder of which name should be in new file name. Before
rem moving the file with new name containing the date, it is checked if
rem the current file name ends already with an underscore and the name
rem of the folder which should be the date. The subroutine is exited
rem if this condition is true to avoid double processing an already
rem processed file in a previous execution of this batch file.
:MoveFile
set "FilePath=%~dp1"
set "FilePath=%FilePath:~0,-1%"
if /I "%FilePath:~-11%" == "\dayreports" set "FilePath=%FilePath:~0,-11%"
for %%J in ("%FilePath%") do set "FolderName=%%~nxJ"
set "FileName=%~n1"
if "%FileName:~-11%" == "_%FolderName%" goto :EOF
move /Y %1 "%FilePath%\%~n1_%FolderName%%~x1" >nul
goto :EOF
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /? ... explains also %~dp0 which expands to drive and path of argument 0 which is the full path of the batch file ending always with a backslash.
del /?
dir /?
echo /?
endlocal /?
for /?
if /?
move /?
rd /?
ren /?
set /?
setlocal /?
Here a PowerShell solution diving into any subdirectories.
it uses a Regular Expression to identify all relevant name parts
if it detects the un prefix or a file with the same name and _V2 suffix,
it sets a delete flag ($Del)
it then either deletes the current file or renames by the parts replacing the number (123) with the name of the parent directory.
Get-ChildItem *.pdf -Recurse |
Where-Object BaseName -match '(un)?(approved)(\d+)(_v2)?' |
ForEach-Object {$Del = $False
If ($matches[1] -eq 'un') {$Del = $True}
If (Test-Path (Join-Path $_.Directory.Fullname ($_.BaseName+"_V2.pdf"))){$Del = $True }
If ($Del) {
Remove-Item $_.FullName -whatif
} else {
Rename-Item $_.FullName -NewName ("{0}_{1}{2}{3}" -f `
$matches[2],$_.Directory.Name,$Matches[4],$_.Extension) -WhatIf
}
}
The Remove-Item and Rename-Item cmdlets have the -WhatIf paramter appended to only show what would be executed - if the output looks OK remove the _WhatIfs
Sample tree after running the script (with a fake May folder):
> tree /f
Auflistung der Ordnerpfade für Volume RamDisk
Volumeseriennummer : 5566-7788
A:.
├───April reports
│ ├───01-04-2018
│ │ approved_01-04-2018_V2.pdf
│ │
│ ├───02-04-2018
│ │ approved_02-04-2018.pdf
│ │
│ └───30-04-2018
│ approved_30-04-2018.pdf
│
└───May reports
├───01-05-2018
│ approved_01-05-2018_V2.pdf
│
├───02-05-2018
│ approved_02-05-2018.pdf
│
└───30-05-2018
approved_30-05-2018.pdf

How to Delete All files But Excluding Specific Files (Batch File)

I know there are questions here like this before but in my case there are special conditions in specifying the files to be excluded in the "delete" operation.
Filename examples:
A_001.xml
A_002.xml
B_001.xml
B_002.xml
B_003.xml
C_009.xml
D_002.xml
The files above are in one directory and I need to delete all files but retain one copy of each file with the highest counter in filename which are A_002.xm, B_003.xml, C_009.xml and D_002.xml.
Is this possible? I'm new in batch file creation so please help me.. I already know how to delete files and even exclude a specific file extension but I don't know what to do in this scenario.
#ECHO OFF
SETLOCAL enabledelayedexpansion
::
:: establish target directory
:: initialise PREFIX to a name that's invalid in a filename
::
SET target=.
SET prefix=:
FOR /f "tokens=1*delims=_" %%i IN (
'dir /b/a-d/o-n "%target%\*_*.xml'
) DO (
IF !prefix!==%%i ECHO DEL "%target%\%%i_%%j"
SET prefix=%%i
)
This solution relies on the presence of "_" in the names of the *xml files in the target directory. I tested it on some dummy files in my batch-development directory, hence I used . for the current directory
The DIR command produces a list of the files in the target directory that match the pattern *_*.xml The/b switch means filename only, /a-d means no directory names, even if they match and /o-n means in the reverse order of name Therefore, the names will appear with the highest numeric value of the part after _ first.
The FOR reads this output and assigns the prefix to %%i and the remainder of the filename to %%j Since prefix is initialised to an invalid-in-filenames character, it cannot possibly match %%i for the first file encountered, so the DEL will not be executed. The last-encountered prefix will then be stored. Only if the next filename has a matching prefix will the DEL be executed
Note that I've inserted the ECHO keyword before the DEL. This is a safety device in case of error. The PROPOSED DEL operation will be reported to the screen, not EXECUTED. Once you're satisfied that the batch is correct, remove the ECHO and the delete operation will proceed.
Point to be observed:
Some editors are quite cavalier about stripping or retaining trailing
spaces. Normally, this is harmless but trailing spaces on a SET
command in batch can cause chaos.
#echo off
SETLOCAL ENABLEDELAYEDEXPANSION
set newprefix=_
for /F %%a in ('dir /on /b /a-d *.xml') do (
for /F "delims=_" %%i in ("%%a") do (
IF NOT *%%i*==*!newprefix!* (
for /F %%q in ('dir /on /b /a-d %%i*.xml') do set lastitem=%%q
attrib +r !lastitem! > NUL
del /s /q %%i*.xml 1>NUL 2>NUL
attrib -r !lastitem! > NUL
set newprefix=%%i
)
)
)
For each prefix of xml file, lists all the files with the prefix in question to get the last one in the list. Then mark the last one read only. Delete all with given pattern except read only. Reset read only flag and do over for next prefix.