Defragmenting Disks the Windows 2003 Way

Let’s pretend that you aren’t feeling well and so you go to the doctor. The doctor conducts an examination and then says, “OK, I know exactly what the problem is. You just need to take two Cillosybarexiums each morning and you’ll be fine.”

“Wow, that’s great,” you say. “Where do I get Cillosybarexium?”

“Well, you can’t,“ says the doctor. “Cillosybarexium hasn’t been invented yet.”

Gee. Thanks.

Now, would Microsoft ever do anything like that to you? Of course not. Well, not that bad anyway. Depending on how you look at it, however, we did do something roughly analogous when we added the Disk Defragmenter to Windows 2000. When Windows 2000 was released we encouraged customers to make use of the Disk Defragmenter, especially when it came to managing servers. And for good reason: you can often-times dramatically increase the performance and reliability of a computer just by ensuring that the hard disks are defragged.

Needless to say, people were thrilled to find a disk defragmenter built right into the operating system. “Wow, that’s great,” you said. “Of course, I don’t actually keep my servers in my office; how do I programmatically check the defragmentation status of a remote computer?”

“Well you can’t,” we said. “That technology hasn’t been invented yet.”

You know, you don’t look so good. Maybe you should take two Cillosybarexiums and finish reading this article in the morning.

*
On This Page
Help Has ArrivedHelp Has Arrived
Running a Defragmentation Analysis ReportRunning a Defragmentation Analysis Report
Defragging a Computer Using the Win32_Volume ClassDefragging a Computer Using the Win32_Volume Class
Better Late Than Never, Right?Better Late Than Never, Right?

Help Has Arrived

Listen, don’t despair: we have some good news for you. No, it has nothing to do with Cillosybarexium; this news is much better. In Windows Server 2003, you can use WMI to check the defragmentation status on remote computers; in fact, you can even use WMI to defragment those remote machines. Who needs Cillosybarexium when you have WMI?

Note. We should point out that Cillosybarexium is something we just made up; it doesn’t really exist. Furthermore, the Scripting Guys do not advocate the use of drugs unless prescribed by a licensed physician. And, yes, that includes Peter: believe it or not, he was not on drugs at any time when he sang during his webcasts.

Good point: maybe we should put him on some kind of drug….

So what is it in Windows Server 2003 that makes it so special, at least when it comes to disk defragmenting? Well, in Windows Server 2003 the Win32_Volume class gained two new methods—DefragAnalysis and Defrag—that replicate the functionality of the Disk Defragmenter utility. These were two very nice features to add to the operating system, but keep in mind that these methods can be used only to run a report on or to defragment a Windows Server 2003 computer. You can sit at your Windows XP workstation, connect to a Windows 2003 machine and then defragment that Windows 2003 computer. However, you can’t do the opposite; you can’t sit at the Windows 2003 machine and defragment a Windows XP or Windows 2000 computer. WMI just doesn’t work that way.

But as long as you have Windows Server 2003 computers you can run defragmentation reports and defrag hard disks remotely and programmatically. Let’s take a closer look at what’s involved in carrying out each of these tasks.

Top of pageTop of page

Running a Defragmentation Analysis Report

As you probably guessed, the DefragAnalysis method is used to run a defragmentation analysis report. This is actually an interesting little method, or at least as interesting as any WMI method can be. DefragAnalysis returns two “out” parameters: a Boolean value (True or False) indicating whether a volume should be defragged, and an embedded instance of the Win32_DefragAnalysis class. This embedded class contains the same properties (and values) that you find when you run a defrag analysis report in the Disk Defragmenter utility. For example, this graphic maps a few of the Win32_DefragAnalysis properties to some of the same properties and values found in the Disk Defragmenter Analysis Report:

Disk Defragmenter Report


For a full-size version of this graphic, click here.

As a matter of fact, the Win32_DefragAnalysis class includes all the properties found in the following table, which happen to correspond very nicely to all the properties found in the Disk Defragmenter Analysis Report:

Property

Description

AverageFileSize

Average size of the files on a volume, in bytes.

AverageFragmentsPerFile

Average number of fragments per file on a volume.

ClusterSize

Size of the file system allocation unit, in bytes.

ExcessFolderFragments

Number of excess folder fragments on a volume.

FilePercentFragmentation

Percentage of files on a volume that are fragmented.

FragmentedFolders

Number of fragmented folders (sub-directories) on a volume.

FreeSpace

Number of bytes currently available for use on a volume.

FreeSpacePercent

Percent of volume that is free space.

FreeSpacePercentFragmentation

Percentage of free space on a volume that is fragmented.

MFTPercentInUse

Percentage of the Master File Table that is in use.

MFTRecordCount

Number of records in the Master File Table on a volume.

PageFileSize

Size of a page file on a volume, in bytes. If there is no page file on a volume, this property is NULL.

TotalExcessFragments

Number of excess file fragments on a volume.

TotalFiles

Number of files on a volume.

TotalFolders

Number of folders (sub-directories) on a volume.

TotalFragmentedFiles

Number of fragmented files on a volume.

TotalMFTFragments

Number of Master File Table fragments on a volume.

TotalMFTSize

Size of the Master File Table on a volume, in bytes.

TotalPageFileFragments

Number of fragments for a page file. If there is no page file on a volume, this property is NULL.

TotalPercentFragmentation

Percent of volume that is fragmented.

UsedSpace

Number of bytes currently used on a volume.

VolumeName

Name of the volume for which this report is generated. This property can be the drive letter, mount point or the volume GUID name.

VolumeSize

Total size of a volume, in bytes.

As we noted, Win32_DefragAnalysis is a somewhat unusual class: for one thing, you can’t directly query the class. This code will not return any data:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery("Select * from Win32_DefragAnalysis")

Instead of working directly with Win32_DefragAnalysis you use ExecQuery to retrieve a collection from the Win32_Volume class. For each item (volume) in that collection you then call the DefragAnalysis method, which returns an instance of the Win32_DefragAnalysis class. In other words, you use code similar to this:

errResult = objVolume.DefragAnalysis(blnRecommended, objReport)

DefragAnalysis will return three things to you. First, you’ll get back the standard return code; in this script we stash the return code in the variable errResult. This tells us whether or not the method succeeded. If errResult is 0, DefragAnalysis succeeded; if errResult is anything but 0 then the method failed and a report could not be generated.

In addition to the standard return code, DefragAnalysis also returns two out parameters. (In case you’re wondering, an out parameter is any parameter a method brings back to you. By contrast, an “in” parameter is any parameter you supply to the method; for example, a file name passed to a Copy method or a Delete method is an example of an in parameter.) The first out parameter is a Boolean value that returns True if the volume needs to be defragged and False if the volume does not need to be defragged; in our script we store the value of this out parameter in the variable blnRecommended. (When dealing with out parameters you supply the variable names, and you can use any valid variable name you want. We could just as easily have named this variable x, y, or z.)

In addition, an instance of the Win32_DefragAnalysis class is returned as our second out parameter; in our script we use an object reference named objReport to refer to this instance. This is important to know because, when reporting back property values, we need to use this object reference; that’s because the defrag analysis properties—such as FilePercentFragmentation—belong to the Win32_DefragAnalysis class rather than Win32_Volume. Because of that we’ll have lines of code similar to this:

Wscript.Echo "File fragmentation: " & objReport.FilePercentFragmentation

You know, you’re right: this might be a little clearer if we show you the script. Here’s a sample script that returns a defrag analysis report for all the volumes on a computer. In this script we connect to the Win32_Volume class and then call the DefragAnalysis method for each volume found in the collection. We then check the return value; if it’s 0 that means no error occurred and we then go through additional code to list the information found in the analysis report. If the return code is something other than 0 we simply note that an error occurred.

Assuming that DefragAnalysis completed successfully we check the value of blnRecommended and indicate whether or not the volume needs to be defragged. We then report back all the individual property values for the Win32_DefragAnalysis class. And then we loop around and do the same thing for any other volumes in the collection.

Here’s what the script looks like:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colVolumes = objWMIService.ExecQuery("Select * from Win32_Volume")

For Each objVolume in colVolumes
    errResult = objVolume.DefragAnalysis(blnRecommended, objReport)
    If errResult = 0 Then
        Wscript.Echo "Volume name: " & objVolume.Name
        If blnRecommended Then
           Wscript.Echo "You should defragment this volume."
        Else
           Wscript.Echo "You do not need to defragment this volume."
        End If
        Wscript.Echo

        Wscript.Echo "Volume size: " & objReport.VolumeSize  
        Wscript.Echo "Cluster size: " & objReport.ClusterSize
        Wscript.Echo "Used space: " & objReport.UsedSpace
        Wscript.Echo "Free space: " & objReport.FreeSpace
        Wscript.Echo "Percent free space: " & objReport.FreeSpacePercent
        Wscript.Echo

        Wscript.Echo "Total fragmentation: " & _
            objReport.TotalPercentFragmentation
        Wscript.Echo "File fragmentation: " & _
            objReport.FilePercentFragmentation
        Wscript.Echo "Free space fragmentation: " & _
            objReport.FreeSpacePercentFragmentation
        Wscript.Echo

        Wscript.Echo "Total files: " & objReport.TotalFiles
        Wscript.Echo "Average file size: " & objReport.AverageFileSize
        Wscript.Echo "Total fragmented files: " & _
            objReport.TotalFragmentedFiles
        Wscript.Echo "Total excess fragments: " & _
            objReport.TotalExcessFragments
        Wscript.Echo "Average fragments per file: " & _
            objReport.AverageFragmentsPerFile
        Wscript.Echo

        Wscript.Echo "Page file size: " & objReport.PageFileSize
        Wscript.Echo "Total fragments: " & _
            objReport.TotalPageFileFragments
        Wscript.Echo

        Wscript.Echo "Total folders: " & objReport.TotalFolders
        Wscript.Echo "Fragmented folders: " & objReport.FragmentedFolders
        Wscript.Echo "Excess folder fragments: " & _
            objReport.ExcessFolderFragments
        Wscript.Echo

        Wscript.Echo "Total MFT size: " & objReport.TotalMFTSize
        Wscript.Echo "MFT record count: " & objReport.MFTRecordCount
        Wscript.Echo "Percent MFT in use: " & objReport.MFTPercentInUse
        Wscript.Echo "Total MFT fragments: " & objReport.TotalMFTFragments
        Wscript.Echo     

    Else
        Wscript.Echo objVolume.Name & " could not be analyzed."
        Wscript.Echo "Error number " & errResult & " occurred."
        Wscript.Echo
    End If
Next

See? It’s a bit unusual; after all, very few WMI scripts return another WMI class as an out parameter. If you take a closer look however, you’ll see that the code is really no more complicated than the code used in any other WMI script.

Now that’s pretty cool; after all, it’s extremely useful to be able to programmatically generate a defrag analysis. But what happens if the analysis says we need to defrag a volume? How do we actually defrag a drive using a script? Can we defrag a drive using a script?

We thought you’d never ask.

Top of pageTop of page

Defragging a Computer Using the Win32_Volume Class

It’s actually pretty easy to defrag a volume using WMI: all you have to do is connect to the volume in question and then call the Defrag method. For example, here’s a script that defrags volume C: on a computer and then reports back the success (or failure) of the operation:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colVolumes = objWMIService.ExecQuery _
    ("Select * from Win32_Volume Where Name = 'C:\\'")

For Each objVolume in colVolumes
    Wscript.Echo "Please wait while drive " & objVolume.Name & " is defragged."
    Wscript.Echo
    errResult = objVolume.Defrag()

    If errResult = 0 Then
        Wscript.Echo "Drive " & objVolume.Name & " successfully defragged."
    Else
        Wscript.Echo "Drive " & objVolume.Name & " could not be defragged."
        Wscript.Echo "Error number " & errResult & " occurred."
        Err.Clear
    End If
   
Next

This is actually a very simple little script; in fact, if we took out the error handling it would be only a few lines long. We begin by connecting to the WMI service on the local computer (though we could just as easily connect to—and thus defrag—a remote computer). After the connection is made we bind to drive C on the computer using this line of code:

Set colVolumes = objWMIService.ExecQuery _
    ("Select * from Win32_Volume Where Name = 'C:\\'")

And no, that’s not a typo: the value for the Name property really must be passed as C:\\. That’s because the \ is a reserved character in WMI; that means we have to “escape” it (by using a second \) any time that character appears in a Where clause.

Note: What if we wanted to defrag all the volumes on the computer? No problem; all we have to do is remove the Where clause that limits the returned data to drive C:

Set colVolumes = objWMIService.ExecQuery("Select * from Win32_Volume")

As usual, the ExecQuery method returns a collection of drives meeting our query criteria. In this sample script, that means all the volumes with the Name C:\\. We then loop through the collection of volumes and do the following:

Echo a message stating that the drive is being defragged…and asking the user to be patient. Depending on the size of the drive and the amount of defragmentation required, it can easily take an hour or more to defrag a disk drive. However, defragging a drive using a script takes no longer than defragging a drive using the Disk Defragmenter utility. In fact, WMI actually uses the same defragmenting engine used by the Disk Defragmenter utility.

Call the Defrag method to kick off the defragmenting process on the volume.

When defragmenting is complete check the return value (errResult) to determine whether or not the disk was successfully defragged. If errResult equals 0 that means no error occurred and we can assume the disk was successfully defragged. If errResult is anything but 0, something went wrong; thus we echo both a message to that effect as well as the return value itself.

You’re right: it’s useful to know that the defragmenting process failed, but what good does it do us to have a number like 4 echoed back to the screen? Actually it does us quite a bit of good, as long as you know what these return values mean:

Return Code

Description

0

Success

1

Access denied

2

Not supported

3

Volume dirty bit is set

4

Not enough free space

5

Corrupt Master File Table detected

6

Call cancelled

7

Call cancellation request too late

8

Defrag engine is already running

9

Unable to connect to defrag engine

10

Defrag engine error

11

Unknown error

As you can see, if our script fails and we get back a return value of 4 that means there is not enough free disk space to defrag the volume.

Important note. Take a look at return value 8: as that implies, this script will fail if the Defrag engine is already running (either because it was started from another script, from the command line, or from the GUI). If you want to get fancy you could easily add a script that checks for the presence of the Dfrgntfs.exe process:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colProcesses = objWMIService.ExecQuery _
    ("Select * from Win32_Process Where Name = ' Dfrgntfs.exe'")

If colProcesses.Count = 0 Then
    Wscript.Echo " Dfrgntfs.exe is not running."
Else
    Wscript.Echo " Dfrgntfs.exe is running."
End If
Top of pageTop of page

Better Late Than Never, Right?

We’ll admit it: maybe we took a little longer to add automated disk defragmenting to Windows than we should have. (Incidentally, there’s also a command line tool—Defrag.exe—that can be used to defragment volumes in Windows Server 2003. However, this tool works only on the local computer.) But now that this capability has been added to Windows there’s no reason not to use it. And now that you can defragment disks remotely and programmatically, well, there’s no reason to use Cillosybarexium either. Throw that stuff away!

Or send it to Peter right before his next webcast.


Top of pageTop of page