Commands in batch script not working - batch-file

I made the following script:
SET localfolder="%~dp0"
mkdir %localfolder%
SET logger="%~dp0"\%~n0.log
echo %date% %time% logger=%logger% >> %logger%
set "list=a b c"
set disk=""
FOR %%i IN (%list%) DO (
SET disk=%%i: & call :chk
)
:chk
cd C:\windows\system32
%disk% >> %logger%
chkdsk %disk% >> %logger%
Running this script file results in the following output:
22.12.2014 11:38:36,82 logger="W:\Scripts\"\chkdsk_all.log
W:\Scripts>(SET disk=b: & call :chk )
W:\Scripts>cd C:\windows\system32
W:\Scripts>b: 1>>"W:\Scripts\"\chkdsk_all.log
W:\Scripts>chkdsk b: 1>>"W:\Scripts\"\chkdsk_all.log
W:\Scripts>(SET disk=c: & call :chk )
W:\Scripts>cd C:\windows\system32
W:\Scripts>c: 1>>"W:\Scripts\"\chkdsk_all.log
W:\Scripts>chkdsk c: 1>>"W:\Scripts\"\chkdsk_all.log
I don't understand why it doesn't work, what am I missing?

I've got a few improvements to suggest.
%~dp0 is the drive and path of the directory containing the running script. I'm puzzled about why you are trying to mkdir %~dp0 when it is impossible for that directory not to exist already. Are you trying to create a directory with the same name as the script? If so, try md %~dpn0 instead, and set "logger=%~dpn0\%~n0.log".
Rather than supplying a static list of drive letters in a for loop, why not ask WMI to supply the list? wmic logicaldisk where "DriveType=3" will get you a list of the non-removable, non-network drives in your system.
Why are you doing cd %windir%\system32? What ability do you gain by changing to that directory that you don't already have from the script's running directory? I don't think you need it.
You know when you chkdsk c: you get an error saying the volume can't be checked while mounted? I suggest adding chkdsk /x to dismount drives first. In the case of the C: drive, you'll probably get "Chkdsk cannot run because the volume is in use by another process. Would you like to schedule this volume to be checked the next time the system restarts? (Y/N)". You can go ahead and supply the Y ahead of time by piping and echo Y| chkdsk /x %drive%.
So here's what I suggest:
#echo off
setlocal
set "logfile=%~n0.log"
for /f "tokens=2 delims==:" %%I in ('wmic logicaldisk where "DriveType=3" get DeviceID /format:list ^| find "="') do (
>>"%logfile%" (
echo Checking "%%I:"
echo y| chkdsk /x %%I:
)
)

It works after I changed "cd C:" to "cd /D C:"

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

Need a little tip on batch files

I'm very new to coding and iI'm having a problem that is probably trivial, but is making me pull out my hair.
I'm using a batch script to automate mounting a VHD, executing a file inside and then pause until the user presses any key, which makes the VHD get unmounted and the script exits.
This is the main batch file:
#echo off
set fileVHD=Gord
CD /D "%~dp0"
powershell -command "Start-Process mount.cmd '%~dp0%fileVHD%.vhd' -Verb runas"
timeout /t 1
for /f %%D in ('wmic volume get DriveLetter^, Label ^| find "%fileVHD%"') do set usb=%%D
CD /D %usb%
index.html
echo "!!!!!!!!!!!!!!!!!!!!Press any key to fully close this program.!!!!!!!!!!!!!!!!!!!!!!!!!"
pause
CD /D "%~dp0"
powershell -command "Start-Process unmount.cmd '%~dp0%fileVHD%.vhd' -Verb runas"
exit
This is the mount script (Not made by me):
#echo off
setlocal enabledelayedexpansion
if "%~1"=="" (
echo Usage: %~nx0 [vhd] [letter]
exit /b 1
)
set "vhdPath=%~dpnx1"
set "driveLetter=%2"
if "!driveLetter!"=="" (
echo Mounting "!vhdPath!"
) else (
echo Mounting "!vhdPath!" to "!driveLetter!":
)
REM
REM create diskpart script
REM
set "diskPartScript=%~nx0.diskpart"
echo select vdisk file="!vhdPath!">"!diskPartScript!"
echo attach vdisk>>"!diskPartScript!"
REM assign the drive letter if requested
if not "!driveLetter!"=="" (
echo select partition 1 >>"!diskPartScript!"
echo assign letter="!driveLetter!">>"!diskPartScript!"
)
REM Show script
echo.
echo Running diskpart script:
type "!diskPartScript!"
REM
REM diskpart
REM
diskpart /s "!diskPartScript!"
del /q "!diskPartScript!"
echo Done!
endlocal
When all the files are located in a system path that contains no spaces, everything works fine. But it breaks where there are spaces.
That means that somewhere in the code a path is badly defined by the lack of quotes, probably in the mount script. The trouble is that i don't fully grasp the mount script when it starts using all the "%~...." variable path names.
I had to mix in some powershell commands because for some reason the script wouldn't work unless executed as Administrator.
If someone could give some insight to a newbie, it would be greatly appreciated.
You need end quotes around your parameters when you change directory, i.e.
CD /D "%~dp0"
You can also see all of the %~ options by running 'help for' in a console window. In those scripts it's getting the path or filename from a variable.
Discovered the root of my problem.
The path from script 1 was not being passed faithfully to script 2, even using using quotes or multiquotes.
Thanks for all the input guys!

Storing command results in a variable in batch file

I need to search a remote directory (psexec), then store the result in a variable.
I have a directory that is filled with many other folders each containing some files I need to execute. Each computer on the network has the files I need are in different directories.
For example, I would search (using psexec) with
dir *Google_Chrome_49_0_2623_112_X64_EN_R01* /s
And I would get the result
Volume in drive C is OSDISK
Volume Serial Number is C8AD-F658
Directory of C:\Windows\ccmcache\2a
12/04/2016 01:52 PM 458 Google_Chrome_49_0_2623_112_X64_EN_R01.cab
14/04/2016 05:26 PM 69,632 Google_Chrome_49_0_2623_112_X64_EN_R01.mst
2 File(s) 70,090 bytes
Total Files Listed:
2 File(s) 70,090 bytes
0 Dir(s) 4,726,218,752 bytes free
Now I need the same batch file to cd into the directory 2a and execute a script inside.
Is there any way to store '2a' in a variable so I can cd into it?
Currently I am doing this in 2 steps, where the batch will show me the search results, then prompt for the directory:
psexec \\%PCNAME% cmd /c (cd ..\ccmcache ^& dir *Google_Chrome_49_0_2623_112_X64_EN_R01* /s)
SET DIRECTORY=%1
SET /P DIRECTORY=Enter Directory:
Problem is, I need this to be fully automated. How can I do this?
Note: I've looked at several other threads, and none of the answers seem to help me. Seemingly the results from psexec don't flow back to the local interface.
I've tried:
for /f %%a in (psexec \\%PCNAME% cmd /c (cd ..\ccmcache ^& dir *Google_Chrome_49_0_2623_112* /s ^| FIND "Directory of C:\Windows\ccmcache\") DO (
SET v=%%a
)
to try to set the entire result line to a var, but when I check the var, it's empty.
I just discovered that you can map to a network share with pushd and run the search there instead of dealing with psexec until you need it.
So my code now looks like:
pushd \\%PCNAME%\C$\Windows\ccmcache
for /f "tokens=3" %%a in ('dir *Google_Chrome_49_0_2623_112* /s ^| FIND "Directory"') DO (
set v=%%a
for /f "tokens=4 delims=\" %%m in ('echo !v!') do (set x=%%m)
)
popd
Where %x% is now the directory I'm searching for.

How to extract error descriptions from a batch file

I have this batch script:
#echo off
set server_list=C:\temp\servers.txt
set cab=E:\PHC\wsusscn2.cab
set sass=E:\PHC\SASS\sass.exe
erase c:\sass\error.log
for /f "tokens=1,2*" %%i in (%server_list%) do (
echo %%i
xcopy %cab% \\%%j\c$\sass /Y || echo %%i: ?error description (invalid drive specification, Access denied etc)? >> c:\sass\error.log
xcopy %sass% \\%%j\c$\sass /Y || echo %%i: ?error description (invalid drive specification, Access denied etc)? >> c:\sass\error.log
wmic /node:%%j PROCESS CALL CREATE "cmd /c c:\sass\sass.exe -i c:\sass\wsusscn2.cab -o c:\sass\%%i.csv" || echo %%i: ?error description (RPC server not available etc)? >> c:\sass\error.log
)
pause`
The script works fine, problem is, I can't get the error descriptions to the error.log file (between the question marks). It would help greatly, if I knew, why exactly the operation was unsuccessful. I tried the following:
echo %%i: cab file copy failed - %ERRORLEVEL%
But it always results in errorlevel 0 (obviously errorlevel of the echo command). I tried several ways to store the errorlevel to a variable, but always unsuccessfully. Furthermore, errorlevel only gives me partially what I want, not the descriptions, but just type of error.
The file Servers.txt contains hostnames of servers in one column, and their IPs in the second column.
How can I extract the error descriptions?
Problem was solved by using STDERR. This is working form of the questioned lines:
xcopy %cab% \\%%j\c$\sass /Y 2>> C:\SASS\error.log
xcopy %sass% \\%%j\c$\sass /Y 2>> C:\SASS\error.log
wmic /node:%%j PROCESS CALL CREATE "cmd /c c:\sass\sass.exe -i c:\sass\wsusscn2.cab -o c:\sass\%%i.csv" 2>> C:\SASS\error.log
This code types precisely what I need to the error.log file. Since this solution does not state the hostname of the server, I had to add these lines to the beginning of the loop:
echo ----------- >> C:\SASS\error.log
echo %%i >> C:\SASS\error.log

Resources