Find all files and copy to another destination keeping the folder structure - windows

I have a folder structure with a bunch of *.jpg files scattered across the folders.
Now I want to find some files listed in a CSV file (one column only) or a text file line by line like
one.jpg
ten.jpg
five.jpg
and copy all those *.jpg files to another folder keeping the folder structure like:
outputfolder\somefolder\somefolder\one.jpg
outputfolder\somefolder\ten.jpg
outputfolder\somefolder\somefolder\somefolder\five.jpg
How can I achieve this?
Here is what I've tried
#echo off
CLS
REM CHECK FOR ADMIN RIGHTS
COPY /b/y NUL %WINDIR%\06CF2EB6-94E6-4a60-91D8-AB945AE8CF38 >NUL 2>&1
IF ERRORLEVEL 1 GOTO:NONADMIN
DEL %WINDIR%\06CF2EB6-94E6-4a60-91D8-AB945AE8CF38 >NUL 2>&1
:ADMIN
REM GOT ADMIN RIGHTS
COLOR
1F ECHO Please wait...
FOR /R "%~dp0" %%I IN (.) DO
for /f "usebackq delims=" %%a in ("%~dp0list.txt") do
echo d |xcopy "%%I\%%a" "C:\B2B_output_files" /e /i
COLOR 2F
PAUSE
GOTO:EOF
:NONADMIN
REM NO ADMIN RIGHTS
COLOR 4F
pause
GOTO:EOF

Stack Overflow is not a free code writing service, see help topic What topics can I ask about here?
However, I have nevertheless written the entire batch code for this task. Learn from this commented code and next time try to write the batch code by yourself and ask only if you stick on a problem you can't solve by yourself after several trials and not finding a solution on Stack Overflow or any other website.
The paths of source and target base folder must be defined at top of the batch script below.
The text file containing the name of the files to copy line by line must be named FileNames.txt and must be stored in source base folder with using batch code below.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
rem Define source and target base folders.
rem Note:
rem The paths should not contain an equal sign as then the
rem string substitutions below would not work as coded. The
rem target base folder can be even a subfolder of the source
rem base folder.
set "SourceBaseFolder=C:\Temp"
set "TargetBaseFolder=C:\Temp\OutputFolder"
rem Set source base folder as current directory. The previous
rem current directory is restored by command endlocal at end.
if not exist "%SourceBaseFolder%\*" (
echo %~nx0: There is no folder %SourceBaseFolder%
set "ErrorCount=1"
goto HaltOnError
)
cd /D "%SourceBaseFolder%"
if not exist "FileNames.txt" (
echo %~nx0: There is no file %SourceBaseFolder%\FileNames.txt
set "ErrorCount=1"
goto HaltOnError
)
rem For each file name in text file FileNames.txt in
rem source base folder the loops below do following:
rem 1. Search recursively for a file with current file name
rem in entire directory tree of source base folder.
rem 2. If a file could be found, check its path. Skip the
rem file if the path of found file contains the target
rem folder path to avoid copying files to itself. This
rem IF condition makes it possible that target base
rem folder is a subfolder of source base folder.
rem 3. Create the folders of found file relative to source
rem base path in target base folder. Then check if this
rem was successful by verifying if the target folder
rem really exists and copy the file on existing folder or
rem output an error message on failure creating the folder.
set "ErrorCount=0"
for /F "usebackq delims=" %%N in ("FileNames.txt") do (
for /R %%J in ("%%N*") do (
set "FilePath=%%~dpJ"
if "!FilePath:%TargetBaseFolder%=!" == "!FilePath!" (
set "TargetPath=%TargetBaseFolder%\!FilePath:%SourceBaseFolder%\=!"
md "!TargetPath!" 2>nul
if exist "!TargetPath!\*" (
echo Copying file %%~fJ
copy /Y "%%~fJ" "!TargetPath!" >nul
) else (
set /A ErrorCount+=1
echo Failed to create directory !TargetPath!
)
)
)
)
:HaltOnError
if %ErrorCount% NEQ 0 (
echo.
pause
)
endlocal
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 /? ... for an explanation of %~nx0
copy /?
echo /?
endlocal /?
for /?
goto /?
if /?
md /?
pause /?
rem /?
set /?
setlocal /?
And read also the Microsoft article about Using command redirection operators to understand 2>nul for suppressing error messages written to STDERR.

The following code should do what you asked for:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "LISTFILE=copylist.txt"
set "SOURCE=.\source"
set "DESTIN=.\destin"
for /F "usebackq eol=| delims=" %%L in ("%LISTFILE%") do (
for /F "eol=| delims=" %%F in ('
cd /D "%SOURCE%" ^
^& xcopy /L /S /I /Y ".\%%~L*" "%TEMP%" ^
^| findstr /I /R "^\..*\\%%~L$"
') do (
2> nul md "%DESTIN%\%%F\.."
copy /B /-Y "%SOURCE%\%%F" "%DESTIN%\%%F"
)
)
endlocal
exit /B
It relies on the fact that xcopy outputs relative paths to the console if a relative source path is given, and that it features a switch /L that tells it not to copy anything but list what would be copied without the switch. There is also the switch /S which defines to search for the source item recursively also within subdirectories.
There is a small problem though which requires to be worked around: xcopy /S only walks through subdirectories if source contains a wildcard * or ?, but not if a dedicated file name is given. That is why * is appended to the file name. Since this could also match some unintended items of course, findstr is used to filter them out.
So basically there is a for /F loop that iterates through the items listed in the text file copylist.txt. Within this loop another for /F is nested that enumerates the output of the aforementioned findstr-filtered xcopy /L /S output, which receives the items of the outer loop one after another. The embedded cd command ensures to be in the source directory. The destination of xcopy is just an existing directory to avoid error messages (remember nothing is actually copied due to /L).
The inner loop bopy contains an md command that creates the destination directory (tree), if not existing (2> nul avoids error messages if has already been created earlier), and a copy command which actually performs the copying activity; the switch /-Y defines to prompt the user in case an item already exists at the destination location, you can change it to /Y to overwrite without asking.

Related

search the requested file in subfolders and copy to destination

I am trying to copy the listed files in file_list.txt to destination folder from source location, source folder has subfolders. my batch should be capable to search the file in source subfolders and copy to destination folder. same for copy all files with extension .exe. what is wrong with my code. I think, I have missed to search subfolders data. don't know what is the command. please help.
#Echo Off
SETLOCAL ENABLEDELAYEDEXPANSION
#color 0a
cls
set "dest=D:\destination"
set /p source=Select source path:
for /R %%f in (%source%/*.exe) do copy %%f "%dest%"
echo Copy requested files
for /f %%f in (file_list.txt) do (
for /f "tokens=*" %%F IN ('dir /S /B /A:-D "%source%/%%f"') Do (
copy "%%F" "%dest%\%USERNAME%"
)
)
pause
ENDLOCAL
I cannot see anything obviously wrong with the code you've posted, although there are possibilities for errors. You have not provided sufficient information, about your current directory, the content of the text file, how the script is invoked or any debugging information.
The following version of your code, requires that you put your file listing text file into the variable definition on line five. I've assumed that the batch file is in the same location as the file listing, and therefore used %~dp0. If it is in the current directory instead, then replace that with %CD%\, and obviously the fully qualified absolute path if neither.
Next I use some validation to try to ensure that the source, destination, and user input exist. The input location will then become the current directory.
Your provided commands are then run, before the curent directory is returned to its original location.
#Echo Off
SetLocal EnableExtensions DisableDelayedExpansion
Color 0A
Set "listdir=%~dp0filelist.txt"
Set "dest=D:\destination"
If Not Exist "%listdir%" GoTo :EOF
If Not Exist "%dest%\" GoTo :EOF
:Ask
ClS
Set "source="
Set /P "source=Select source path: "
If Not Defined source GoTo Ask
PushD "%source:"=%" 2> NUL || GoTo Ask
For /R %%G In (*.exe) Do Copy /Y "%%G" "%dest%"
Echo Copy requested files
For /F "UseBackQ EOL=? Delims=" %%G In ("%listdir%"
) Do For /F "Delims=" %%H In ('Dir "%%~nxG" /S /B /A:-D 2^> NUL'
) Do Copy /Y "%%H" "%dest%\%UserName%"
PopD
Pause
Feel free to try the code, and provide some proper debugging information if it still fails to work as intended.

Move files batch doesn't work with double click but works from command line

I have some files
afjj.txt
agk.png
beta.tt
Ritj.rar
I use this script to move files in alphabetically order into autogenerated folders
a
|
|----> afjj.txt
|----> agk.png
b
|----> beta.tt
R
|----> Ritj.rar
To do I use this code
#echo off
setlocal
for %%i in (*) do (
set name=%%i
setlocal enabledelayedexpansion
if not "!name!" == "%0" (
set first=!name:~0,1!
md !first! 2>nul
if not "!first!" == "!name!" move "!name!" "!first!\!name!"
)
)
What is problem? If I double-click on this batch, batch doesn't work, not move.
But this batch works from command line.
Why?
NOTE: I use Windows Server 2016
P.S: from command line I use this command and works but not if I double click directly on .bat
move.bat "Z:\# 2020\Alfa\test"
The first mistake is naming the batch file move.bat. It is never a good idea to give a batch file the name of a Windows command because hat cause usually troubles. See also: SS64.com - A-Z index of Windows CMD commands.
The second mistake is using setlocal enabledelayedexpansion inside the loop without a corresponding endlocal also within same loop executed as often as setlocal. Please read this answer for details about the commands SETLOCAL and ENDLOCAL. The command SETLOCAL pushes several data on stack on every iteration of the loop and the data are never popped from stack in same loop on each loop iteration. The result is sooner or later a stack overflow depending on the number of files to process as more and more data are pushed on stack.
The third mistake is the expectation that the current directory is always the directory of the batch file. This expectation is quite often not fulfilled.
The fourth mistake is using a loop to iterate over a list of files which permanently changes on each execution of the commands in body of FOR loop. The loop in code in question works usually on storage media with NTFS as file system, but does not work on storage media using FAT32 or exFAT as file system.
The fifth mistake is the expectation that %0 expands always to name of the currently executed batch file with file extension, but without file path which is not true if the batch file is executed with full qualified file name (drive + path + name + extension), or with just file name without file extension, or using a relative path.
The sixth mistake is not enclosing the folder name on creation of the subfolder in double quotes which is problematic on file name starting unusually with an ampersand.
The seventh mistake is not taking into account to handle correct file names starting with a dot like .htaccess in which case the second character must be used as name for the subfolder, except the second character is also a dot. It is very uncommon, but also possible that file name starts with one or more spaces. In this case also the first none space character of file name should be used as Windows by default prevents the creation of a folder of which name is just a space character.
The solution is using following commented batch file with name MoveToFolders.cmd or MyMove.bat.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Get folder path of batch file assigned to an environment
rem variable. This folder path ends always with a backslash.
set "FilesFolder=%~dp0"
rem Optionally support calling of this batch file with another folder
rem path without checking if the passed string is really a folder path.
if not "%~1" == "" set "FilesFolder=%~1"
rem Replace all / by \ as very often people use / as directory separator
rem which is wrong because the directory separator is \ on Windows.
set "FilesFolder=%FilesFolder:/=\%"
rem The folder path should end always with a backslash even on folder
rem path is passed as an argument to the batch file on calling it.
if not "%FilesFolder:~-1%" == "\" set "FilesFolder=%FilesFolder%\"
rem Get a list of files in specified folder including hidden files loaded
rem into the memory of running command process which does not change on
rem the iterations of the loop below. Then iterate over the files list and
rem move the files to a subfolder with first none dot and none space character
rem of file name as folder name with the exception of the running batch file.
for /F "eol=| delims=" %%i in ('dir "%FilesFolder%" /A-D /B 2^>nul') do if /I not "%FilesFolder%%%i" == "%~f0" (
set "FileName=%%i"
set "FirstPart="
for /F "eol=| delims=. " %%j in ("%%i") do set "FirstPart=%%j"
if defined FirstPart (
setlocal EnableDelayedExpansion
set "TargetFolderName=%FilesFolder%!FirstPart:~0,1!"
md "!TargetFolderName!" 2>nul
if exist "!TargetFolderName!\" move "%FilesFolder%!FileName!" "!TargetFolderName!\"
endlocal
)
)
rem Restore the previous execution environment.
endlocal
The batch file can be started also with a folder path as argument to process the files in this folder without checking if the passed argument string is really referencing an existing folder.
Please read very carefully the answers on How to replace a string with a substring when there are parentheses in the string if there is interest on how to verify if a passed argument string really references an existing 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.
call /?
dir /?
echo /?
endlocal /?
for /?
if /?
md /?
move /?
rem /?
set /?
setlocal /?
Here is a slightly amended script. Notice I removed the !first! variable as it is not required. I also built in a safety measure, if it is unable to pushd to the given directory you passed to %~1 it will not continue the move. Else it might move files in a path you do not want it to move files. i.e the working dir you started the script in.
#echo off
setlocal enabledelayedexpansion
pushd "%~1" && echo( || goto :end
for %%i in (*.test) do (
set "name=%%~ni"
if not "%%~nxi" == "%~nx0" (md !name:~0,1!)2>nul
if not "!name:~0,1!" == "%%~i" move "%%~i" "!name:~0,1!\%%~i"
)
popd
goto :eof
:end
echo The directory you entered does not exist. Exited script..
pause
Note, with the above you can also drag a directory to the batch file, which will process that directory.
Or if you plan on double clicking, without parameters from cmd.
#echo off
setlocal enabledelayedexpansion
for %%i in (*.test) do (
set "name=%%~ni"
if not "%%~nxi" == "%~nx0" (md !name:~0,1!)2>nul
if not "!name:~0,1!" == "%%~i" move "%%~i" "!name:~0,1!\%%~i"
)
pause
A slightly different take.
This script will work on the current directory if double clicked, or run at the command line without a path specified.
But it will also allow you to provide a path to it as well.
#( SETLOCAL ENABLEDELAYEDEXPANSION
ECHO OFF
SET "_PATH=%~1"
IF NOT DEFINED _PATH SET "_PATH=%CD%"
)
CALL :Main
( Endlocal
Exit /B )
:Main
For /F "Tokens=*" %%_ in ('
DIR /B/A-D-S-H-L "%path%"
') DO (
IF /I "%%_" NEQ "%~nx0" (
SET "_TmpName=%%_"
IF NOT EXIST "%_Path%\!_TmpName:~0:1!\" MD "%_Path%\!_TmpName:~0:1!\"
MOVE /Y "%_Path%\!_TmpName!" "%_Path%\!_TmpName:~0:1!\!!_TmpName!"
)
)
GOTO :EOF
Based upon only filenames beginning with alphabetic characters, here's a somewhat simpler methodology:
#Echo Off
SetLocal EnableExtensions
PushD "%~1" 2> NUL
If /I "%~dp0" == "%CD%\" (Set "Exclusion=%~nx0") Else Set "Exclusion="
For %%G In (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) Do (
%SystemRoot%\System32\Robocopy.exe . %%G "%%G*" /Mov /XF "%Exclusion%" 1> NUL 2>&1
RD %%G 2> NUL
)
PopD

How to create subfolder on RAR/ZIP extraction if archive does not have one?

I have a lot of RAR or ZIP archives to decompress. Some of the archives contain a single folder with all files in this folder. Some other archives have all files at root level.
Case 01
Archive01.rar
MyFolder
file01.txt
file02.txt
file03.txt
etc.
Case 02
Archive02.rar
-file01.txt
-file02.txt
-file031.txt
etc.
I know how extract all archives into a subfolder.
But how to create the subfolder only when there is none present in archive?
What I mean is within a batch process for processing thousands of archives there should be no folder created additionally on extraction if the archive file belongs to case 01 . But if archive file belongs to case 02 the extraction should be done into a subfolder with name of archive file.
Case 01 result
MyFolder <- Folder
file01.txt
file02.txt
file03.txt
etc.
Case 02 result
Archive02 <-Folder base on the archive name
-file01.txt
-file02.txt
-file031.txt
etc.
The console version Rar.exe has the command l to list archive file contents according to text file Rar.txt in program files folder of WinRAR being the manual for console version. The list output on running Rar.exe with command l (or L) could be processed in a batch file to determine if the RAR archive file contains at top level just a single directory and nothing else. But Rar.exe supports like free UnRAR.exe just RAR archives.
To support also ZIP archives it is necessary to use GUI version WinRAR.exe which supports extraction of RAR and ZIP archives and some other archive types.
The manual for WinRAR.exe is the help of WinRAR which can be opened on clicking in menu Help on menu item Help topics on running WinRAR. On help tab Contents there is the list item Command line mode with all necessary information in referenced help pages for running WinRAR.exe from command line.
It can be seen on looking on list of Commands that WinRAR.exe does not support a command l to output archive file contents to a console window because of being a graphic user interface application.
So it is not really possible to determine from command line or within a batch file if an archive file contains at top level just a single directory on using WinRAR.exe.
However, that does not really matter as it would be inefficient to first parse an archive file for file and directory names and then use the appropriate command to extract the archive file without or with specifying an extra extraction folder on command line.
It is much more efficient to first extract all *.rar (and later also all *.zip) files using just one WinRAR call with switch -ad to extract each archive file into a subdirectory with name of the archive file and second eliminate each extraction directory not being necessary because the corresponding archive file contained just a single directory at top level.
This smart approach is used in the batch file below which contains following additional features to make it useful for hopefully many WinRAR users:
The working directory can be specified as first argument on calling the batch file which can be even a UNC path.
The batch file finds out automatically where WinRAR.exe is installed working also for those use cases with 32-bit or 64-bit WinRAR not being installed in default program files directory (as on all of my computers).
Note: The commented batch file as posted below does not check if in current or specified directory an existing archive file was extracted already before. So it is not advisable to run the batch file multiple times on a directory with archive files once processed not being removed from that directory.
#echo off
rem Change working directory if batch file was started with an argument.
if not "%~1" == "" (
pushd "%~1" 2>nul
if errorlevel 1 (
echo Specified directory "%~1" does not exist.
echo/
pause
goto :EOF
)
)
setlocal EnableExtensions DisableDelayedExpansion
rem Does WinRAR exist in default program files folder?
set "WinRAR=%ProgramFiles%\WinRAR\WinRAR.exe"
if exist "%WinRAR%" goto StartExtraction
rem Does WinRAR exist in default program files folder for x86 applications?
set "WinRAR=%ProgramFiles(x86%\WinRAR\WinRAR.exe"
if exist "%WinRAR%" goto StartExtraction
rem Try to determine installation location of WinRAR.exe from registry.
set "TypeToken=2"
goto GetPathFromRegistry
rem On Windows Vista and later REG.EXE outputs without version info:
rem HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe
rem (Default) REG_SZ Full path to WinRAR\WinRAR.exe
rem There are only spaces used to separate value name, value type and value string.
rem But REG.EXE version 3.0 outputs on Windows XP with version info:
rem ! REG.EXE VERSION 3.0
rem
rem HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe
rem <NO NAME> REG_SZ Full path to WinRAR\WinRAR.exe
rem NOTE: There are 4 indent spaces and 2 separating tabs in REG 3.0 output line.
rem So either token 2 or token 3 contains value type REG_SZ
rem used to identify the line with the wanted information.
:GetPathFromRegistry
for /F "skip=1 tokens=%TypeToken%*" %%A in ('%SystemRoot%\System32\reg.exe QUERY "HKLM\Software\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe" /ve 2^>nul') do (
if "%%A" == "REG_SZ" (
if exist "%%~fB" (
set "WinRAR=%%~fB"
goto StartExtraction
)
) else if "%%A" == "NAME>" (
set "TypeToken=3"
goto GetPathFromRegistry
)
)
endlocal
if not "%~1" == "" popd
echo Could not determine directory containing WinRAR.exe.
echo/
echo Please configure it manually in file: %~f0
echo/
pause
goto :EOF
rem WinRAR supports multiple archive types on extraction.
rem Specify here the archive file extensions for extraction.
:StartExtraction
for %%I in (rar zip) do call :ExtractArchives %%I
rem Restore previous command environment, restore previous current directory
rem and exit this batch file without fall through to the subroutines below.
endlocal
if not "%~1" == "" popd
goto :EOF
rem The subroutine ExtractArchives processes all archive files in current
rem directory with the file extension passed to subroutine as first argument.
rem WinRAR is called once to extract all files with specified file extension
rem for extraction into a subdirectory with name of the archive file.
rem Then one more subroutine is called for each archive file to determine
rem if it is safe to move the extracted archive file contents up one level.
:ExtractArchives
if not exist "*.%~1" goto :EOF
"%WinRAR%" x -ad -cfg- -ibck -y -- "*.%~1"
for %%A in ("*.%~1") do call :MoveUpExtracted "%%~nA"
goto :EOF
rem The subroutine MoveUpExtracted first checks if for the archive file
rem passed to the subroutine as first argument a subdirectory exists at
rem all, i.e. the extraction before was successful for that archive.
rem Next it counts the subdirectories in the archive extraction directory.
rem Nothing is moved up if there is more than 1 subdirectory in archive
rem extraction directory.
rem Also nothing is moved up if archive extraction directory contains
rem 1 or more files.
rem After verification of archive extraction directory really containing
rem only a single subdirectory and nothing else, the name of the archive
rem extraction directory is compared case-insensitive with the name of
rem the single subdirectory in archive extraction directory. On equal
rem directory names the archive extraction directory is renamed by
rem appending _tmp to make it possible to move the subdirectory with same
rem name up one level in directory hierarchy. There is hopefully by chance
rem never a directory present in current directory with name of an archive
rem file and _tmp appended.
rem Next it is checked if in current directory there is not already existing
rem a directory with name of the subdirectory from extracted archive in which
rem case it is also not possible to move the directory up one level. In this
rem special use case the archive extraction directory is kept containing just
rem a single subdirectory with restoring original directory name.
rem Then the single subdirectory in archive extraction directory is moved up
rem one level which is very fast as just the file allocation table is updated
rem and no data is really moved.
rem The directory movement could fail if the extracted directory has hidden
rem attribute set. In this case temporarily remove the hidden attribute,
rem move the directory up one level in directory hierarchy and set the
rem hidden attribute again on the directory.
rem On a succesful moving up of the extracted directory the (renamed)
rem extraction directory being now empty is deleted as not further needed.
:MoveUpExtracted
if not exist "%~1\" (
echo Error: No folder for archive %~1
goto :EOF
)
echo Processing archive folder "%~1"
set FolderCount=0
set "FolderName="
for /F "delims=" %%D in ('dir "%~1\*" /AD /B 2^>nul') do (
if defined FolderName goto :EOF
set /A FolderCount+=1
set "FolderName=%%D"
)
if not %FolderCount% == 1 goto :EOF
for /F "delims=" %%F in ('dir "%~1\*" /A-D /B 2^>nul') do goto :EOF
set "ParentRenamed=0"
set "ParentFolder=%~1"
if /I "%~1" == "%FolderName%" (
ren "%~1" "%~1_tmp" 2>nul
if errorlevel 1 (
echo Failed to rename "%~1" to "%~1_tmp".
goto :EOF
)
set "ParentFolder=%~1_tmp"
set "ParentRenamed=1"
)
if exist "%FolderName%" (
if %ParentRenamed% == 1 ren "%~1_tmp" "%~1"
echo Error: Folder exists "%FolderName%"
goto :EOF
)
move "%ParentFolder%\%FolderName%" "%FolderName%" >nul 2>nul
if not errorlevel 1 (
rd "%ParentFolder%"
goto :EOF
)
%SystemRoot%\System32\attrib.exe -h "%ParentFolder%\%FolderName%" >nul
move "%ParentFolder%\%FolderName%" "%FolderName%" >nul
if errorlevel 1 (
if %ParentRenamed% == 1 (
ren "%ParentFolder%" "%~1"
goto :EOF
)
)
%SystemRoot%\System32\attrib.exe +h "%FolderName%"
rd "%ParentFolder%"
goto :EOF
I'm using 32-bit Windows since Windows 95, but I ran myself never into the MAX_PATH limitation, i.e. absolute file/folder names being longer than 259 characters.
So it was a really interesting and also a very time consuming challenge to rewrite the batch file to work also when archive file names are very long, for example exactly 256 characters for file name + file extension.
During the development of the the batch file below I found out following:
Some commands like DIR, FOR, RD and REN support short 8.3 names in path AND file/folder name while other commands like ATTRIB and MOVE support them only in path, but not in file/folder name (at least on Windows XP).So it is not possible to move a folder or change its attributes using its short 8.3 name.
All commands fail on using just relative folder names with relative folder path when folder name with full path is longer than 259 characters. This means Windows command interpreter first determines folder name with complete path before executing any command. So the current directory should have a short path on processing archives with very long names or containing a directory with a very long name.
I could not figure out how to get short name of a folder or its path using %~fs1 as explained by call /? or %%~fsI (in batch file) as explained by for /? when only a relative folder path is parsed by Windows command interpreter, i.e. just the long name of a folder without its path.
On running command DIR with option /X to get short name of a directory, the third column contains the short name and the fourth column the long name. But short name in third column can be missing on very short folder names.
Output of dir /AD /X on English Windows 7 SP1 x64 executed on an NTFS partition with Germany set in Windows Region and Language settings:
Volume in drive C is System
Volume Serial Number is 7582-4210
Directory of C:\Temp\Test
29.04.2017 22:39 <DIR> .
29.04.2017 22:39 <DIR> ..
29.04.2017 22:39 <DIR> ARCHIV~1 archive_with_a_very_very_very_..._long_name_1
29.04.2017 22:39 <DIR> Batch
29.04.2017 22:39 <DIR> xyz
Same command dir /AD /X executed on German Windows XP SP3 x86 on a FAT32 partition also with Germany set in Windows Region and Language settings:
Datenträger in Laufwerk F: ist TEMP
Volumeseriennummer: CAA5-41AA
Verzeichnis von F:\Temp
29.04.2017 22:39 <DIR> .
29.04.2017 22:39 <DIR> ..
29.04.2017 22:39 <DIR> BATCH Batch
29.04.2017 22:39 <DIR> xxx
29.04.2017 22:39 <DIR> ARCHIV~1 archive_with_a_very_very_very_..._long_name_1
Note: The very long directory name was truncated here by me with ... in name.
Why directory Batch has on Windows XP computer short name BATCH but no short name on Windows 7 is not really explainable for me.
Here is the batch script supporting also long archive names and long directory names in archive as long as the path of current directory is short.
#echo off
rem Change working directory if batch file was started with an argument.
if not "%~1" == "" (
pushd "%~1" 2>nul
if errorlevel 1 (
echo Specified directory "%~1" does not exist.
echo/
pause
goto :EOF
)
)
setlocal EnableExtensions DisableDelayedExpansion
rem Does WinRAR exist in default program files folder?
set "WinRAR=%ProgramFiles%\WinRAR\WinRAR.exe"
if exist "%WinRAR%" goto StartExtraction
rem Does WinRAR exist in default program files folder for x86 applications?
set "WinRAR=%ProgramFiles(x86%\WinRAR\WinRAR.exe"
if exist "%WinRAR%" goto StartExtraction
rem Try to determine installation location of WinRAR.exe from registry.
set "TypeToken=2"
goto GetPathFromRegistry
rem On Windows Vista and later REG.EXE outputs without version info:
rem HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe
rem (Default) REG_SZ Full path to WinRAR\WinRAR.exe
rem There are only spaces used to separate value name, value type and value string.
rem But REG.EXE version 3.0 outputs on Windows XP with version info:
rem ! REG.EXE VERSION 3.0
rem
rem HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe
rem <NO NAME> REG_SZ Full path to WinRAR\WinRAR.exe
rem NOTE: There are 4 indent spaces and 2 separating tabs in REG 3.0 output line.
rem So either token 2 or token 3 contains value type REG_SZ
rem used to identify the line with the wanted information.
:GetPathFromRegistry
for /F "skip=1 tokens=%TypeToken%*" %%A in ('%SystemRoot%\System32\reg.exe QUERY "HKLM\Software\Microsoft\Windows\CurrentVersion\App Paths\WinRAR.exe" /ve 2^>nul') do (
if "%%A" == "REG_SZ" (
if exist "%%~fB" (
set "WinRAR=%%~fB"
goto StartExtraction
)
) else if "%%A" == "NAME>" (
set "TypeToken=3"
goto GetPathFromRegistry
)
)
endlocal
if not "%~1" == "" popd
echo Could not determine directory containing WinRAR.exe.
echo/
echo Please configure it manually in file: %~f0
echo/
pause
goto :EOF
rem WinRAR supports multiple archive types on extraction.
rem Specify here the archive file extensions for extraction.
rem But first delete temporary folder from a previous breaked execution.
:StartExtraction
rd /Q /S # 2>nul
for %%I in (rar zip) do call :ExtractArchives %%I
rem Restore previous command environment, restore previous current directory
rem and exit this batch file without fall through to the subroutines below.
endlocal
if not "%~1" == "" popd
goto :EOF
rem The subroutine ExtractArchives processes all archive files in current
rem directory with the file extension passed to subroutine as first argument.
rem WinRAR is called once to extract all files with specified file extension
rem for extraction into a subdirectory with name of the archive file.
rem Then one more subroutine is called for each archive file to determine
rem if it is safe to move the extracted archive file contents up one level.
:ExtractArchives
if not exist "*.%~1" goto :EOF
"%WinRAR%" x -ad -cfg- -ibck -y -- "*.%~1"
for %%A in ("*.%~1") do call :MoveUpExtracted "%%~nA" %1
goto :EOF
rem The subroutine MoveUpExtracted first checks if for the archive file
rem passed to the subroutine as first argument a subdirectory exists at
rem all, i.e. the extraction before was successful for that archive, and
rem determines short 8.3 name of this directory.
rem Next it counts the subdirectories in the archive extraction directory
rem using short directory name. Nothing is moved up if there is more than
rem 1 subdirectory in archive extraction directory.
rem Also nothing is moved up if archive extraction directory contains
rem 1 or more files.
rem After verification of archive extraction directory really containing
rem only a single subdirectory and nothing else, the current archive folder
rem is renamed to # (single character folder name) using short folder name.
rem This folder rename should work in general. The current archive folder
rem is kept in case of this folder rename fails unexpected because it is
rem not yet known if the current directory does not already contain the
rem single directory extracted from current archive or rename failed
rem because of a permission or a directory sharing access restriction.
rem Next it is checked if in current directory there is not already existing
rem a directory with name of the subdirectory from extracted archive in which
rem case it is also not possible to move the directory up one level. In this
rem special use case the archive extraction directory is kept containing just
rem a single subdirectory with restoring original directory name. In case of
rem restoring archive directory fails unexpected, the directory with name #
rem is deleted and the archive is extracted once again into a directory with
rem name of archive file.
rem It is clear on this point that the single directory in archive extraction
rem directory can be moved up to current directory from directory wit having
rem now the temporary name #.
rem Moving a directory with command MOVE is not possible if hidden attribute
rem is set on directory. For that reason it is checked next if the directory
rem to move up has hidden attribute set using its short directory name.
rem In case of directory has hidden attribute is indeed set, it is removed
rem which is also verified. The verification can't be done with errorlevel
rem evaluation as external command ATTRIB does not set errorlevel on failed
rem attribute change. So the attribute check is done once again after the
rem hidden attribute is removed with ATTRIB.
rem ATTRIB also fails to change the attribute if absolute folder path is
rem longer than 259 characters. In this case the current extraction folder
rem with temporary name # is deleted completely and the current archive is
rem extracted once again to current directory without creation of an
rem additional directory with name of archive file.
rem Then the single subdirectory in archive extraction directory having
rem now name # is also renamed to # using short directory name to avoid
rem a problem on next command MOVE with an absolute folder path longer
rem than 259 characters as much as possible.
rem The directory extracted from archive with name # in directory # is
rem moved up to current directory with suppressing all errors which could
rem occur for example if path of current directory plus name of directory
rem as extracted from archive file is too long.
rem The directory # in current directory with its subdirectory # is deleted
rem on a moving error and the current archive file is extracted once again
rem into current directory without creation of an additional directory with
rem name of archive file.
rem But on successful movement of the folder with correct name to current
rem directory the hidden attribute is set on folder if the extracted folder
rem has it also set before moving the folder and the finally empty folder #
rem is deleted before exiting subroutine.
:MoveUpExtracted
set "FolderToCheck=%~f1"
set "FolderToCheck=%FolderToCheck:~0,258%"
for /F "skip=5 tokens=4*" %%X in ('dir "%FolderToCheck%*" /AD /X 2^>nul') do (
if "%%Y" == "%~1" set "ArchiveFolder=%%X" & goto Subfolders
if "%%Y" == "" if /I "%%X" == "%~1" set "ArchiveFolder=%%X" & goto Subfolders
)
echo Error: No folder for archive %~1
goto :EOF
:Subfolders
#echo off
echo Processing archive folder "%~1"
set FolderCount=0
set "FolderName="
for /F "delims=" %%D in ('dir "%ArchiveFolder%\*" /AD /B 2^>nul') do (
if defined FolderName goto :EOF
set /A FolderCount+=1
set "FolderName=%%D"
)
if not %FolderCount% == 1 goto :EOF
for /F "delims=" %%F in ('dir "%ArchiveFolder%\*" /A-D /B 2^>nul') do goto :EOF
ren "%ArchiveFolder%" # 2>nul
if errorlevel 1 (
echo Error: Failed to rename "%~1"
goto :EOF
)
set "FolderToCheck=%~dp1%FolderName%"
set "FolderToCheck=%FolderToCheck:~0,258%"
for /F "skip=5 tokens=4*" %%X in ('dir "%FolderToCheck%*" /AD /X 2^>nul') do (
if "%%Y" == "%FolderName%" goto FolderExist
if "%%Y" == "" if /I "%%X" == "%FolderName%" goto FolderExist
)
set "HiddenFolder=0"
set "FolderToCheck=%~dp1#\%FolderName%"
set "FolderToCheck=%FolderToCheck:~0,258%"
for /F "skip=5 tokens=4*" %%X in ('dir "%FolderToCheck%*" /AD /X 2^>nul') do (
if "%%Y" == "%FolderName%" set "FolderToMove=%%X" & goto CheckHidden
if "%%Y" == "" if /I "%%X" == "%FolderName%" set "FolderToMove=%%X" & goto CheckHidden
)
:CheckHidden
for %%X in ("#\%FolderToMove%") do (
for /F "tokens=2 delims=h" %%H in ("%%~aX") do (
if %HiddenFolder% == 1 goto ArchiveExtract
set "HiddenFolder=1"
%SystemRoot%\System32\attrib.exe -h "#\%FolderName%"
goto CheckHidden
)
)
ren "#\%FolderToMove%" # 2>nul
move #\# "%FolderName%" >nul 2>nul
if errorlevel 1 goto ArchiveExtract
if %HiddenFolder% == 1 %SystemRoot%\System32\attrib.exe +h "%FolderName%"
rd #
goto :EOF
:ArchiveExtract
rd /Q /S #
"%WinRAR%" x -cfg- -ibck -y -- "%~1.%~2"
goto :EOF
:FolderExist
echo Error: Folder exists "%FolderName%"
ren # "%~1" 2>nul
if not errorlevel 1 goto :EOF
rd /Q /S #
"%WinRAR%" x -ad -cfg- -ibck -y -- "%~1.%~2"
goto :EOF
It would be definitely better to write a console application in C or C++ or C# being long path aware replacing subroutine MoveUpExtracted in above batch scripts.
On Windows 10 version 1607 (Anniversary Update) or later Windows versions the MAX_PATH limit of 260 characters (259 characters plus terminating null byte) can be disabled via a group policy or by adding a registry value, see
How to enable NTFS Long Paths in Windows 10 by Sergey Tkachenko
How to Make Windows 10 Accept File Paths Over 260 Characters by Walter Glenn
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.
attrib /?
call /?
dir /?
echo /?
endlocal /?
for /?
goto /?
if /?
move /?
pause /?
popd /?
pushd /?
rd /?
reg /?
reg query /?
rem /?
ren /?
set /?
setlocal /?
Read also the Microsoft articles:
Using Command Redirection Operators
Testing for a Specific Error Level in Batch Files
You'll have to check if data is inside folder or in root.
If it's in root click on extract to /foldername (Case 02)
otherwise just extract here (Case 01)

Batch script to automatically delete empty folders - need help adding exceptions

My batch experience is rather limited but I did manage to write the following script to delete all empty subfolders of my target folder.
set "Target=C:\Target"
for /f "delims=" %%i in ('dir "%Target%" /A:D /B /S ^| sort /r') do rd "%%i" 2>NUL >NUL
My problems are:
1. I would like to be able to keep the first layer of subfolders intact and only delete empty folders in the following layers.
2. If it is possible I would also like to skip certain folders completely based on their name.
Is it possible to do that or would i need to write the script for all of the subfolders i want to clean up?
The code below was tested on following folder structure in folder C:\Target
Folder1
Folder To Delete
Folder To Keep A
FolderToKeepB
Folder 2
Folder To Keep A
Not Empty Folder
File in Folder.txt
The resulting folder structure after running the batch file below was
Folder1
Folder To Keep A
FolderToKeepB
Folder 2
Folder To Keep A
Not Empty Folder
File in Folder.txt
Just the single folder Folder To Delete was deleted as this one should not be always kept and it was indeed empty.
#echo off
rem For each subfolder in C:\Target do ...
for /D %%D in ("C:\Target\*") do (
rem For each subfolder in found subfolder of C:\Target do ...
for /D %%S in ("%%~D\*") do call :DeleteFolder "%%~S"
)
rem This goto :EOF results in exiting the batch file.
goto :EOF
rem Subroutine for easier comparing the name of the current
rem subfolder with the name of the subfolders to always keep.
rem The goto :EOF commands below just exit the subroutine.
:DeleteFolder
if "%~nx1"=="Folder To Keep A" goto :EOF
if "%~nx1"=="FolderToKeepB" goto :EOF
rem Delete this subfolder which fails if it is not empty.
rem The error message is suppressed by redirecting output stream
rem stderr (standard error) with handle 2 to the NUL device.
rd "%~1" 2>nul
goto :EOF
For more details on the used commands read help output printed into a console window on running
For command call: call /? or help call
For command for: for /? or help for
For command goto: goto /? or help goto
For command rmdir (rd): rd /? or help rd or rmdir /? or help rmdir
Using command redirection operators

How to copy a file to many directories using prompt?

I need to copy a file and paste it in many directories, can I do this with a single command in windows prompt?
I tried this code, but it didn't work:
> copy C:\main\folder-1\docs\file.txt C:\main\*\docs
The names are illustratives, but the idea is: inside the "main" folder I have 50 folders ("folder-1", "folder-2", ..., "folder-50")... and inside each "folder-N", I have other folder named "docs". Every time that I create a file into any "folder-N\docs" I need to paste it into all "folder-N\docs".
Is it possible? or I really need to paste the file, folder by folder?
Straight up from the command line:
for /D %x in (c:\main\*.*) DO COPY c:\main\folder-1\docs\file.txt %x\docs\file.txt
From a BAT file or CMD file (not from the command line), you need to escape the % variable again
for /D %%x in (c:\main\*.*) DO COPY c:\main\folder-1\docs\file.txt %%x\docs\file.txt
Of course, if the subdirectory "docs" directory doesn't exist in each subfolder of "main", the iteration will print an error. That's why in my example above I explicitly specify copying to %x\docs\file.txt. If I had just said `%x\docs" as the target of the copy, it might create a file called "docs" that contains the contents of the file.txt source.
More information on for loops here and here:
Or just type "help for" at the command prompt.
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir\one"
:: Build list of directorynames
SET "dnames="
FOR /f "delims=" %%a IN ('dir /b /ad "%sourcedir%" ') DO (
IF EXIST "%sourcedir%\%%a\docs\" SET "dnames=!dnames! "%%a""
)
FOR %%a IN (%dnames%) DO FOR %%b IN (%dnames%) DO IF NOT %%a==%%b (
FOR %%s IN ("%sourcedir%\%%~a\docs\*") DO (
IF NOT EXIST "%sourcedir%\%%~b\docs\%%~nxs" ECHO(COPY "%sourcedir%\%%~a\docs\%%~nxs" "%sourcedir%\%%~b\docs\%%~nxs"
)
)
GOTO :EOF
You would need to change the setting of sourcedir to suit your circumstances.
The required COPY commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(COPY to COPY to actually copy the files.
I'd suggest that you create a testing subtree of a few small subdirectories before releasing in anger. Remember that this "report" may say to copy the same file from dir1 and dir5 into dir3 but when released because the file would actually be copied from dir1 to dir3, the copy from dir5 would not occur (when the copy from dir5 is checked, the copy from dir1 would already have occurred.)
You could suppress the copy report by appending >nul to the copy line.
Note that this routine is oriented towards one-file-at-a-time and showing what should be done. This following routine should do the same thing, is shorter but doesn't provide an elegant testing structure:
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir\one"
:: Build list of directorynames
SET "dnames="
FOR /f "delims=" %%a IN ('dir /b /ad "%sourcedir%" ') DO (
IF EXIST "%sourcedir%\%%a\docs\" SET "dnames=!dnames! "%%a""
)
FOR %%a IN (%dnames%) DO FOR %%b IN (%dnames%) DO IF NOT %%a==%%b (
ECHO(xcopy /d "%sourcedir%\%%~a\docs\*" "%sourcedir%\%%~b\docs\"
)
GOTO :EOF
Again, the xcopy is reported, not executed for testing purposes.

Resources