I want to copy files off a removable drive using a batch file, regardless of the drive letter it gets.
So far, no go. There don't seem to be any readily available commands or 3rd-party command-line tools that would handle paths based on volume labels.
I tried FreeFileSync, but it works in big batches, and I need precise file operations here. Also, it doesn't do deletions, and I need to MOVE files off the pendrive.
What piques my interest, however, is that issuing a nonsense command like...
C:\> cd MYPENDRIVE:
... results in quite an interesting bit in the default error message:
The filename, directory name, or volume label syntax is incorrect.
^^^^^^^^^^^^^^^^^^^
If this message is to be trusted, then it means there IS some correct syntax for putting a volume label in there. Or isn't there?
Note: I can settle for writing a small "getdriveletterforvolume" tool, if I have to, but I'd rather not reinvent the wheel. Also, during longer batch file operations the drive may be removed and replaced with another, at which point I'll need the operations to cease, and not continue on another drive that gets the same drive letter.
Note2: Using \\?\Volume{12345678.....} is a last resort, though it can be tamed to some extent, to build a rudimentary batch file like...
SET PENDRIVE=\\?\Volume{949a764e-a2f0-11e3-b710-6cf04975450b}
SET BACKUPDRIVE=\\?\Volume{e79091c4-5c2a-11e3-86f9-806e6f6e6963}
if exist %PENDRIVE% {
if exist %BACKUPDRIVE% {
move %PENDRIVE%\*.* %BACKUPDRIVE%
}
}
... but it's ugly and doesn't let me do any magic like giving two pendrives the same label to have them behave identically. I know it's not a common case, but hey, why limit one's options where there might be a ready solution?
Edit: Progress. I modified the subroutine to return a value as VOLUMEID:
CALL :GetVolumeID PENDRIVE
SET "PENDRIVE=%VOLUMEID%"
IF EXIST "%PENDRIVE%\" ECHO PENDRIVE is connected at %PENDRIVE%!
GOTO :eof
:GetVolumeID
FOR /F "skip=1" %%A in ('"wmic volume where label='%~1' get deviceid 2>/null"') do (
SET "VOLUMEID=%%A"
GOTO :eof
)
GOTO :eof
... but now if the pendrive is missing, wmic returns some empty-like value (NOT an empty string; it fails an if %%A=="" check) - and so IF EXIST \ ends up true. How do I eliminate an empty result like that..? I tried SET VOLUMEID=$FOO$ before the FOR, but it overwrites that with an empty value anyway.
Finally! Here's a proof of concept, for whomever finds it useful.
#ECHO OFF
CALL :GetVolumeID BACKUPDRIVE
SET "BACKUPDRIVE=%VOLUMEID%"
CALL :GetVolumeID PENDRIVE
SET "PENDRIVE=%VOLUMEID%"
if exist %PENDRIVE%\ (
if exist %BACKUPDRIVE%\ (
echo Emptying the pendrive.
move %PENDRIVE%\*.* %BACKUPDRIVE%\pendrive\
)
)
GOTO :eof
:GetVolumeID
SET VOLUMEID=$$$
FOR /F "tokens=1,* delims==" %%A IN ('"wmic volume where label='%~1' get deviceid /value 2>nul"') DO IF NOT "%%~B"=="" SET "VOLUMEID=%%~B"
SET "VOLUMEID=%VOLUMEID:~0,-2%"
GOTO :eof
I added the $ bogus value to be returned to fail an EXIST check.
The final SET VOLUMEID=%VOLUMEID... line removes the trailing backslash (for some reason it counts as TWO characters) so that paths written as %MYDRIVE%\file*.* look sane.
here's a subroutine that will push you to a drive by its name.It takes one argument - the drive label:
#echo off
:pushToLabel label
setlocal
for /f "skip=1 delims=:" %%A in ('"wmic logicaldisk where VolumeName='%~1' get name"') do (
set "drive=%%A:"
goto :break
)
:break
echo %drive%
endlocal && (
pushd %drive%
)
Here's a final proof of concept, for whomever finds it useful. Calling GetVolumeID sets a DRIVELETTER variable as return, in the "F:" form.
#ECHO OFF
CALL :GetVolumeID BACKUPDRIVE
SET "BACKUPDRIVE=%DRIVELETTER%"
CALL :GetVolumeID PENDRIVE
SET "PENDRIVE=%DRIVELETTER%"
if exist %PENDRIVE%\ (
if exist %BACKUPDRIVE%\ (
echo Emptying the pendrive.
move %PENDRIVE%\*.* %BACKUPDRIVE%\pendrive\
)
)
GOTO :eof
:GetVolumeID
SET DRIVELETTER=$$$
FOR /F "tokens=1,* delims==" %%A IN ('"wmic volume where label='%~1' get deviceid /value 2>nul"') DO IF NOT "%%~B"=="" SET "DRIVELETTER=%%~B"
SET "DRIVELETTER=%DRIVELETTER:~0,-2%"
GOTO :eof
Related
I am trying to create windows batch script to check the size of file after it is downloaded but it fails with this message.
The Process cannot access the file as it is used by another process
2048 was unexpected at this time.
Thanks in advance for any help.
#echo off
d:\wget.exe ...
Rem this wget process downloads tst_file.xml
set file="tst_file.xml"
set maxbytesize=2048
for /F "usebackq" %%A IN ('%file%') DO set size=%%~zA
if %size% LSS %maxbytesize% (echo file is less than 2KB)
Consider this alteration.
#echo off
d:\wget.exe ...
Rem this wget process downloads tst_file.xml
set "file=tst_file.xml"
set maxbytesize=2048
if not exist "%file%" (
#echo '%file%' does not exist.
goto :SKIP_CHECK
)
for /F "usebackq" %%A IN ('%file%') DO set size=%%~zA
if %size% LSS %maxbytesize% (echo file is less than 2KB)
:SKIP_CHECK
Notes
If the file doesn't exist, the FOR loop will not be able to get the file size, and the LSS comparison will blow up with no operand to compare.
If you had quoted each of the vars (e.g. if "%size%" LSS "%maxbytesize%" ...), it wouldn't have blown up. However, it would have performed a lexical comparison, which when compared to anything would evaluate to true.
In batch, if you use double quotes around only the value in a SET command, the quotes will be included in the value. Even though it looks unorthodox, put quotes around the whole <variable>=<value> expression.
If you skip tokenization and get the entire line (i.e. tokens=*), you'll be able to handle files with spaces in the name.
In PHP or Javascript and other languages as well, there is the switch, case statement as a control flow tool. One of the neat features of that is it allows for multiple cases to be pointed to a single command group. For example:
switch(abc) {
case a:
case b:
case false:
console.log("hi")
break;
case c:
console.log("see ya")
break;
etc...
}
So that if abc is equal to a or b or is false, the "Hi" will be logged. Depending on the code, it can be a lot cleaner than calling from an object or tons of if else or if x || y || z statements.
I have a windows batch file where I'm doing the following:
GOTO %1
..... stuff
.... more stuff
REM =================LABELS BELOW==============
:-h
:--help
:-?
:--?
type help.txt
exit /b
It's more detailed than the above pseudocode, but that's the gist of it. It allows for aliases for the same argument And it works. If I execute mycmd -h or mycmd --help etc., I help the help file text displayed on the screen.
However, on the last line of my output, I get the error, THE SYSTEM CANNOT FIND THE BATCH LABEL SPECIFIED -.
The error might be caused by something else. I have some CALL commands and GOTO :EOF statements, so that certainly could be the source of the error.
But I've never seen the logic I applied above before used in a batch file, and I'm wondering if there are some other side effects that I might not be considering. Is it possible that I will encounter unpredictable side effects down the road? Is this bad practice for any reason?
Update:
I hadn't posted my code, because I think it's hard to read, and it's a work in progress. Basically, what you're seeing here is argument parsing. I'm parsing the passed values in to categories - imagine this example: node --file=d.js. I'm calling the --file the param, and the d.js the arg. And much of the code below is creating an array of each.
#echo off
SETLOCAL enabledelayedexpansion
#SET "T=%1"
IF /i NOT DEFINED T (GOTO -h)
SET /a counter=1
SET /a argCount=0
SET /a index=0
for %%x in (%*) do set /A argCount+=1
for /l %%x in (1,2,%argCount%) do (
call SET param[!index!]=%%!counter!
set /a counter+=2
SET /a index+=1
)
SET /A paramCount=!index!
SET /a counter=2
SET /a index=0
for /l %%x in (2,2,%argCount%) do (
call SET arg[!index!]="%%!counter!"
set /a counter+=2
SET /a index+=1
)
for /l %%i in (0,1,!paramCount!) do (
SET "arg=!ARG[%%i]!"
SET "p=!param[%%i]!"
CALL :!p! !arg!
)
GOTO END
:-h
:--help
:--?
:-?
type %~dp0\lib\help.txt
GOTO END
:--unzip
SET "a=%1"
IF /i NOT DEFINED a (
SET "MASK=\d+-[a-z]+.zip"
) ELSE if [%a%]==["all"] (
SET "MASK=\d+-[a-z]+.zip"
) else (
SET "MASK=!a!"
)
for /f "usebackq tokens=*" %%i in (`dir /a /b %dls%^|grep -iE "!MASK!"`) do (
#7z x %dls%\%%i -omypath\share\icons
)
GOTO :EOF
:END
#echo Done
ENDLOCAL
Update: I don't know if this will be helpful for anyone, but the problem was with SET /A paramCount=!index!. That let to always looking for a parameter with an argument no matter what, so that if my second parameter didn't have an argument, or neither did, it was causing problems. I 'solved' the problem by setting paramCount to !index!-1, but I think the upshot of this is that it's quite difficult to pass arbitrary, potentially optional, parameters to batch files, and probably should be parsed differently than I have done - or using a different coding language. That said, it's working fine now for what I need.
like I told in the question I get an unexplainable Syntax error from my Code.
I've bin searching for a program which automatically backups some saves every 5 Minutes. After I hadn't found anything which belongs to my purposes I decided to do it my own.. in Batch.
Here's the code:
#echo off
set name=Backup
for /f "tokens=1-3 delims=/:" %%a in ("%TIME%") do (set mytime=%%a.%%b,%%c)
set mytime=%mytime:~0,8%
set backupname=%name%_%date%_%mytime%
set dir1= (here comes the source directory)
set dir2= (here comes the target directory)
set countvar=1
:start
For /f "tokens=1-3 delims=/:" %%a in ("%TIME%") do (set mytime=%%a.%%b,%%c)
set mytime=%mytime:~0,8%
set backupname=%name%_%date%_%mytime%
set dir2=(here comes the target directory)\%backupname%
echo Backupordner: %backupname%
ROBOCOPY %dir1% %dir2%
if "%countvar%" == "1" (
set VarDir1=%dir2%
)
if "%countvar%" == "2" (
set VarDir2=%dir2%
)
if "%countvar%" == "3" (
set VarDir3=%dir2%
rmdir /S /Q "%VarDir1%"
set VarDir1=%VarDir2%
set VarDir2=%VarDir3%
set /a countvar=%countvar%-1
)
set /a countvar=%countvar%+1
#ping -n 30 localhost> nul
goto start
What it basically does is copying the files from the source directory into a folder, which is named after the date and time, in the target directory.
Caused by the high size of the backuped files I decided to add a feature, which deletes the third oldest save, so there are two remaining, newer save-files.
This is where the problem occurs: The first two "deletes" work properly, the third "delete" causes a syntax error. Everything runs normal after it.
Does anybody have an idea where the problem could be?
Yet another example of the delayedexpansion trap.
When vardir3 is established, it has no value, so vardir2 acquires nothing on the first occasion that count=3
On the second occasion, var1 acquires that value so on the third occasion, you get a syntax error as var1 is empty.
Solution: Forget var3 entirely. In count=3, set var2 to %dir2%.
Please search SO for the many, many articles on delayed expansion.
Also, you're better off using set "var=value" for a string assignment as it does not assign any trailing spaces that may be on the line.
Ok, so I've been bating (hehe) my head against a wall here.
I am looking for an option/code that would allow me to search for a partial path and/or filename from a .bat script that I would export to an outside file.
Now, "search", "export" and "outside file" is something I am fine with. The part that is giving me a headache is the "partial".
To elaborate.
I am looking for a folder called DATA and a file called userinfo.txt inside DATA.
Those are constant. So the path I have is DATA\userinfo.txt
I am also 99% certain that this folder will be in D:\ but thats not a concern right now. Where ever it is I'll find it.
But I cannot figure out how to look for a partial path\filename for the life of me.
Reason I have specified that DATA\userinfo.txt is a constant is due to other folders ability to be named arbitrarily. So in my below example 01-12-2016 does not have to be named according to that convention. For USA it would most likely be named 12-01-2016. It is also sometimes named 20161201 or 20160112 or on top of all that has a letter prefix such as d01-12-2016. On that note DATA is always DATA, which is why I said DATA is constant in my search. Another thing that will be the same is the grandparent folder. When i say "same" i mean "shared" between the two applications. It does not mean it will always be named "program" as in my example below.
Googling this and using things I know has got me nowhere.
Reason I cannot simply use
where /r d: userinfo.txt
is that that specific command will return hundreds of results as there is a userinfo.txt created for every.single.day the program was running and is stored separately.
Alternatively - if there would be a way to comb trough those hundreds of results and find the matching part that would also resolve my issue.
This however brings up another headache as there is usually more than one program with this exact file.
so in the example of
d:\users\path\program\storage\01-12-2016\userinfo.txt
d:\users\path\program\otherstorage\01-12-2016\userinfo.txt
d:\users\path\program\storage\02-12-2016\userinfo.txt
d:\users\path\program\otherstorage\02-12-2016\userinfo.txt
d:\users\path\program\storage\03-12-2016\userinfo.txt
d:\users\path\program\otherstorage\03-12-2016\userinfo.txt
d:\users\path\program\storage\04-12-2016\userinfo.txt
d:\users\path\program\otherstorage\04-12-2016\userinfo.txt
d:\users\path\program\storage\05-12-2016\userinfo.txt
d:\users\path\program\otherstorage\05-12-2016\userinfo.txt
d:\users\path\program\storage\06-12-2016\userinfo.txt
d:\users\path\program\otherstorage\06-12-2016\userinfo.txt
d:\users\path\program\storage\data\userinfo.txt
d:\users\path\program\otherstorage\data\userinfo.txt
Note: storage, otherstorage, storageother, storage2, storagegh are all arbitrary names as these folders are named accoring to end-user wishes.
I would want to export two separate variables for
d:\users\path\program\storage
and
d:\users\path\program\otherstorage
I would also need to do this for \data\userinfo.txt
So if searching for \data\userinfo.txt it would return
d:\users\path\program\storage\data\userinfo.txt
d:\users\path\program\otherstorage\data\userinfo.txt
I would also want to isolate both
d:\users\path\program\storage
and
d:\users\path\program\otherstorage
and use it as (separate) local variables.
I would need to note that installing/downloading any external scripting tools/aids would not be a suitable solution as I work on a lot of computers, most of which I do not have internet access and/or sufficient permissions for external downloads/installations so anything that is not integrated into the bat and needs to be imported separately is a bad idea.
Also, I am working on Windows XP SP3 but I would need this bat to be able to run on XP SP2, XP SP3, Windows 7, Windows 10, Windows NT, Windows 2000.
Any help would be appreciated.
Please note that
d:\users\path\program
would also be an acceptable variable. In this case I would manually amend the remainder of the path or would rely on end-user (my coworkers) input to complete the path correctly. The last has proven to be a fools errand.
The way that I've been handling it until now is to look for a .exe that I KNOW will be in both folders. This is a part of my code below edited to match the current example.
#echo off
SETLOCAL
echo Program will now look for program.exe and programgh.exe. Please input, when asked, matching part of the path for these files.
echo Example:
echo d:\users\path\program\storage\bin\program.exe
echo d:\users\path\program\otherstorage\bin\programgh.exe
echo In above example matching part is d:\users\path\program so you would enter that when prompted
echo Please do not input the last pathing mark: \ (backslash)
echo -------------searching---------------
::I am exporting errors to nul as I don't want them to be spammed by errors and other data that they would think is their fault
where /r c: program*.exe 2>nul
where /r d: program*.exe 2>nul
where /r e: program*.exe 2>nul
where /r f: program*.exe 2>nul
set /p dualpath="Please enter matching paths for program folder: "
After that I would proceed to work with %dualpath% variable.
As it usually happens (to me at least) most people would just copy the example path without taking a look at what the program has spat out and would be confused as to why the program did not work. Either that or would copy everything up to program.exe and programgh.exe - including the otherstorage\bin\ without noticing that \storage\ and \otherstorage\ do not match.
I think this now covers all the comments or additional questions and clarifies a bit better what I need. Thank you all for help so far and I hope that this is easier to understand.
If a Windows cmd command allows wildcards in a (partially or fully qualified) path then wildcards must be used only in the path leaf (i.e. the last item or container in the path). However, you could apply findstr regex to narrow command output e.g. as follows:
where /r d:\ userinfo.txt | findstr /I "\\storage2*\\data\\userinfo.txt"
above command wold narrow output to paths ending with \storage\data\userinfo.txt and \storage2\data\userinfo.txt
Another example - narrow output to paths ending with \storageX\data\userinfo.txt where X is either nothing or any decimal cipher [0-9]:
dir /B /S d:\userinfo.txt | findstr /I "\\storage[0-9]*\\data\\userinfo.txt"
Put the paths to environment variables (with _var prefix for easier next identification), e.g. _varstorage, _varstorage2, …
#ECHO OFF
SETLOCAL EnableExtensions
for /F "delims=" %%F in ('
dir /B /S "d:\userinfo.txt" ^| findstr /I "\\storage[0-9]*\\data\\userinfo.txt"') do (
for /D %%D in ("%%~dpF..") do (
set "_var%%~nxD=%%~fD"
rem %%~fD path
rem %%~nxD last item in above path
rem _var variable name prefix
)
)
rem show result:
set _var
See also next %%~nxD and %%~D explanation: Command Line arguments (Parameters): Parameter Extensions
If I got your intention right, the following script should do what you want:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=D:\" & rem "D:\", "D:\users",..., or "D:\users\path\program"
set "_FILE=userinfo.txt"
rem // Initialise index:
set /A "INDEX=1"
rem // Search for the specified file in the given root directory:
for /F "delims=" %%F in ('dir /B /S "%_ROOT%\%_FILE%"') do (
rem // Iterate once over the grandparent directory itself:
for /D %%D in ("%%F\..\..") do (
rem // Resolve the path of the grantparent directory;
set "ITEM=%%~fD"
rem // Initialise flag (non-empty means not yet stored):
set "FLAG=#"
rem // Toggle delayed expansion to avoid trouble with exclamation marks:
setlocal EnableDelayedExpansion
rem // Iterate over all currently stored grantparent paths:
for /F "tokens=1,* delims==" %%V in ('2^> nul set $ARRAY[') do (
rem // Clear flag in case current grandparent has already been stored:
if /I "!%%V!"=="!ITEM!" set "FLAG="
)
rem // Check flag:
if defined FLAG (
rem // Flag is empty, so current grandparent needs to be stored:
set "$ARRAY[!INDEX!]=!ITEM!"
rem // Transfer stored grandparent over localisation barrier:
for /F "delims=" %%E in ("$ARRAY[!INDEX!]=!ITEM!") do (
endlocal
set "%%E"
)
rem // Increment index
set /A "INDEX+=1"
) else endlocal
)
)
rem // Retrieving final count of grandparent directories:
set /A "INDEX-=1"
rem // Return stored grandparent paths:
set $ARRAY[
endlocal
exit /B
This should return D:\users\path\programs\otherstorage and D:\users\path\programs\storage in your situation, which are stored in the variables $ARRAY[1] and $ARRAY[2], respectively. Due to the array-style variables, this approach is flexible enough to cover also cases where more than two grandparent directories are present.
Based on your above sample this batch
#Echo off
Set Search=\\data\\userinfo.txt
pushd "D:\Users\path\program
For /f "Delims=" %%A in (
'Dir /B/S/A-D userinfo.txt ^|findstr "%Search%$"'
) Do Call :Sub "%%~fA" "%%~dpA.."
Popd
Goto :Eof
:Sub FullName DrivePath
Echo Found %~nx1
Echo in %~dp1
Echo Granny %~nx2
Set "Granny=%~nx2"
Echo in %~dp2
Echo -------
Should give this output (only partially tested)
Found userinfo.txt
in D:\Users\path\program\storage\data\
Granny storage
in D:\Users\path\program\
-------
Found userinfo.txt
in D:\Users\path\program\storage2\data\
Granny storage2
in D:\Users\path\program\
-------
The backslash in Search has to be doubled as it is an escape char for findstr
I have given myself a simple task of writing a batch file that will do the following:
Compare CRC32 of each file in "first" folder with each file in "second" folder. Output needs to be the name of every file from the "first" folder, which doesn't have its duplicate in "second".
Note: "CRC32.exe -nf" outputs CRC32 in first line, and file size in second one.
Here is how I tried to do it:
#echo off
for %%i in ("%cd%\first\*.*") do (
set uniq=0
for /f %%x in ('crc32 %%i -nf') do (
set one=%%x
goto find_two
)
:find_two
for %%j in ("%cd%\second\*.*") do (
for /f %%x in ('crc32 %%j -nf') do (
set two=%%x
goto compare
)
:compare
if one==two (
goto next
) else (
set uniq=1
)
)
if uniq==1 (
echo %%i >>result.txt
)
:next
)
I assume there are several errors present in this code, but I had trouble finding them. So if anyone has time, and thinks he can help, I would be grateful.
If you think different approach is required, feel free to show it.
There are two major problems.
Goto's inside brackt doesn't work as expected, they stop the loop immediately, so your code will simply fail there.
The solution is to avoid goto or to call a function and use the goto there.
The second problem are the compares.
if one==two ( will be always false, as it compare the word one against the word two, but not the content of the variables.
Normally you would use if "%one%"=="%two%" echo equal, but as it should work inside of brackets you need delayed expansion, so it looks like.
if !one!==!two! echo equal
Don't forget to enable the delayed expansion before you start the loop with
setlocal EnableDelayedExpansion
Might be easier to capture the output of crc32 using something like this:
for /f %%x in ('crc32 "%filename%" -nf ^| find /v "CRC32"') do stuff with %%x
Can you show me the full output of an example crc32 if this is not helpful?