Batch symbolic link creation from .txt list - batch-file

I need a way to create symbolic links to multiple files in one folder, all listed in a .txt file. Filenames in the list lack the file extension. I used to do copy with the following script, and I failed to replace the copy command to symlink creation.
#echo off
chcp 65001 > nul
for /f "usebackq delims=" %%i IN ("selection_list.txt") DO (
xcopy "..\%%i.zip" "..\selection\%%i.zip*"
)
pause
Using relative paths because i wanna be able to use this in multiple folders.
Filenames in the .txt file don't include file extensions.
For instantce, let's say I wanna use this in a folder "F:\assets", i'll put my script in a folder "F:\assets\selection_script" along with the .txt file named selection_list.txt. After launching the script, it'll create a folder "F:\assets\selection" with all the files I wanted in it.
I tried replacing xcopy command with mklink /D, using this syntax example
mklink /D "C:\Link To Folder" "C:\Users\Name\Original Folder"
New script looks like this
#echo off
chcp 65001 > nul
for /f "usebackq delims=" %%i IN ("selection_list.txt") DO (
mklink /D "..\selection_links\%%i.zip*" "..\%%i.zip"
)
pause
Obviously this didn't work. Says System can't find the file selection_list.txt
I tried to manually run the command for a single named file with relative paths and it worked, so my problem is getting it to work in a function with a list. Seems to me that file extensions being added on top of filename from .txt list might be the problem, but idk how to resolve it. I tried few syntax variations I found, without success
I'm quite unexperienced with this so any help would be greatly appreciated!

Let me first explain better the task to do. There are following folders and files:
F:\assets
selection
Development & Test(!).zip
;Example Zip File.zip
selection_script
create_selection.cmd
selection_list.txt
The text file selection_list.txt contains the lines:
Development & Test(!)
;Example Zip File
Not existing file
The execution of create_selection.cmd should result in the following folders and files:
F:\assets
selection
Development & Test(!).zip
;Example Zip File.zip
selection_links
Development & Test(!).zip
;Example Zip File.zip
selection_script
create_selection.cmd
selection_list.txt
The directory entries Development & Test(!).zip and ;Example Zip File.zip in created directory selection_links are symbolic links and not copies of the two files in directory selection.
This symbolic links creation task can be done with F:\assets\selection_script\create_selection.cmd with the following command lines:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "tokens=*" %%G in ('%SystemRoot%\System32\chcp.com') do for %%H in (%%G) do set /A "CodePage=%%H" 2>nul
%SystemRoot%\System32\chcp.com 65001 >nul 2>&1
for %%I in ("%~dp0..\selection_links") do set "LinksFolder=%%~fI"
if not exist "%LinksFolder%\" md "%LinksFolder%" 2>nul
if not exist "%LinksFolder%\" echo ERROR: Failed to create directory: "%LinksFolder%"& goto EndBatch
pushd "%LinksFolder%"
if exist "%~dp0selection_list.txt" for /F "usebackq eol=| delims=" %%I in ("%~dp0selection_list.txt") do if exist "..\selection\%%I.zip" if not exist "%%I.zip" mklink "%%I.zip" "..\selection\%%I.zip" >nul
popd
:EndBatch
%SystemRoot%\System32\chcp.com %CodePage% >nul
endlocal
There is defined first completely the required execution environment with the first two command lines setting up a local execution environment with command echo mode turned off, command extensions enabled and delayed variable expansion disabled as required for this task.
There is next determined the currently active code page and stored in environment variable CodePage using a command line published by Compo on DosTips forum topic [Info] Saving current codepage. Then the active code page is changed to UTF-8 although not really needed for the example.
There is next determined once the full path of the folder in which the symbolic links should be created which is the folder selection_links being a subfolder of the parent folder F:\assets of the folder selection_script containing the batch script. It does not matter if this folder already exists or not on determining the fully qualified folder name.
There is next verified if the target folder exists. The folder selection_links is created on not existing with checking once again if the folder really exists now. A useful error message is output on creation of folder failed and the batch file restores the initial code page and the initial execution environment.
The target folder is made the current directory by using the command PUSHD which should not fail anymore now after verification that the target folder exists.
There are next processed the lines in the text file selection_list.txt referenced with its fully qualified file name by using %~dp0 which expands to drive and path of argument 0 which is the full path of the batch file always ending with a backlash.
Each non-empty line not starting with the character | is assigned completely one after the other to the loop variable I. The character | is not valid for a file name as explained in the Microsoft documentation about Naming Files, Paths, and Namespaces. There is verified next if there is really a ZIP archive file with that name in the folder selection and if there is no directory entry with same name in current folder selection_links.
If these two conditions are both true, MKLINK is executed to create in current directory selection_links a file symbolic link to the ZIP file in the directory selection.
Please note that a ZIP archive file is not a directory and for that reason the usage of MKLINK option /D to create a directory symbolic link cannot work ever.
Finally the initial current directory is restored using POPD and the initial code page and the initial execution environment are also restored by the batch file before it ends.
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 %~dp0 ... drive and path of argument 0 – the batch file path
chcp /?
echo /?
endlocal /?
for /?
goto /?
if /?
md /?
mklink /?
popd /?
pushd /?
set /?
setlocal /?
See also:
Microsoft documentation about Using command redirection operators
Single line with multiple commands using Windows batch file

Related

How can I find my error in the batch script?

The code below should archive some files by moving them into a subfolder. The batch file asks the user for the folder path. Then a subfolder should be created and if that was successful, it should move all files in the user input directory into the subdirectory. It works, but it closes although using pause. It does not output anything about a syntax error or anything at all. Please let me know if somebody notices something.
#echo off
SETLOCAL EnableDelayedExpansion
echo Insert path:
set /p path=
echo the path is %path%
cd %path%
echo The files will be moved to a new folder
pause
mkdir %path%\archived_files
IF EXIST "archived_files" (
for /f %%A in ('DIR /A /D /B') do (
echo %%A && move /Y %path%\%%A %path%\archived_files)
echo Folder "archived_files" created or already exists
) else ( echo Folder "archived_files" does not exist )
echo the files have been transferred
pause
ENDLOCAL
I suggest to use this batch file for the file moving task.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "BatchFileName=%~nx0"
set "BatchFilePath=%~dp0"
set "UserPath=%~1"
if defined UserPath goto ChangeFolder
:UserPrompt
set "UserPath="
set /P "UserPath=Enter path: "
rem Has the user not entered a string?
if not defined UserPath goto UserPrompt
rem Remove all double quotes from input string.
set "UserPath=%UserPath:"=%"
rem Has the user entered just one or more double quotes?
if not defined UserPath goto UserPrompt
:ChangeFolder
pushd "%UserPath%" 2>nul || (echo Folder "%UserPath%" does not exist.& goto UserPrompt)
for /F "eol=| delims=" %%I in ('dir /A-D /B 2^>nul') do goto CreateSubfolder
echo The folder does not contain any file to archive.& goto EndBatch
:CreateSubfolder
md "archived_files" 2>nul
if not exist "archived_files\" echo Failed to create subfolder: "archived_files"& goto EndBatch
rem It must be avoided that the currently running batch file is moved too.
set "ExcludeFileOption="
for %%I in ("%UserPath%\") do set "CurrentFolderPath=%%~dpI"
if "%CurrentFolderPath%" == "%BatchFilePath%" set "ExcludeFileOption= /XF "%BatchFileName%""
rem The command MOVE used with wildcard * does not move hidden files. A FOR loop
rem with MOVE is slow in comparison to usage of ROBOCOPY to move really all files.
rem The ROBOCOPY option /IS can be removed to avoid moving same files existing
rem already in the subfolder archived_files from a previous batch execution.
echo The files are moved to a new folder.
%SystemRoot%\System32\robocopy.exe . archived_files%ExcludeFileOption% /MOV /R:2 /W:5 /IS /NDL /NFL /NJH /NJS
if not errorlevel 2 if errorlevel 1 echo All files are moved successfully.
:EndBatch
popd
endlocal
pause
The batch file can be started with a a folder path as argument. So it is possible to right click on the batch file and click in opened context menu in submenu Send to on item Desktop (create shortcut). The .lnk file created on the user´s desktop can be renamed now also via context menu or key F2 to whatever name is useful like Archive files. Then the shortcut file can be cut with Ctrl+X and pasted with Ctrl+V in the folder %APPDATA%\Microsoft\Windows\SendTo to have in Send to context submenu the menu item Archive files. This makes it possible to right click on a folder and click in opened context menu in submenu Send to on Archive files to run the batch file without the need to enter a folder path manually.
The batch file prompts the user for the path if not started with a folder path as first argument or the folder cannot be found at all. This user prompt is done using a safe method. The batch file makes the passed or entered folder temporarily the current folder for the remaining commands using PUSHD and POPD instead of CD to work also with UNC paths.
There is checked next if the folder contains any file at all. Otherwise the user is informed that the directory does not contain files to archive and batch file ends without any further action.
The file movement is done with ROBOCOPY for the reasons described in a remark in the batch file which requires Windows Vista or a newer Windows version or Windows Server 2003 or a newer Windows server version.
I recommend to see also:
Debugging a batch file which answers your question.
What is the reason for "X is not recognized as an internal or external command, operable program or batch file"? It explains why path as name for the environment variable to assign the user entered path is a really bad idea.
How to stop Windows command interpreter from quitting batch file execution on an incorrect user input? It explains the reasons for using the additional code to evaluate the string entered by the user.
Why is no string output with 'echo %var%' after using 'set var = text' on command line? It explains the recommended syntax for the (re)definition of an environment variable and why using this syntax.
Syntax error in one of two almost-identical batch scripts: ")" cannot be processed syntactically here describes several common issues made by beginners in batch file coding like not enclosing a file/folder path in double quotes.
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 %~nx0, %~dp0 and %~1 whereby argument 0 is always the batch file itself.
dir /?
echo /?
endlocal /?
for /?
goto /?
if /?
md /?
pause /?
popd /?
pushd /?
rem /?
robocopy /?
set /?
setlocal /?
Other useful documentations used to write this code:
single line with multiple commands using Windows batch file
the Microsoft documentations for the used Windows Commands
the SS64 documentations for the used Windows CMD commands, especially:
ROBOCOPY.exe
ROBOCOPY Exit Codes
the Microsoft documentation about Using command redirection operators
and the SS64 documentation How-to: Redirection
Note: 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 with %ComSpec% /c and the command line within ' appended as additional arguments.

Batch file to remove specific subfolders but move everything inside them to the outside first

I am looking for help with a batch script which can help me remove specific subfolders from all directories that are in the same place as the batch file. But instead of deleting everything in the subdirectory, I want to move the contents outside of the subdirectory first.
To elaborate, I receive files which are always nested inside folders with a very specific pattern:
Every request I receive is in a uniquely named folder which follows no specific pattern (the Job Folder).
Inside every Job Folder, there are two things:
A folder denoting the language the file is in (Lang Folder).
An xml file named "Manifest.xml" which contains instructions and metadata.
There is a folder inside every Lang Folder denoting the brand of the file (Brand Folder).
Inside every Brand Folder is a random assortment of subfolders containing JSON files with various degrees of nesting inside different subfolders.
So a typical request would contain the following structure:
Job Folder/Lang Folder/Brand Folder/...
And I want to transform all folders to follow this structure:
Job Folder/Brand Folder/...
You can see an example below of how the folder structure currently looks and how I would like it to look in the end.
Structure of the folders and files before running the batch file:
French_Job1373
French
BrandA
Subfolder
Example.json
Manifest.xml
German_Job1374
German
BrandB
Subfolder1
Subfolder2
Subfolder3
ExampleFile.json
Manifest.xml
Japanese_Job1375
Japanese
BrandC
Subfolder1
Example.json
Manifest.xml
Korean_Job1376
Korean
BrandC
Subfolder1
Subfolder13
ExampleSrc.json
Manifest.xml
Structure of the folders and files as it should be after running the batch file:
French_Job1373
BrandA
Subfolder
Example.json
Manifest.xml
German_Job1374
BrandB
Subfolder1
Subfolder2
Subfolder3
ExampleFile.json
Manifest.xml
Japanese_Job1375
BrandC
Subfolder1
Example.json
Manifest.xml
Korean_Job1376
BrandC
Subfolder1
Subfolder13
ExampleSrc.json
Manifest.xml
I would like to have a batch file which essentially removes the Lang Folder, pulling everything from inside the Lang Folder one level up.
Following should be taken into consideration:
The file Manifest.xml should not be touched. It should remain inside the Job Folder level.
Everything from the Brand Folder level including the Brand Folder itself should be moved one level up (to the same level where the file Manifest.xml is).
The Language Folder (which at this point should be empty) should be deleted.
There is a finite list of possible languages after which the Lang Folder is named. If required to specify all possible languages in the batch file, I would like the option to add new languages to the list in the future.
So far, I have managed to get the batch file below to work as expected, but I need to run as many times as there are Job Folders and I have to place it inside the Language Folder to have it work as expected. If I place it anywhere else, it just deletes the Lang Folder with all of its contents and does not move anything. I am looking to have the batch file check all folders that are next to it and perform the operation as many times as needed.
What I have so far:
#echo off
if -%1==- echo No parameters! You must add %%P parameter! & pause & goto :EOF
cd /d %1
move * ..
for /d %%f in (*) do move %%f ..
cd ..
"%commander_path%\totalcmd.exe" /o /s %1\..
rd %1
FOR /d /r . %%d IN (Russian) DO #IF EXIST "%%d" rd /s /q "%%d"
FOR /d /r . %%d IN (French) DO #IF EXIST "%%d" rd /s /q "%%d"
FOR /d /r . %%d IN (German) DO #IF EXIST "%%d" rd /s /q "%%d"
FOR /d /r . %%d IN (Japanese) DO #IF EXIST "%%d" rd /s /q "%%d"
That is an excellent description of the folder moving task which can be done with a batch file stored in the folder containing all the Job Folders containing the following lines.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "ErrorPause="
for /D %%I in ("%~dp0*_Job*") do for /F "eol=| delims=_" %%J in ("%%~nxI") do if exist "%%I\%%J\" (
for /F "eol=| delims=" %%K in ('dir "%%I\%%J" /A /B 2^>nul') do move "%%I\%%J\%%K" "%%I\" 2>nul || (echo Failed to move: "%%I\%%J\%%K"& set "ErrorPause=1")
rd "%%I\%%J" 2>nul || (echo Failed to delete: "%%I\%%J"& set "ErrorPause=1")
)
if defined ErrorPause pause
endlocal
The first FOR loop searches in folder of the batch file referenced with %~dp0 (path ends always with a backslash) for non-hidden subfolders matching the wildcard pattern *_Job*. So assigned to loop variable I are one after the other the full qualified folder names of the Job Folders French_Job1373, German_Job1374, German_Job1374, Korean_Job1376, ...
The second FOR command splits up the current Job Folder name on underscores with everything up to first underscore assigned to loop variable J. That is the name of the Lang Folder in current Job Folder.
The IF condition checks if the Lang Folder in current Job Folder exists at all as otherwise there is nothing to do for the current Job Folder.
The third FOR loop starts one more command process in background with %ComSpec% /c and the command line within ' appended as additional arguments. So there is executed with Windows installed into C:\Windows and batch file path being C:\Temp, for example, for the first Lang Folder of first Job Folder:
C:\Windows\System32\cmd.exe /c dir "C:\Temp\French_Job1373\French" /A /B 2>nul
The command DIR outputs
all names of the folders and files including hidden ones because of option /A (all attributes)
in bare format because of option /B which means just folder/file name without path
in the specified directory.
FOR ignores folders and files with hidden attribute set which is the reason for using the DIR command line executed by a separate command process in the background.
Read the Microsoft documentation 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.
FOR with option /F would split up by default all lines captured from handle STDOUT of background command process after started cmd.exe terminated itself into substrings using normal space and horizontal tab as string delimiters, would ignore next the line if the first substring starts with a semicolon being the default end of line character, and would otherwise assign just the first space/tab separated string to specified loop variable K. That behavior would be no problem according to the example for the folders BrandA, BrandB and two times BrandC. But the usage of the option string "eol=| delims=" results in using a vertical bar as end of line character which no folder/file name can contain and the definition of an empty list of string delimiters which disables line splitting behavior. So each folder/file name output by DIR without path is assigned completely to the loop variable K one after the other.
The command MOVE moves the current folder/file in Lang Folder of current Job Folder one level up into the Job Folder. This action is very fast as this is done by just updating the master file table of the file system which is cached by Windows. There is an error message output and the environment variable ErrorPause is defined if an error occurs on moving a folder or file up one level.
The current Lang Folder is removed with command RD after all folders and files in the current Lang Folder are moved up hopefully successfully. The deletion of the folder fails if a folder or file in Lang Folder could not be moved up because of a file in this folder tree is currently opened by an application, or a folder in this folder tree is the current folder of any running process, or a folder/file with same name exists already in Job Folder. An error message is output and the environment variable ErrorPause is defined on deletion of Lang Folder fails because of the folder is not empty.
The command PAUSE halts the batch file execution until a key is pressed if an error occurred. Otherwise the processing of the batch file ends without requiring any further user action.
The batch file can be simply executed once again in case of an error because of a file is opened in an application, or a folder is the current folder of a running process, or the folder/file to move exists already in Job Folder after closing the file in the application or the application itself respectively deletion of the folder/file in Job 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 /? ... explains %~dp0 ... drive and path of argument 0 which is the batch file path.
dir /?
echo /?
endlocal /?
for /?
if /?
move /?
pause /?
rd /?
set /?
setlocal /?
All these commands are internal commands of Windows command processor cmd.exe.
See also: Single line with multiple commands using Windows batch file

Batch file using 7zip to extract nested zips and delete zips after successful extraction

I have a folder full of zip files. Those zip files sometimes contain zip files, that sometimes contain zip files within them, and so on. I am trying to write a batch file that I can paste into the top folder containing all the zips, and when it runs it will unzip all the nested zip files, and within sub-directories, all the way down, and delete the zips once they have been successfully extracted. The full file paths need to be preserved. If there is an error and a file cannot be extracted then it should not be deleted and the file and file path need to be printed to a text file.
So far I have this:
#ECHO ON
SET source=%cd%
FOR /F "TOKENS=*" %%F IN ('DIR /S /B "%source%\*.zip"') DO "C:\Program Files\7-Zip\7z.exe" x "%%~fF" -o"%%~pF\"
EXIT
Which I can drop into a folder and run, it will unzip the first level of zips but none of the nested zips inside. That's the first hurdle.
The next hurdle would be to delete the successfully extracted zips. And last, not to delete any zips that could not be extracted and print their name and/or path to a text file.
Any suggestions or chunks of code are appreciated. Or if there's a better way to do this entirely.
**** UPDATED ****
Mofi posted an answer that looks like it's working except for one piece:
When a ZIP is extracted, it needs to be extracted to a folder with the same name, so I can still follow the structure.
Starting Example:
[Top Level Folder Holding Zips] (folder)
--ExampleZip.zip
---FileInZip.txt
---FileinZip2.txt
--ExampleZip2.zip
---Folder1 (folder)
----ExampleZip3.zip
-----FileinZip3.txt
-----FileinZip4.txt
---ExampleZip4.zip
----FileinZip5.txt
----FileinZip6.txt
Needs to become this:
[Top Level Folder Holding Zips] (folder)
--ExampleZip (folder)
---FileInZip.txt
---FileinZip2.txt
--ExampleZip2 (folder)
---Folder1 (folder)
----ExampleZip3 (folder)
-----FileinZip3.txt
-----FileinZip4.txt
---ExampleZip4 (folder)
----FileinZip5.txt
----FileinZip6.txt
So the full structure is still visible.
I think the top answer in this question shows what I need to include: Extract zip contents into directory with same name as zip file, retain directory structure
This part:
SET "filename=%~1"
SET dirName=%filename:~0,-4%
7z x -o"%dirName%" "%filename%"
Needs to be smashed in there somewhere. Or it seems like there should be a switch for 7Zip that does it, since you can do this from the context menu with "Extract to *" I thought that's what the "extract with full paths" command does but that must have something to do with the -o switch, specifying output path? How do I specify the output path to be a folder with the same name as the input zip? Or merge the answer from that question I linked with Mofi's answer?
*** UPDATED AGAIN ***
I thought there was an issue with the batch file ignoring ZIP files with underscores in the name, but that was a coincidence and it was actually ignoring ZIP files without the Archive file attribute set.
Mofi suggested another fix for that which worked, but the batch file is not extracting nested zips that needed the Archive file attribute set.
This does kind of work, in that I can manually execute the batch file a few times and it will work it's way through everything in the folder, but the loop calculation does not seem to work, or is calculating/terminating before the batch file sets the Archive attribute for all zip files?
Here is the current version I'm working with:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "ErrorOutput="
set "LoopCount=20"
rem The current directory is used on batch file being called without
rem a base folder path or with just one or more double quotes.
set "BaseFolder=%~1"
if defined BaseFolder set "BaseFolder=%BaseFolder:"=%"
if not defined BaseFolder set "BaseFolder=%CD%" & goto VerifyFolderPath
rem Make sure the folder path contains backslashes and not forward slashes
rem and does not contain wildcard characters or redirection operators or a
rem horizontal tab character after removing all double quotes.
set "BaseFolder=%BaseFolder:/=\%"
for /F "delims=*?|<> " %%I in ("%BaseFolder%") do if not "%BaseFolder%" == "%%I" (
echo ERROR: %~nx0 must be called with a valid folder path.
echo "%~1" is not a valid folder path.
set "ErrorOutput=1"
goto EndBatch
)
rem Get full folder path in case of the folder was specified with
rem a relative path. If the folder path references the root of a
rem drive like on using "C:\" or just "\", redefine the folder
rem path with full path for root of the (current) drive.
for %%I in ("%BaseFolder%") do set "BaseFolder=%%~fI"
:VerifyFolderPath
rem The base folder path must end with a backslash for verification.
if not "%BaseFolder:~-1%" == "\" set "BaseFolder=%BaseFolder%\"
rem Verify the existence of the folder. The code above processed also
rem folder paths of folders not existing at all and also invalid folder
rem paths containing for example a colon not (only) after drive letter.
if not exist "%BaseFolder%" (
echo ERROR: Folder "%BaseFolder%" does not exist.
set "ErrorOutput=1"
goto EndBatch
)
rem Make sure to process all ZIP files existing in base folder and all
rem its subfolders by setting archive file attribute on all ZIP files.
%SystemRoot%\System32\attrib.exe +A /S "%BaseFolder%*.zip"
rem Process all *.zip files found in base folder and all its subfolders
rem which have the archive file attribute set. *.zip files with archive
rem file attribute not set are ignored to avoid an endless running loop
rem if a ZIP archive file cannot be extracted successfully with reason(s)
rem output by 7-Zip or if the ZIP file cannot be deleted after successful
rem extraction of the archive. The archive extraction loop runs are limited
rem additionally by a loop counter as defined at top of the batch file for
rem 100% safety on prevention of an endless loop execution.
:ExtractArchives
set "ArchiveProcessed="
for /F "delims=" %%I in ('dir "%BaseFolder%*.zip" /AA-D /B /S 2^>nul') do (
set "ArchiveProcessed=1"
echo Extracting archive: "%%I"
"%ProgramFiles%\7-Zip\7z.exe" x -bd -bso0 -o"%%~dpnI\" -spd -y -- "%%I"
#pause
if errorlevel 255 set "ErrorOutput=1" & goto EndBatch
if errorlevel 1 (
set "ErrorOutput=1"
%SystemRoot%\System32\attrib.exe -A "%%I"
) else (
del /A /F "%%I"
if exist "%%I" (
echo ERROR: Failed to delete: "%%I"
set "ErrorOutput=1"
%SystemRoot%\System32\attrib.exe -A "%%I"
)
)
)
if not defined ArchiveProcessed goto EndBatch
set /A LoopCount-=1
if not LoopCount == 0 goto ExtractArchives
:EndBatch
if defined ErrorOutput echo/& pause
endlocal
echo[
echo[
echo If no errors are displayed above, everything extracted successfully. Remember to delete the batch file once you are done.
#pause
It is rare that there would be maybe 10 or 20 layers of nested zips, so a quick and dirty fix may be just somehow looping the whole batch file 10 or 20 times, unless that is a bad idea or there is a more elegant way to do it.
The task to recursively extract all ZIP archives including nested ZIP archives inside a ZIP archive can be achieved by running the ZIP archive file extraction process in a loop until no ZIP file exists anymore. But there must be at least two use cases taken into account to avoid an endless running archive extraction loop:
The extraction of a ZIP archive file fails for whatever reason. 7-Zip outputs information about the error reason(s). Such a ZIP file should not be processed a second time.
The deletion of a successfully extracted ZIP file fails for whatever reason. The ZIP file should not be processed once again.
The solution is processing only ZIP files with archive file attribute set as done automatically by Windows on creating, renaming or modifying a file and remove the archive file attribute on every ZIP file on which the extraction process or the deletion of the file failed to avoid processing the ZIP file again.
The archive file attribute is set on all *.zip files on directory tree to process before starting the archive files extraction process to make sure that really all existing *.zip files are processed at least once. The archive file attribute is also set on all *.zip files in output directory of a completely successfully processed ZIP archive file to make sure that even *.zip files inside a ZIP file with archive file attribute not set after extraction are processed also on next archive file extraction loop run.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "ErrorOutput="
set "LoopCount=20"
rem The current directory is used on batch file being called without
rem a base folder path or with just one or more double quotes.
set "BaseFolder=%~1"
if defined BaseFolder set "BaseFolder=%BaseFolder:"=%"
if not defined BaseFolder set "BaseFolder=%CD%" & goto VerifyFolderPath
rem Make sure the folder path contains backslashes and not forward slashes
rem and does not contain wildcard characters or redirection operators or a
rem horizontal tab character after removing all double quotes.
set "BaseFolder=%BaseFolder:/=\%"
for /F "delims=*?|<> " %%I in ("%BaseFolder%") do if not "%BaseFolder%" == "%%I" (
echo ERROR: %~nx0 must be called with a valid folder path.
echo "%~1" is not a valid folder path.
set "ErrorOutput=1"
goto EndBatch
)
rem Get full folder path in case of the folder was specified with
rem a relative path. If the folder path references the root of a
rem drive like on using "C:\" or just "\", redefine the folder
rem path with full path for root of the (current) drive.
for %%I in ("%BaseFolder%") do set "BaseFolder=%%~fI"
:VerifyFolderPath
rem The base folder path must end with a backslash for verification.
if not "%BaseFolder:~-1%" == "\" set "BaseFolder=%BaseFolder%\"
rem Verify the existence of the folder. The code above processed also
rem folder paths of folders not existing at all and also invalid folder
rem paths containing for example a colon not (only) after drive letter.
if not exist "%BaseFolder%" (
echo ERROR: Folder "%BaseFolder%" does not exist.
set "ErrorOutput=1"
goto EndBatch
)
rem Make sure to process all ZIP files existing in base folder and all
rem its subfolders by setting archive file attribute on all ZIP files.
%SystemRoot%\System32\attrib.exe +A /S "%BaseFolder%*.zip" >nul
rem Process all *.zip files found in base folder and all its subfolders
rem which have the archive file attribute set. *.zip files with archive
rem file attribute not set are ignored to avoid an endless running loop
rem if a ZIP archive file cannot be extracted successfully with reason(s)
rem output by 7-Zip or if the ZIP file cannot be deleted after successful
rem extraction of the archive. The archive extraction loop runs are limited
rem additionally by a loop counter as defined at top of the batch file for
rem 100% safety on prevention of an endless loop execution.
:ExtractArchives
set "ArchiveProcessed="
for /F "delims=" %%I in ('dir "%BaseFolder%*.zip" /AA-D /B /S 2^>nul') do (
set "ArchiveProcessed=1"
echo Extracting archive: "%%I"
"%ProgramFiles%\7-Zip\7z.exe" x -bd -bso0 -o"%%~dpI" -spd -y -- "%%I"
if errorlevel 255 set "ErrorOutput=1" & goto EndBatch
if errorlevel 1 (
set "ErrorOutput=1"
%SystemRoot%\System32\attrib.exe -A "%%I"
) else (
%SystemRoot%\System32\attrib.exe +A /S "%%~dpnI\*.zip" >nul
del /A /F "%%I"
if exist "%%I" (
echo ERROR: Failed to delete: "%%I"
set "ErrorOutput=1"
%SystemRoot%\System32\attrib.exe -A "%%I"
)
)
)
if not defined ArchiveProcessed goto EndBatch
set /A LoopCount-=1
if not LoopCount == 0 goto ExtractArchives
:EndBatch
if defined ErrorOutput echo/& pause
endlocal
Note: There must be one horizontal tab character after "delims=*?|<> and " on line 16 of the batch file code and not a series of space characters as there will be after copying the code from browser window and pasting the code into a text editor window.
The batch file is commented with lines with command REM (remark). These comments should be read for understanding the code and then can be removed for a more efficient execution of the batch file by Windows command processor.
The 7-Zip switches used in code are explained by help of 7-Zip opened by double clicking on file 7-zip.chm or opening Help from within GUI window of started 7-Zip. On help tab Contents expand the list item Command Line Version and click on list item Switches to get displayed the help page Command Line Switches with all switches supported by currently used version of 7-Zip.
The batch file can be executed with a folder path as argument to process all ZIP files in this folder and all its subfolders. So it is possible to add to Send to context menu of Windows File Explorer a shortcut file which runs the batch file with the folder path passed by Windows File Explorer to the batch file as first argument. It would be also possible to registry the batch file as context menu option for Directory in Windows registry to be able to run the batch file easily from within any application supporting the Windows context menu handlers for a directory.
Edit after question edited: The command line running 7-Zip can be modified to:
"%ProgramFiles%\7-Zip\7z.exe" x -bd -bso0 -o"%%~dpnI\" -spe -spd -y -- "%%I"
Each ZIP file is extracted with this command line into a subfolder in folder of the ZIP file with name of the ZIP file because of replacing -o"%%~dpI" by -o"%%~dpnI\". The additional 7-Zip switch -spe avoids duplicating the folder name if the ZIP file contains at top level a folder with same name as the ZIP file. So if Example3.zip contains at top level the folder Example3, the files are extracted to folder Example3 and not to folder Example3\Example3 as it would occur without usage of option -spe.
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 /?
rem /?
set /?
setlocal /?
Read the Microsoft documentation 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.
Using Groovy, or Ant
This would be a lot easier using Apache Ant or, better still, the Groovy AntBuilder.
e.g. this Groovy script will unzip all the top leval zip files then delete them:
new AntBuilder().with {
def sourceRoot = '.'
// Unzip all .zip files in / underneath sourceRoot
unzip( dest: 'some-folder' ) {
fileset( dir: sourceRoot ) {
include name: "**/*.zip"
}
}
// Unzip throws an exception on failure.
// Delete all .zip files in / underneath sourceRoot
delete {
fileset( dir: sourceRoot, includes: '**/*.zip' )
}
}
You'll need to keep scanning the destination folder for zips, and repeating the above process, until everythings unzipped. You may also find it useful to use a FileScanner.
AntBuilder throws an exception if anything fails, so you can avoid deleting archives that fail to unzip. AntBuilder will also log it's progress, using the standard Java logging mechanisms. You can tell it the level of detail you want, or supress it completely
The full AntBuilder documentation is here:
http://docs.groovy-lang.org/latest/html/documentation/ant-builder.html
Using a fileScanner
Example from the Groovy AntBuilder documentation:
// let's create a scanner of filesets
def scanner = ant.fileScanner {
fileset(dir:"src/test") {
include(name:"**/My*.groovy")
}
}
// now let's iterate over
def found = false
for (f in scanner) {
println("Found file $f")
found = true
assert f instanceof File
assert f.name.endsWith(".groovy")
}
assert found
Putting it together
It's not a huge leap to combine a filesScanner with an AntBuilder to get the job done. I suspect it will be a lot easier than doing it with a batch script.
Finally managed to write a batch file that can unzip nested zips, keeping the archive file structure intact!
logic is that, run recursively until all the zip files are unzipped. Number of iterations default is 5, and can be passed as cmd arg "extract.bat 3". may be changed to a while loop until hit file not found exception. And most importantly delete the archive file after extraction, so, we don't get into endless loop!
But follow the rules below
it uses 7z, make sure in the cmd window 7z can be run, that is in the path
zip file names cannot have spaces. make sure of that and ext is zip
copy the zip file to a directory where there are no other zip files
And only .zip ext, you may change that to rar or anything in the batch file
Here is the batch file
Rem Nested unzip - #sivakd
echo off
if "%1"=="" (set iter=5) else (set iter=%1)
echo Running %iter% iterations
for /l %%x in (1, 1, %iter%) do (
dir *.zip /s /b > ziplist.txt
for /F %%f in (ziplist.txt) do (
7z x %%f -o%%~dpnf -y & del /f %%f
)
del ziplist.txt
)

Test IF file exist, ELSE xcopy these two files

Morning all.
So I've been up hours trying to cobble together -a variety of replies to other posts- into my own code in order to see if I could get something usable. No-go. I'm sufficiently lost in the sauce that I've now got to ask for some help from you.
Background:
OS: Windows 10
I use the program text2folders.exe to create 20-30 new folders on a secondary drive every night.
Primarily, I have a base file "aGallery-dl.bat" that I populate each folder with using an xcopy batch file. Secondarily, from time to time I update the source file "aGallery-dl.bat" using the same xcopy and this overwrites the older target file, populating all folders with the newest "aGallery-dl.bat" (whether they need it or not). All is well.
#echo off
for /D %%a in ("U:\11Web\gallery-dl\deviantart\*.*") do xcopy /y /d ".\aGallery-dl.bat" "%%a\"
I've recently decided I want to add two new files to each folder and have expanded my xcopy to include these. All is well.
#echo off
for /D %%a in ("U:\11Web\gallery-dl\deviantart\*.*") do xcopy /y /d ".\aGallery-dl.bat" "%%a\"
for /D %%a in ("U:\11Web\gallery-dl\deviantart\*.*") do xcopy ".\Folder.jpg" "%%a\"
for /D %%a in ("U:\11Web\gallery-dl\deviantart\*.*") do xcopy ".\Folder2.jpg" "%%a\"
Folder.jpg
a big red X
Folder2.jpg
a big yellow ! mark
When I choose to run a "aGallery-dl.bat" in a given folder (again, one of 100's), it first deletes Folder.jpg then renames Folder2.jpg to Folder.jpg. This has the effect of the red X being replaced by the yellow ! when viewing the folder in File Explorer parent folder. Secondly, it calls "gallery-dl.exe." I use going from red to yellow to let me know I've run "aGallery-dl.bat" at least once. All is well.
rem #echo off
del .\Folder.jpg
ren .\Folder2.jpg Folder.jpg
FOR /F %%i IN ('cd') DO set FOLDER=%%~nxi
"C:\Program Files (x86)\gallery-dl\gallery-dl.exe" -d "U:\11Web\gallery-dl" --download-archive ".\aGDB.sqlite3" "https://www.deviantart.com/"%FOLDER%"/gallery/all"
del .\Folder.jpg
If "aGallery-dl.bat" completes successfully, it finally deletes the Folder.jpg (currently yellow !), and now the representative contents of the folder (usually DeviantArt .jpg's) are visible.
Problem:
When I have to re-run my original xcopy command to update "aGallery-dl.bat" in ALL FOLDERS, the Folder.jpg and Folder2.jpg will be re-copied to all folders, defeating the purpose of deleting them once via "aGallery-dl.bat." I don't want to have to go back and re-run "aGallery-dl.bat" intermittently across 100's of folders (again, only those that have had aGallery-dl.bat run at least once). I need some type of test, that if "aGallery-dl.bat" is already present in the target folder, DO NOT xcopy Folder.jpg and Folder2.jpg aka vague example, below.
*********************************Some sort of test statement here!!!***********************
:aGallery-dlPresent
GOTO eof
:aGallery-dlNotPresent
for /D %%a in ("U:\11Web\gallery-dl\deviantart\*.*") do xcopy ".\Folder.jpg" "%%a\"
for /D %%a in ("U:\11Web\gallery-dl\deviantart\*.*") do xcopy ".\Folder2.jpg" "%%a\"
GOTO eof
:eof
I had found a hopeful candidate test statement in the below (copied in its original form from what/where I read in other post), but am looking for ideas/replacements as I HAVE NO IDEA how to modify/inject/implement the below to work in the above.
If exist \\%DIR%\%Folder%\123456789.wav xcopy \\%DIR%\%Folder%\123456789.wav D:\%New Folder%\ /y
Having XCopy copy a file and not overwrite the previous one if it exists (without prompting)
Note: The below is a vague approximation of what it should all look like (barring having a correct -test statement-).
rem #echo off
*********************************Some sort of test statement here!!!***********************
:aGallery-dlPresent
GOTO eof
:aGallery-dlNotPresent
for /D %%a in ("U:\11Web\gallery-dl\deviantart\*.*") do xcopy ".\Folder.jpg" "%%a\"
for /D %%a in ("U:\11Web\gallery-dl\deviantart\*.*") do xcopy ".\Folder2.jpg" "%%a\"
GOTO eof
:eof
for /D %%a in ("U:\11Web\gallery-dl\deviantart\*.*") do xcopy /y /d ".\aGallery-dl.bat" "%%a\"
The command for copying a file is COPY. It is an internal command of Windows command processor cmd.exe. XCOPY is an eXtended file and directory copying executable in directory %SystemRoot%\System32 which is deprecated since Windows Vista as there is even more powerful ROBOCOPY which is with full qualified file name %SystemRoot%\System32\robocopy.exe.
There is no need to use XCOPY or ROBOCOPY for this simple file copying task. COPY is enough on source files aGallery-dl.bat, Folder.jpgand Folder2.jpg don't have hidden attribute set and the same files in the target directories don't have read-only attribute set.
.\ references the current directory which can be any directory. Windows Explorer sets the directory of the batch file as current directory on double clicking on a batch file. But this is nearly the only method to run a batch file on which the directory of the executed batch file is set automatically as current directory (except the batch file is stored on a network resource accessed using UNC path).
There is %~dp0 to reference the path of the batch file. This path always ends with a backslash which means that no additional backslash is needed on concatenating the batch file path with a file or folder name. The usage of %~dp0 makes it possible to reference files in same directory as the executed batch file independent on which directory is the current directory on execution of the batch file.
The batch file needed for your task is:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /D %%I in ("U:\11Web\gallery-dl\deviantart\*") do (
if not exist "%%I\aGallery-dl.bat" (
copy "%~dp0Folder.jpg" "%%I\"
copy "%~dp0Folder2.jpg" "%%I\"
)
copy /Y "%~dp0aGallery-dl.bat" "%%I\"
)
endlocal
A file/folder name must be enclosed in " if containing a space or one of these characters &()[]{}^=;!'+,`~. For that reason all file/folder names are enclosed in this batch file in double quotes although inside the batch files no file/folder name contains a space or one of the characters in the list. It is important to understand on batch file writing how a command line looks like after Windows command processor processed the command line. See following topics:
How does the Windows Command Interpreter (CMD.EXE) parse scripts?
How to debug a batch file?
Windows interprets *.* like just * which means any file or folder name. For that reason it is enough to just write * and omit .*.
Please note that for /D ignores directories with hidden attribute set.
The batch file checks first for each subfolder if it does not contain the batch file aGallery-dl.bat. In this case it copies the two files Folder.jpg and Folder2.jpg from directory of executed batch file to current subdirectory of U:\11Web\gallery-dl\deviantart.
Then the batch file aGallery-dl.bat is copied from directory of executed batch file to to current subdirectory of U:\11Web\gallery-dl\deviantart independent on its existence in the destination directory.
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 %~dp0 ... drive and path of argument 0 ... full batch file path.
cmd /? ... outputs the help of Windows command processor executing a batch file.
copy /?
echo /?
endlocal /?
for /?
if /?
setlocal /?
See also the chapters Issue 6 and Issue 7 in this answer why using setlocal EnableExtensions DisableDelayedExpansion and endlocal although not necessary by default and why using I instead of a as loop variable although a would work here, too.

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

Resources