Hey, Scripting Guy! Event 8 *Solutions* from Expert Commentators (Beginner and Advanced; the pole vault)

  

(Note: These solutions were written for Event 8.) 

Beginner Event 8: The pole vault

In the pole vault event, you will raise the bar as you run down the folder that is consuming the most disk space on your computer.

Guest commentator: Michael Frommhold

Image of guest commentator Michael Frommhold 

Michael is a premier field engineer at Microsoft Germany.


VBScript solution

Archimedes gave the hint; Dr. Watson was supposed to do the job. Finding the folder consuming the most disk space should be easy said Dr. Watson. You connect to the drive that contains the folder you wish to scan by using the GetFolder method from the FileSystemObject. Next you use a query to obtain the size of the subfolders by using the subfolders property. At this point you can also obtain the size of the folder from the size property of the folder object.

Store the path to the largest folder in a variable named sWinner and the amount of disk space in the dbFileSize variable. Now you compare each new folder size with the one who claimed to be the largest one up to now. When enumerated all folders, convert the size from bytes to a readable number. This is done via Sub Handlesize. Our first attempt at solving the Beginner Event 8 is BeginnerEvent8Solution_1.vbs script, which is seen here.

BeginnerEvent8Solution_1.vbs

On Error Resume Next

Dim oFSO 
Dim oFolder
Dim oSubFolder
Dim sPath
Dim dbFileSize
Dim dbCtrl
Dim sWinner
Dim sUnit

dbFileSize = CDbl(0)

Set oFSO = CreateObject(“Scripting.FileSystemObject”)
Set oFolder = oFSO.GetFolder(“”)

For Each oSubFolder In oFolder.SubFolders
          sPath = vbNullString
          sPath =  oSubFolder.Path
         
          If Not Len(sPath) = 0 Then
                   dbCtrl = CDbl(0)
                   dbCtrl = CDbl(oSubFolder.Size)
                  
                   If dbCtrl > dbFileSize Then
                             dbFileSize = dbCtrl
                             sWinner = sPath
                   End If
          End If
Next ‘oSubFolder
         
Set oFolder = Nothing
         
HandleSize dbFileSize, sUnit

WScript.Echo “Archimedes says: Lever thrown!”
WScript.Echo “The folder consuming the largest amount of disk space is:”
WScript.Echo vbTab & sWinner & ” : ” & dbFileSize & ” ” & sUnit

Sub HandleSize(ByRef dbRet, ByRef sRet)
         
          On Error Resume Next
         
          If CDbl(dbRet / 1024) > 1 Then
                   dbRet = CDbl(dbRet / 1024)
                   sRet = “KB”
          End If
                  
                   If CDbl(dbRet / 1024) > 1 Then
                   dbRet = CDbl(dbRet / 1024)
                   sRet = “MB”
          End If
         
          If CDbl(dbRet / 1024) > 1 Then
                   dbRet = CDbl(dbRet / 1024)
                   sRet = “GB”
          End If

          ‘just for completeness
          If CDbl(dbRet / 1024) > 1 Then
                   dbRet = CDbl(dbRet / 1024)
                   sRet = “TB”
          End If
         
          dbRet = Round(dbRet, 1)
         
End Sub

When the BeginnerEvent8Solution_1.vbs script is run, the output displayed in the following image is shown (keep in mind the red background seen here is a product of my own personal laptop configuration, and not a result of anything in the script):

Image of the output of the script 

There are a few things that could be improved in the BeginnerEvent8Solution_1.vbs script.

If you do not have access to any subfolder of the first-level subfolders, you will not get any value for the folder size, even if you would have access after agreeing to a UAC dialogue. This is because VBScript is not UAC aware.

If there are any junction points or symbolic links on your drive, you will scan these as well. This could result in a larger size that is actually larger than the total disk space of your drive.

So, what’s the proper solution? Consulting Archimedes, we find that he tells us to find a sufficiently large lever. When working with VBScript perhaps the biggest lever we have is WMI. We query the Win32_Directory class from the rootcimV2 namespace, and handle the returned directories. The Win32_Directory class has a property called FileSize but unfortunately the FileSize property is always null. The reason is that a directory is an entrypoint for a collection of files and directories and this entrypoint has no size.

We will need to use another WMI class, the CIM_DataFile, which can be associated with a Win32_Directory object to obtain the FileSize for files. Now all we have to do is add all FileSize values in a directory and we have the size of the directory.  We also need to add the queried FileSize of any folder to his parent and to the parent of his parent and…you get the point.


To do this we can use a dictionary object with pairs of folder paths and folder sizes. For every new folder we process, the dictionary will be checked for parents and grandparents and so on.  The size will be added to the right side of the pair of the elders. As an add-on to the script, we save the pairs in another dictionary as well, but this time we only save the amount of used space in the directories root.

When we are finished with enumerating the drive, the pair with the highest number will be the folder consuming the most disk space.

Be sure to start the script in an administrative console. The drive to be scanned has to be passed as argument /disk to the script. You can optionally scan a remote machine (but due to performance considerations I wouldn’t do it). Verbose output shows you that each folder scanned each and every updated dictionary pair. It is therefore very verbose.

You will find further information about the details of the script inline as comments. The complete BeginnerEvent8Solution_2.vbs script is seen here.

BeginnerEvent8Solution_2.vbs

‘=====================================================================
‘ Usage: cscript lever.vbs /disk:<disk to scan>
                                                                  {Optional /comp:<targetmachine> /dbg:True}
‘=====================================================================

‘handle errors when you need to
On Error Resume Next

‘_____________________
‘ #region declarations

‘constants
Const SUCCESS = 0
Const ERROR_WMI = 1
Const ERROR_WQL_DIR = 2
Const ERROR_DISK_ACCESS = 3
Const ERROR_NO_DRIVE = 4

Const WbemAuthenticationLevelPktPrivacy = &h6

‘WMI stuff
Dim oWMILoc                  ‘As SwbemLocator
Dim oWMI           ‘As SwbemService

‘store arguments
Dim sMachine       ‘As String
Dim sDrive                   ‘As String
Dim blOutput       ‘As Boolean

‘misc
Dim aDrives                  ‘As String()
Dim iCount                   ‘As Integer

Dim dcOverAll      ‘As Scripting.Dictionary
Dim dcList                   ‘As Scripting.Dictionary

Dim sMSG           ‘As String

Dim sOverall       ‘As String
Dim sList          ‘As String

‘ #endregion

‘_____________
‘ #region Main

‘we’re starting
WScript.Echo Now
WScript.Echo “Throwing the lever” & VbCrLf

‘init return code
sMSG = vbNullString

‘init dictionaries
Set dcOverAll = CreateObject(“Scripting.Dictionary”)
Set dcList = CreateObject(“Scripting.Dictionary”)

‘_________________________
‘anything to take care of?
ReadArguments sMachine, sDrive, blOutput

‘____________________
‘check WMI connection
If Not ConnectWMI(sMachine) Then _
          HastaLaVistaDotCom ERROR_WMI, _
                                                          “Failed to connect WMI!”

‘____________________________
‘told me which drive to scan?
If Len(sDrive) = 0 Then _
          HastaLaVistaDotCom ERROR_NO_DRIVE, _
                                                                   “No drive to scan given!”
         
‘drive accessable?
If Not CheckAccess(sDrive) Then _
          HastaLaVistaDotCom ERROR_DISK_ACCESS, _
                                                                   “Access to ” & sDrive & ” is not granted!”
         
‘__________
‘scan drive
If Not WalkDirectories(sDrive, dcOverAll, dcList, sMSG) Then _
          HastaLaVistaDotCom ERROR_WQL_DIR, _
                                                          “Failed to query for directories: ” & sMSG

‘_______________
‘process results
TheWinnerIS dcOverAll, dcList
         
‘__________________________
‘we’re done -> big clean up
HastaLaVistaDotCom SUCCESS, vbNullString

‘ #endregion

‘__________________________________
‘ #region functions and subsequents

‘get all directories in given partition
Function WalkDirectories(ByVal sDisk, _
                                                          ByRef dcWinners, _
                                                          ByRef dcDirs, _
                                                         ByRef sRet) ‘As Boolean
         
          On Error Resume Next
         
          Dim sWQL           ‘As String
          Dim colDirs                  ‘As Collection
          Dim oDir           ‘As Object
         
          Dim dbFileSize     ‘As Double
         
          Dim sFolder        ‘As String
         
          WalkDirectories = True
         
          sWQL = “SELECT Name FROM Win32_Directory WHERE Drive = ‘” & sDisk & “‘”
         
          If blOutput Then WScript.Echo sWQL
         
          Set colDirs = oWMI.ExecQuery(sWQL)
                  
                   If colDirs Is Nothing Then
                  
                             sRet = sWQL & ” :: ” & Err.Description : WalkDirectories = False
                             sWQL = vbNullString : Exit Function
                            
                   End If
                  
                   ‘walk directories
                   For Each oDir In colDirs
                            
                             ‘get added up file size in dir
                             ‘(dirs are just collections of files and subfolders
                             ‘they do not have sizes)!
                   HandleDirectory oDir.Name, dbFileSize, sMSG
                  
                   ‘add dir to collection cverall
                             dcWinners.Add oDir.Name, dbFileSize
                             ‘add dir to collection for single info
                             dcDirs.Add oDir.Name, dbFileSize
                            
                             If blOutput Then WScript.Echo oDir.Name & ” ” & dbFileSize
                            
                             ‘is scanned dir a subfolder or subsub…folder of any
                             ‘of the already scanned dirs(?) -> add size
                             For Each sFolder In dcWinners.Keys
                                     
                                      If InStr(oDir.Name & “”, sFolder) > 0 Then