Batch script (recursive file moving) - Stops after moving - batch-file

So I'm not experienced at all with scripting so just trying to figure it out as I go and I'm kinda stuck. So with the following script I'm trying to basically search through a set of folders and within each folder there's two files. They stay paired with each other (for calling an external application to run them together). So I'm having them moved, then trying to call the external application and such. I've also had in to delete the two files after the other application has ran... The issue is that after the 2nd file move, I can't put anything else code wise after it for it to execute. For example, there's two test files I was trying this with. If I add the command to run the external application AFTER the move is performed which is what I'm trying to do....it moves the files but it basically moves the first file twice and uses it as both files if that makes sense. So there's file A and file b. I end up with two file A's, just one of them ends up named file b. If I comment any code after the move, it works perfect.
#ECHO ON
SET Loc1Dir=C:\Users\*****\Desktop\temp
SET Loc2Dir=S:\shared\*****\*****\Input\Run\Working_Folder
CD /D "%Loc1Dir%"
FOR /R %%F IN ("*.STMT*") DO CALL :CopyFile %%~F
FOR /R %%I IN ("*.CARD*") DO CALL :CopyFile2 %%~I
:CopyFile
SET copyfname=%~1
SET fname=driver
ECHO F | XCOPY /Y /F "%copyfname%" "%Loc2Dir%\%fname%"
:::CALL :CopyFile2
:::GOTO :EOF
:CopyFile2
SET copyfname2=%~1
SET fname2=card
ECHO F | XCOPY /Y /F "%copyfname2%" "%Loc2Dir%\%fname2%"
I know some is commented out, but that's just from moving stuff around and trying to get it to work correctly. Essentially after the 2 moves are done, I will call an external application, and then it will run and then come back and I'll have this script delete the files and then proceed with the next folder/set of files.

It could be an issue with how you are returning after your CALL statements. Using EXIT /R or a GOTO :EOF at the end of the subroutine will return you to where the CALL was made.
I would try something like this:
EDIT: fixed EXIT /R to be EXIT /B
EDIT2: Added EXE in second SUB
```
#ECHO ON
SET Loc1Dir=C:\Users\*****\Desktop\temp
SET Loc2Dir=S:\shared\*****\*****\Input\Run\Working_Folder
CD /D "%Loc1Dir%"
FOR /R %%F IN ("*.STMT*") DO CALL :CopyFile %%~F
FOR /R %%I IN ("*.CARD*") DO CALL :CopyFile2 %%~I
:CopyFile
SET copyfname=%~1
SET fname=driver
ECHO F | XCOPY /Y /F "%copyfname%" "%Loc2Dir%\%fname%"
foo.exe -argument
EXIT /B
:CopyFile2
SET copyfname2=%~1
SET fname2=card
ECHO F | XCOPY /Y /F "%copyfname2%" "%Loc2Dir%\%fname2%"
EXIT /B
:EOF
```

Related

How to properly use rename (ren) in Batch

I am trying to rename every image in a directory to add the date that each file was created, however, I keep either getting "invalid syntax" or "A duplicate file name exists, or the file cannot be found"
I am running Windows 10, and accessing the images off a flash drive (hence the short file path). I tried having all the code in one for-loop, when that didn't work I tried using batch functions, no dice. I did see someone mention on another thread to use delayed expansion, I would be up for using this if someone could give a better explanation than the /? command.
#echo off
REM batch file is placed in top of F drive, same as "images 2017+"
cd "F:\images 2017+"
FOR /R "F:\images 2017+" %%F in (*.jpg) do call :renER "%%~nF" "%%~tF"
goto :eof
:renER
cd "F:\images 2017+"
pause
echo %1
echo %2
rename %1.jpg %1_%2.jpg
pause
goto :eof
:end
For every .jpg file in "images 2017+", the date which that file was created would be stuck onto the end after a space.
thisIsMyFile.jpg made at 5-13-2017, would become thisIsMyFile 5-13-2017.jpg
Current output
EDIT:
I am CDing into the same directory as the images are, then using the passed variables to locate the correct image (The date is one of the passed variables, and shows up in the echo command).
I notice that you only want the date, not the time so you can do that as follows using your existing Call to a label, There is also no need to use FOR /R in this case so I'll use a normal for loop:
#echo off
FOR %%A IN ("F:\images 2017+\*.jpg") DO (
CALL :RenER "%%~fA" %%~tA
)
GOTO :eof
:RenER
PAUSE
ECHO %1
ECHO %2
SET "_tmp=%~2"
SET "_tmp=%tmp:/=-"
REN "%~1" "%~n1_%_tmp%%~x1"
PAUSE
GOTO :eof
Notice how above we are dropping the Time off immediately by not wrapping it in quotes since you don't want that to be part of the file name.
You can also forgo the call to a label entirely without needing delayed expansion by using a second loop, as a matter of preference I think this is quite a bit cleaner!
#echo off
FOR %%A IN ("F:\images 2017+\*.jpg") DO (
FOR /F "Tokens=1-3 Delims=/ " %%a IN ('echo.%%~tA') DO (
PAUSE
ECHO.%%~fA
ECHO.%%~tA
REN "%%~fA" "%%~nA_%%a-%%b-%%c%%~xA"
PAUSE
)
)
this is nice and clean and with a minor edit we can paste it directly into the CMD Prompt which is nicer still This is because we are not using DelayedExpansion, Calling a Label, or using Temp variables so by changing the %%s to %s, we can then Paste this directly into the CMD Line which is often more convenient when doing these sorts of operations:
This Multi-line will do just fine to be pasted into CMD directly:
FOR %%A IN ("F:\images 2017+\*.jpg") DO (
FOR /F "Tokens=1-3 Delims=/ " %a IN ('echo.%~tA') DO #(
PAUSE
ECHO.%~fA
ECHO.%~tA
REN "%~fA" "%~nA_%a-%b-%c%~xA"
PAUSE
)
)
or, as a single line to paste into CMD if you prefer:
FOR %A IN ("F:\images 2017+\*.jpg") DO #( FOR /F "Tokens=1-3 Delims=/ " %a IN ('echo.%~tA') DO #( PAUSE& ECHO.%~fA& ECHO.%~tA& REN "%~fA" "%~nA_%a-%b-%c%~xA"& PAUSE ) )
no need to cd anywhere. ren takes a full path/filename for source - just the destination must be a filename only. So ... do call :renER "%%~fF" "%%~tF" is fine (no need to snip the extension and add it again later). In the subroutine reformat the time to a valid string and reassemble the destination file name:
#echo off
FOR /R "F:\images 2017+" %%F in (*.jpg) do call :renER "%%~fF" "%%~tF"
goto :eof
:renER
pause
echo %1
echo %2
set "string=%~2"
set "string=%string::=-%"
set "string=%string:/=-"
ECHO rename "%~1" "%~n1_%string%%~x1"
pause
goto :eof
:end
NOTE: I disarmed the rename command. Remove the ECHO after troubleshooting, if it works as intended.
#Stephan's answer is probably the best approach. But if you want to change directories ...
The windows shell has a working drive/volume, and on each drive/volume a current working folder. cd changes the working folder on a disk; to change the working folder on a drive (which is not the working drive) and to make that drive the working drive, you need to use cd /d, in this case cd /d "F:\images 2017+".
(A plain cd in this instance changes the working folder on F:\, but if your working folder is on C: -- as I'm guessing is the case -- it will not be changed.)
Assuming command extensions are enabled, you should also be able to use pushd and popd. pushd behaves like cd /d but also saves your previous location; popd returns you to that previous location. (And IIRC pushd will accept UNC paths.)
So at the beginning of your script, pushd "F:\images 2017+", and at the end popd.
I tend to favor pushd/popd over cd because invocations can be nested. So you can do things like
(assume working directory is C:\Users\IoCalisto):
pushd "F:\images 2017+"
(working directory is now F:\images 2017+)
pushd "Z:\images 2015-2016"
(working directory is now Z:\images 2015-2016)
popd
(working directory is now F:\images 2017+)
popd
(working directory is now C:\Users\IoCalisto)
... with this approach, your scripts will have fewer "side effects" and be more modular, or at least modularizable.

How do you search through a directory and take actions?

I am looking to make a batch file which will sift through a directory full of computer backups. The file format is "computername-date." Since I know the computer name is static, I need to find and take that directory so I can restore it's contents.
I never realized that for loops are so foreign from what I play with in other languages, so I find myself getting nowhere anytime soon.
REM First mount the drive that contains the backed up files
net use P: \\DC1\Shared\1Backups
REM Get the computer's name so we know what PC backup to use.
set SaveDirectory=%computername%
REM For each folder in the directory, do this when the computer name is found.
FOR /R "P:\" %%G in (%SaveDirectory%*) DO (
REM Restore files
echo found it.
REM Copy subdirectories into User Folder
mkdir P:\UGH
)
REM Dismount drive
The problem with what I have now is that when I run the code, the DO never runs. It should find that there is a folder called "INTERN-6.21.2019" by searching "INTERN*"
My impression of the for statement may be wrong. I need it to search through the P:/ Directory, not the subfolders. Compare the folder names to the SavedDirectory, then do something when they match. What am I doing wrong?
I've normally had good results with using CALL statements to invoke a subroutine rather than running items inside ( ).
The trick here is that you need to pass arguments to the the subroutine. Typically you'd pass the variable(s) you use in your FOR statement. These are referenced using %1 (or %1 %2 %3 if you have multiple things to pass) because these act like command line arguments. To exit the subroutine you then jump to EOF which then returns control to your FOR loop. After your FOR loop, don't forget to jump over your subroutine or you'll execute it again. In the example below I jump to :end, but you certainly could jump to somewhere else to do more things.
So using this methodology, your code might be like this:
set SaveDirectory=%computername%
FOR /R "P:\" %%G in (%SaveDirectory%*) DO CALL :process %%G
Goto :end
:process
REM Processing goes here
goto :end
:end
Hope this helps
This might get you going. This will find the "newest" directory. It is not clear from the question exactly what is to be copied and to where.
SET "MOSTRECENT=unknown"
FOR /F "delims=" %%d IN ('DIR /B /A:D /O:D "P:\%COMPUTERNAME%*"') DO (
SET "MOSTRECENT=%%~fd"
)
ECHO The most recent backup is "%MOSTRECENT%"
IF EXIST "MOSTRECENT" (COPY ...
This answer is based upon my understanding of what you're trying to do, copy a network directory named with the computername and a datestring, (with todays date), to another location:
#PushD "\\DC1\Shared\1Backups" || GoTo :EOF
#For /F "EOL=DTokens=1-3" %%A In (
'WMIC Path Win32_LocalTime Get Day^,Month^,Year'
)Do #For /F Tokens^=* %%D In ("%%C"
)Do #XCopy "%COMPUTERNAME%-%%B.%%A.%%D" "P:\UGH\" /H /R /K /Y>NUL 2>&1
#PopD

CMD trying to set variable in a loop

I'm working with windows' cmd and trying to set a variable in a loop. Here's the code I have:
for /d %%a in ("F:\backup*") do set folder=%%a
ECHO %folder%
PAUSE
I want to look for a folder with name starting with "backup" on drive F and save that folder's name to %folder% variable. So for example if the folder would be called "backup 2017-01-18" I'd like that saved to a var.
Instead it doesn't seem to set anything as the ECHO just prints that "ECHO is on". The for loop is correct and the folder is there as well (I'm already using that piece of code for other batch with robocopy).
I could theoretically put all my code inside the FOR loop and use %%a instead of the %folder% var but that seems like a hacky solution.
All the solutions I found so far pointed to using EnableDelayedExpansion. I modified the code to use it like that:
Setlocal EnableDelayedExpansion
for /d %%a in ("F:\backup*") do set folder=%%a
ECHO !folder!
PAUSE
But now ECHO prints "!folder!" as if it would not detect the variable. If I revert to ECHO %folder% I once again learn that "ECHO is on".
EDIT:
I found the issue here. I was also running another batch file on the backup folder. It turns out that ROBOCOPY (which I used in that batch) is setting the enclosing folder to hidden, system and readonly by default (even if copied files are not hidden or system o_0). When I removed HSR attributes on the directory the code posted here started working fine (the initial version).
If your loop for /d %%a in ("F:\backup*") do does not detect any directories whose names begin with backup, they either do not exist or there are the attributes hidden and/or system set.
To detect also such hidden or system directories, replace the for /D loop by this:
rem Change to parent directory "F:\" temporarily in order for the `~f` modifier to resolve the full path properly:
pushd "F:\" || exit /B 1
for /F "eol=| delims=" %%D in ('dir /B /A:D /O:N "backup*"') do set "folder=%%~fD"
popd

Batch Script to create folder based on name, add to existing code

The code below works fine, here is a list of it's functions:
It moves files based on the fist 4 characters to a pre-created folder with the same first 4 characters
If the folder does not exist, it will not move the file, as there is no folder with the same fist 4 chars.
#echo on
setlocal enabledelayedexpansion
cls
pushd R:\Contracts\Sites
for /f "tokens=*" %%1 in ('dir /a-d /b *') do (
set filename=%%1&set dirname=!filename:~0,4!
for /f "tokens=*" %%A in ('dir /ad /b') do (
set dirid=%%A & set dirid=!dirid:~0,4!
if "!dirid!" equ "!dirname!" move %%1 %%A
)
)
I would like to add one extra function to this code please. Pleas have a look at the example below.
I have 5 files
X32A-test.docx or X32A-test.pptx (there will only be one docx or pptx, "NEVER two with the same name")
X32A-test.pdf
X32A-test.avi
X32A-test-eng.sub
X32A-test-small.jpg
I would like the code to CREATE a folder if it does not exist, based on the file name if it has the extension docx or pptx.
So with the above example it would create a folder named: "X32A-test". Then all the other files with "X32A" in the front of the name will be moved to that newly created folder "X32A-test".
I hope it is clear enough. If not please ask me for more information.
Thank you
It is much simpler and more efficient to use the simple FOR instead of FOR /F in your case.
And rather than looping through every file and moving them individually, it is easier and more efficient to use wildcards.
The first loop finds the .pptx and .docx files and creates folders as needed
The second loop finds all the directories and moves all files that start with the directory name into the directory.
#echo on
setlocal enableDelayedExpansion
cls
pushd R:\Contracts\Sites
for %%F in (*.docx *.pptx) do (
set "folder=%%F"
2>nul md !folder:~0,4!
)
for /d %%F in (*) do move %%F* %%F
popd
If needed, you can protect yourself against directory names shorter than length 4.
#echo on
setlocal enableDelayedExpansion
cls
pushd R:\Contracts\Sites
for %%F in (*.docx *.pptx) do (
set "folder=%%F"
set folder=!folder:~0,4!
if !folder:~0,3! neq !folder! 2>nul md !folder!
)
for /d %%F in (????) do (
set "folder=%%F"
if "!folder:~0,3!" neq "%%F" move %%F* %%F
)
popd
Note that this solution may fail if a file name contains !. If that arises then you need to toggle delayed expansion on and off within the loop(s).
I can see the entire process (including the part already implemented) like this:
All the files that are not yet in their "home" directories are moved there.
For all .docx and .pptx files left, create directories based on the files' names.
Obviously, the step #2 creates new "homes" and those will still be "uninhabited" this far. So all that is left to do now is to repeat the step #1.
So I would probably reorganised your process and, with the additional requirement, it could be implemented this way:
…
PUSHD your_root_directory
FOR /D %%D IN (*) DO (
CALL :movefiles "%%D"
)
FOR %%F in (*.docx *.pptx) DO (
MKDIR "%%~dpnF"
CALL :movefiles "%%~dpnF"
)
…
GOTO :EOF
:movefiles
SET "dirname=%~n1"
SET "mask=%dirname:~0,4%*"
MOVE "%~dp1%mask%" %1
Note: The steps #2 and #3 could be either implemented as separate loops or combined in one. The above script uses the latter approach.
You can use negative offsets in the !var:~offset,len! evaluation as follows:
set fspec=X32A-test.docx
echo !fspec:~-10!
echo !fspec:~0,-10!
That second line above gives you -test.docx and you can simply check that against your two desired possibilities with an if statement (or two).
Then you can use the third line to get the rest of the name for creating a directory.
The following example script shows how this could be done:
#setlocal enableextensions enabledelayedexpansion
#echo off
set fspec=X32A-test.docx
set bit1=!fspec:~-10!
set bit2=!fspec:~0,-10!
if .!bit1!.==.-test.docx. echo mkdir !bit2!
if .!bit1!.==.-test.pptx. echo mkdir !bit2!
endlocal
I'm echoing the mkdir command rather than executing it so you need to take out the echo. You'll also need to integrate the set and if statements into your loop but, based on what you have so far, you should have little trouble with that.
If, as you seem to indicate in a comment, the first four characters are the key and the last five decide on whether to make the directory, as in:
x32s-test.docx
a21w-production.pptx
xxxx-whatever_the_blazes_you_want.some_other_rubbish.docx
Then you're really only interested in the first four and last five:
#setlocal enableextensions enabledelayedexpansion
#echo off
set fspec=a12b-whatever_the_blazes_you_want.some_other_rubbish.docx
set bit1=!fspec:~-5!
set bit2=!fspec:~0,4!
if .!bit1!.==..docx. echo mkdir !bit2!
if .!bit1!.==..pptx. echo mkdir !bit2!
endlocal
This checks the correct extensions and outputs:
mkdir a12b
as expected.

copying *.dat from different directories to a mirror directory

I have a directory with the following structure:
C:\Directory1\
sub1\
sub2\
sub3\
somefilename.txt
someotherfile.txt
Inside each sub*\ there are .dat files that I need to copy to another directory mirroring along the way the directory name where they were found. So if I find C:\Directory1\sub2\file.dat I would copy that into C:\mirror\sub2\file.dat and so on.
I tried several combinations of things similar to
for /R %SRC_DIR% %%f in (*.dat) do copy "%%f" %BACKUP_DIR%\%%~nf%%~xf
(please note this is just an example of code I was playing with, i know it doesn't work)
anyway, after trying to a couple of day I still don't know how to do it. Any chance of help?
Code is appreciated.
thanks!
This works for me:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set SourceDir=c:\source\dir
set TargetDir=d:\target\path
set FileMask=*.cpp
for /r "%SourceDir%" %%F in (%FileMask%) do (
call :ReplacePrefix target_path "%%~F" "%SourceDir%" "%TargetDir%"
call :CopyFile "%%~F" "!target_path!"
)
endlocal
goto :EOF
:CopyFile %1=source_path %2=target_path
mkdir %~dp2
copy %1 %2
goto :EOF
:ReplacePrefix %1=result_var_name %2=string %3=replace_what %4=replace_with
rem a question mark is prepended to ensure matching only at the beginning of the string
set rp_value=?%~2
call :DoIt "set %1=%%rp_value:?%~3=%~4%%"
goto :EOF
:DoIt %1=cmd
%~1
goto :EOF
Keep in mind though that it can break if paths contain unusual characters (such as = and some others which I can't remember now).
Use the following XCOPY command:
xcopy "c:\directory1\*.dat" "c:\mirror\" /s /v /c /y
If you do not want to see the filenames displayed on the screen add '/q' to the list of options.
The '/s' will copy files from subfolders. If the subfolders don't already exist they will be created.
The '/v' forces verification. Not necessary but it's nice to have that peace of mind.
The '/c' forces XCOPY to continue with the rest of the files if it encounters any problems - in other words, your batch file won't halt abruptly with only 'some' of your files copied. XCOPY will copy all that it can.
The '/y' suppresses prompting to overwrite an existing file.

Resources