Solved

Need help with chkdsk batch file

Posted on 2010-11-30
16
1,319 Views
Last Modified: 2012-06-21
I am attempting to put together a batch file that will run a chkdsk on all local drives on servers in our environment. Once the chkdsk is complete, it should create a log entry in the System log via the eventcreate utility. When I attempt to run the script below, it correctly runs the chkdsks but reports back on every drive letter, even if it wasn't checked.

I am not very experienced with this, so any help would be great. Thanks!
@echo off

for %%x in (C D E Q S T U) do (

if exist %%x:\ chkdsk %%x: /i



if ERRORLEVEL 0 goto ERRORLEVEL 0

if ERRORLEVEL 1 goto ERRORLEVEL 1

if ERRORLEVEL 2 goto ERRORLEVEL 2

if ERRORLEVEL 3 goto ERRORLEVEL 3





:ERRORLEVEL 0

eventcreate  /l SYSTEM /so chkdsktest /t INFORMATION /id 10 /d "No errors were found on %%x."

goto end



:ERRORLEVEL 1

eventcreate  /l SYSTEM /so chkdsktest /t INFORMATION /id 11 /d "Errors were found and fixed on %%x."

goto end



:ERRORLEVEL 2

eventcreate  /l SYSTEM /so chkdsktest /t WARNING /id 12 /d "Performed disk cleanup (such as garbage collection) or did not perform cleanup because /f was not specified on %%x."

goto end



:ERRORLEVEL 3

eventcreate  /l SYSTEM /so chkdsktest /t ERROR /id 13 /d "Could not check the disk, errors could not be fixed, or errors were not fixed because /f was not specified on %%x."

goto end

)

:end

Open in new window

0
Comment
Question by:itgops
  • 6
  • 6
  • 3
  • +1
16 Comments
 
LVL 6

Expert Comment

by:ipajones
ID: 34240839
Your section labels cannot contain spaces so your errorlevel conditional won't work.  Try using ERRORLVL_1 etc.

So...

@echo off
for %%x in (C D E Q S T U) do (

	if exist %%x:\ chkdsk %%x: /i

	if ERRORLEVEL 0 goto ERRORLVL_0
	if ERRORLEVEL 1 goto ERRORLVL_1
	if ERRORLEVEL 2 goto ERRORLVL_2
	if ERRORLEVEL 3 goto ERRORLVL_3


	:ERRORLVL_0
	eventcreate  /l SYSTEM /so chkdsktest /t INFORMATION /id 10 /d "No errors were found on %%x."
	goto end

	:ERRORLVL_1
	eventcreate  /l SYSTEM /so chkdsktest /t INFORMATION /id 11 /d "Errors were found and fixed on %%x."
	goto end

	:ERRORLVL_2
	eventcreate  /l SYSTEM /so chkdsktest /t WARNING /id 12 /d "Performed disk cleanup (such as garbage collection) or did not perform cleanup because /f was not specified on %%x."
	goto end

	:ERRORLVL_3
	eventcreate  /l SYSTEM /so chkdsktest /t ERROR /id 13 /d "Could not check the disk, errors could not be fixed, or errors were not fixed because /f was not specified on %%x."
	goto end

	)

:end

Open in new window


There may also be a better way of doing this by only enumerating the drives which exist, will post something else shortly.

--IJ
0
 
LVL 83

Expert Comment

by:oBdA
ID: 34241307
Apart from the labels with spaces, there are other errors.
Batch is not a programming language; brackets actually do group commands together, but the behavior is different from real programming languages. Don't try to jump into a statement like this, it can have strange effects.
Then your errorlevel query is incorrect.
"if errorlevel x" is true if the errorlevel is x OR HIGHER.
In other words: "errorlevel 0" is always true, so different errorlevels have to be queried "backwards", from the highest to the lowest.
Try the script below; it's currently in test mode and will only echo out the chkdsk command; remove the capitalized ECHO in front of chkdsk to run it for real.

@echo off
for %%x in (C D E Q S T U) do (
  if exist %%x: call :Process %%x
)
goto :eof

:Process
echo Processing %1 ...
ECHO chkdsk %1: /i
if errorlevel 3 goto Errorlevel_3
if errorlevel 2 goto Errorlevel_2
if errorlevel 1 goto Errorlevel_1

eventcreate  /l SYSTEM /so chkdsktest /t INFORMATION /id 10 /d "No errors were found on %%x."
goto :eof

:Errorlevel_1
eventcreate  /l SYSTEM /so chkdsktest /t INFORMATION /id 11 /d "Errors were found and fixed on %%x."
goto :eof

:Errorlevel_2
eventcreate  /l SYSTEM /so chkdsktest /t WARNING /id 12 /d "Performed disk cleanup (such as garbage collection) or did not perform cleanup because /f was not specified on %%x."
goto :eof

:Errorlevel_3
eventcreate  /l SYSTEM /so chkdsktest /t ERROR /id 13 /d "Could not check the disk, errors could not be fixed, or errors were not fixed because /f was not specified on %%x."
goto :eof

Open in new window

0
 
LVL 51

Accepted Solution

by:
Bill Prew earned 250 total points
ID: 34242096
Just as another alternative approach to this, here's a thought.  Would be easier if BAT files supported arrays, but we can improvise a bit...

@echo off
setlocal EnableDelayedExpansion

REM Define event data to log (id;type;description)
set LogInfo0=10;INFORMATION;"No errors were found on [DRIVE]."
set LogInfo1=11;INFORMATION;"Errors were found and fixed on [DRIVE]."
set LogInfo2=12;WARNING;"Performed disk cleanup (such as garbage collection) or did not perform cleanup because /f was not specified on [DRIVE]."
set LogInfo3=13;ERROR;"Could not check the disk, errors could not be fixed, or errors were not fixed because /f was not specified on [DRIVE]."

REM Process required drives, run CHKDSK, log results
for %%D in (C D E Q S T U) do (
  if exist %%D: (
    echo Processing %%D ...
    ECHO chkdsk %%D: /i
    if %ERRORLEVEL% GTR 3 (set Return=!LogInfo3!) else (set Return=!LogInfo%ERRORLEVEL%!)
    set Return=!Return:[DRIVE]=%%D!
    for /F "tokens=1-3 delims=;" %%A in ("!Return!") do (
      ECHO eventcreate  /l SYSTEM /so chkdsktest /t %%B /id %%A /d %%C
    )
  )
)

Open in new window

~bp
0
 
LVL 6

Expert Comment

by:ipajones
ID: 34242118
I've been trying to think of a better way of doing this.  You can certainly make a better job in VBScript or PowerShell but in a batch script its more difficult, somewhat for the reasons oBdA has suggested.  However if you want to get the drive letters from the system you can replace your initial for loop, so you could enhance oBdA's version as follows:

@echo off
setlocal EnableDelayedExpansion
for /f "tokens=* delims= " %%A in ('fsutil fsinfo drives') do (
	set drive_list=%%A
	for %%B in (%drive_list%) do (
		set curr_drive=%%B
		if not %%B == Drives: (
			for /f "tokens=1,2 delims=-" %%C in ('fsutil fsinfo drivetype %%B') do (
				if "%%D" == " Fixed Drive" (
					call :process !curr_drive!
				)
			)
		)
	)
)
goto :eof

:Process
echo Processing %1 ...
ECHO chkdsk %1: /i
if errorlevel 3 goto Errorlevel_3
if errorlevel 2 goto Errorlevel_2
if errorlevel 1 goto Errorlevel_1

eventcreate  /l SYSTEM /so chkdsktest /t INFORMATION /id 10 /d "No errors were found on !curr_drive!."
goto :eof

:Errorlevel_1
eventcreate  /l SYSTEM /so chkdsktest /t INFORMATION /id 11 /d "Errors were found and fixed on !curr_drive!."
goto :eof

:Errorlevel_2
eventcreate  /l SYSTEM /so chkdsktest /t WARNING /id 12 /d "Performed disk cleanup (such as garbage collection) or did not perform cleanup because /f was not specified on !curr_drive!."
goto :eof

:Errorlevel_3
eventcreate  /l SYSTEM /so chkdsktest /t ERROR /id 13 /d "Could not check the disk, errors could not be fixed, or errors were not fixed because /f was not specified on !curr_drive!."
goto :eof

Open in new window


oDbA:  I was already working on this before I saw your post and intended to produce a better working version.  It's difficult to come up with a simple solution to this in a batch file.  I hope you don't mind but I've incorporate my element to enumerate the drive letters into your version of the script.

itgops:  If you don't want to check the drive type you can remove conditional if statement before calling the subprocedure.

--IJ
0
 
LVL 6

Expert Comment

by:ipajones
ID: 34242158
Having just seen billprew's post, you could combine my last post with billprew's script.
0
 

Author Comment

by:itgops
ID: 34243593
Wow... these are all excellent replies. Thank you all for your help.  I had checked here earlier only when oBdA had posted and I had been using his script (had to add .exe for chkdsk to work... that stumbled me for a second).

That seems to be the simplest solution and since I have it already working, I will probably implement this one... but I will be testing the others as well.

@billprew: This is definitely a more elegant solution; I'm going to test it out, but I'm a bit hesitant to use it only because of my lack of knowledge with batch scripting and not sure I want to implement something I do not understand 100% (namely the use of expanded variables, tokens, and delims).

@ipajones: Another great script. That's a great use of fsutil, but I do not want to run a chkdsk against all recognized drives because I'm looking to avoid external media drives (floppy, CD-ROM, etc.) and mapped network drives.

Thanks again... I will revisit this tomorrow.
0
 
LVL 51

Expert Comment

by:Bill Prew
ID: 34243630
==> itgops

No problem, I do understand that sometimes simpler is better, just wanted to share a slightly different approach to help expose you to some different techniques.

You won't hurt my feelings with whatever approach you take, you need to be comfortable with it.

And certainly if you do want to ask any specific questions about things that don't make sense in my script feel free.

~bp
0
 

Author Comment

by:itgops
ID: 34243756
Since you offered... can you explain this part of the code? I don't know how delayed expanded variables work.


if %ERRORLEVEL% GTR 3 (set Return=!LogInfo3!) else (set Return=!LogInfo%ERRORLEVEL%!)

set Return=!Return:[DRIVE]=%%D!

for /F "tokens=1-3 delims=;" %%A in ("!Return!")

Open in new window

0
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 51

Expert Comment

by:Bill Prew
ID: 34244825
Okay, the basic concept of delayed expansion if very simialr to the way normal variables are reference.  Instead of %varname% you use !varname!.

The subtle different though is that rather than replacing the referenced variable with its assigned variable at the time the script interpreter reads the script line for "pre-processing", it delays the variable replacement until each time it executes the statement.

In many cases this yields the same results, but sometimes, especially inside a FOR loop, where the value of a variable may be changed inside the FOR loop, and then the new value needs to be referenced.  The lines you asked about were insode a FOR loop so I used some delayed expansion to reference any variable that was updated inside the FOR loop.  Note though, that to reference variables defined outside the FOR loop and not changed, you can use the normal %varname% format.

A little more info on these pages and a couple of examples that may help.

http://batcheero.blogspot.com/2007/06/how-to-enabledelayedexpansion.html
http://ss64.com/nt/setlocal.html

Okay, let's talk about what I did in these lines below.

if %ERRORLEVEL% GTR 3 (set Return=!LogInfo3!) else (set Return=!LogInfo%ERRORLEVEL%!)

Open in new window

Here we are checking the return from the prior command via the %ERRORLEVEL% variable (which is sot of a special case, you always reference it with % signs and the batch script interpretter knows to always get it's most recently set value).

Since we want to use the same error message for any error greater than or equal to 3 I added a little logic to use LogInfo3 for all ERRORLEVELS greater than or equal to 3.

This done via the basic assignment:

set Return=!LogInfo3!

after which Return will have the value from the LogInfo3 variable assigned to it.  If ERRORLEVEL was less than 3 though, we want to pick the appropriate LogInfo data for that event, and so we want to get either LogInfo0, LogInfo1, or LogInfo2 depending on ERRORLEVEL eing 0, 1 or 2.  So we do that by:

set Return=!LogInfo%ERRORLEVEL%!

Notice that we are "nesting" variable references here.  That is another place where mixing % and ! to reference variables is useful.  First, the interpreter will replace %ERRORLEVEL% with the value from the prior command.  Then it will look for a variable named LogInfoX where X was the ERRORLEVEL value, and assign the value of that variable to the Return variable.

So at this point we have the three values that wee want to pass to the eventcreate command that vary depending on the ERRORLEVEL we got.  The only issue is the three values that we need (id,type,description) are all in a single string in the Result variable, seperated by semicolons.

In addition, we wanted the current drive letter to be inserted into the error messages.  To allow for this I used a place holder in the original messages defined of [DRIVE] that we can now replace with the current drive letter.

So we want to take the current string that is assigned to the Result variable, and just do a replace on the [DRIVE] substring with the current drive letter.  The next statement does this.  It uses the basic SET syntax for substring replacement (%varname:old=new%) which is shown in the SET /? online help.  Basically it get the variable named and then looks for "old" and replaces it with "new".

In our case we are still in the FOR loop so %%D still has the current drive letter being processed.  Since Return was assigned inside the FOR loop, we use delayed expansion to reference it with the ! marks.  We reassign Result to the current value of Result, with [DRIVE] replace by the current value of %%D, the drive letter.

set Return=!Return:[DRIVE]=%%D!

Open in new window

Now we just need to parse appart the three values that are stored in the LofInfo variables, breaking apart on semicolons.  The FOR /F is very handy for parsing text apart, and again a read of FOR /? gives a bit more info on this.

We know there are three values seperated by semicolons we are after, so we use "tokens=1-3" to request the three values.  They are delimited by semicolons so we specifiy "delims=;" as another option on the FOR /F.  We specify the string we want to parse by !Return!, again using delayed expansion since we created and changed Result inside the FOR loop of the drive letters.  When the FOR /F breaks apart the string on the semicolons, it will place the first value in %%A, the second in %%B, and the third in %%C allowing us easy access to them in the eventcreate statement.

for /F "tokens=1-3 delims=;" %%A in ("!Return!")

Open in new window

Hope this helps a bit, let me know what isn't clear.  I'd be lying if I said advanced techniques like the nested variable resolutions take a little trial and error, and there are some good websites like ss64.com, robvanderwoude.com, an dostips.com that can shed some light on advanced (and basic) topics like these.

~bp
0
 
LVL 6

Expert Comment

by:ipajones
ID: 34247343
billprew has given you an excellent summary of delayed variable expansion and you'll notice that the technique is also used in my script above. Hopefully the various posts here have given you some good examples and options as to how you could improve the final script. As BP has stated I'm also happy to answer any queries and I won't be offended by whichever solution you choose to implement.

I just wanted to post again to pickup on something you said:

>>>>>>That's a great use of fsutil, but I do not want to run a chkdsk against all recognized drives because I'm looking to avoid external media drives (floppy, CD-ROM, etc.) and mapped network drives.

One of things that I tried to accomplish with my version was to automatically detect the available drives in the system, without looping through hard coded drive letters.  Perhaps you didn't pickup on this but these lines in my script:

for /f "tokens=1,2 delims=-" %%C in ('fsutil fsinfo drivetype %%B') do (
	if "%%D" == " Fixed Drive" (
		call :process !curr_drive!
	)
)

Open in new window


are checking that the drive type is a fixed hard drive before performing the chkdsk command.  You can adapt this to whichever drive type you want to run chkdsk against.  The script checks each drive letter using the "fsutil fsinfo drivetype 'drive_letter'" command and could be adapted to process whichever drive type you wish.  Have a look at the results using the "fsutil fsinfo drivetype" command against different drives in your system.

Lastly, I think if you were to combine billprew's approach to handling the error control with utilising my part for finding installed drives you'd have a pretty good working script.

Good luck!
--IJ

0
 
LVL 51

Expert Comment

by:Bill Prew
ID: 34247567
Another approach which I have used, if you just want to process fixed disks on the system running the script (skipping CDs, network connects, etc) would be to replace:

for %%D in (C D E Q S T U) do (

with

for /F "skip=2 tokens=2 delims=," %%D in ('wmic logicaldisk where drivetype^=3 get drivetype^,deviceid /format:csv') do (

~bp
0
 
LVL 6

Expert Comment

by:ipajones
ID: 34247685
billprew:  I looked at using 'wmic' but couldn't get the command to work in a for loop.  The output from wmic is better and easier to use than 'fsutil fsinfo' as each drive is on a diiferent line of output.  I notice you've used '^' to escape some characters in the 'parsed command' - perhaps you could explain how/when it's necessary to escape characters when parsing commands to a for loop ?

--IJ
0
 
LVL 51

Expert Comment

by:Bill Prew
ID: 34251476
Boy, that's a tough one.  I don't think I ever really found a single definitive discussion on this topic.  Some of it sort of makes sense, like if the command you are trying to execute is:

DIR *.TXT | SORT

(okay, not the greatest example, but bear with me).  That is the command you might enter a command line.  But in a batch file, if you want to wrap that in a FOR /F, then it would become:

FOR /F %%A in ('DIR *.TXT | SORT) DO (...)

But the pipe symbol (|) is a special character on a line of a batch file, and typically means send the output of one command on to the next.  We don't really want that to apply to the FOR command though, we want it to apply to the DIR command, inside the command we want executed first, before the FOR executes on it's output.  As a result we have to escape the pipe with a caret so that it gets passed on when the command inside the parens is executed as part of the FOR command, not when the line is parsed.  Sorry, not a great explanation.  Some other obvious characters you might guess could cause trouble are things like >, >>, < and <<.  In addition I think =, comma, (, and ) can cause problems.  For me it often is a bit of trial and error to get things working properly

A few resources on this:

http://www.dostips.com/?t=Snippets.Escape
http://www.experts-exchange.com/Programming/Languages/Scripting/Shell/Batch/A_673-Advanced-DOS-batch-pitfalls.html
http://www.robvanderwoude.com/escapechars.php
http://www.lingubender.com/forum/viewtopic.php?f=12&t=615

In addition, a lot of times you can get around special characters by using double quotes, in FOR commands and other places.  For example:

for /F %A in ('dir /B *.pdf|sort') do @echo %A

at a command line (not in BAT) will fail on the pipe.  But either of these adjustments will get around that:

for /F %A in ('dir /B *.pdf^|sort') do @echo %A
for /F %A in ('"dir /B *.pdf|sort"') do @echo %A

~bp
0
 

Author Comment

by:itgops
ID: 34251559
A big thank you to all of you. I'm currently working a couple of these solutions. I am out of the office until early next week after today, but I will come back and accept a solution(s) once I have one implemented.

You guys rock!
0
 
LVL 6

Expert Comment

by:ipajones
ID: 34252090
Billprew: many thanks for the info.
--IJ
0
 
LVL 51

Expert Comment

by:Bill Prew
ID: 34254422
Glad to help.

~bp
0

Featured Post

What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

Join & Write a Comment

One of my most closely kept secrets is revealed in this discussion How to output text on the same line This question was recently posted in EE by Simon336697 (http://www.experts-exchange.com/Programming/Languages/Scripting/Shell/Batch/Q_2459…
I have published numerous articles here at Experts Exchange that present programs/scripts written in a language called AutoHotkey. Each of those articles has a brief paragraph describing where to download the product and how to install it. I have al…
This video gives you a great overview about bandwidth monitoring with SNMP and WMI with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're looking for how to monitor bandwidth using netflow or packet s…
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …

757 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

20 Experts available now in Live!

Get 1:1 Help Now