Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Erasing data on free space on NTFS partitions

0.00/5 (No votes)
19 Sep 2004 1  
A VBScript that overwrites free disk space to prevent file recovery on a NTFS partition, by creating a file with random data and (mostly) the size of the free space.

Introduction

This week I ordered a new computer to replace one of my private machines at home. Because I'm still indecisive what to do with the old one and selling it is one option, I thought it might be a good idea to erase all the old data on the two hard disks. The problem is that format does not really do this, leaving the possibility to potentially restore files later. So, I wrote this little script that you would not want to use at an intelligence service (you will see why later) but which should be sufficient for home use. Because it is a command line script, it must be run by CScript.

CScript Overwrite.vbs [path] [filename]

Global Declarations

The basic idea is to create a file which consumes all free space on a partition and contains only random data. So, my script starts with some default settings and then calls Main.

Option Explicit

Const sDefaultDir = "C:\"
Const sDefaultFilename = "overwrite.garbage"
Const lStartBlockSize = 32768

Call Main

Option Explicit forces you to explicitly declare variables you want to use. This helps to avoid using variables you accidentally created by a typo, and those errors can be really hard to debug. The Constants contain the default directory, default filename, and the block size. Directory and filename should be self-explanatory. The block size defines how many bytes are written with each call of TextStream.Write in CreateGarbageFile. After experimenting with some block sizes, I decided to use a 32 kilobyte block (= 32 * 1024 bytes = 215 bytes) because a larger block would take too much time to be created on a 200 MHz processor, and a 32 Kb block takes a few seconds to be created even on a 2 GHz processor.

Sub Main()

The Sub Main does all the work regarding the fulfillment of the prerequisites and also starts off with some local declarations and initializations.

The variables sPath and sFilename will store the actual values which can be eventually the default values or values passed as command line parameters. The other variables will be used for various objects. ShowStartMsg is a simple method that just uses WScript.Echo to display a static start-up message.

The last two lines are more interesting. The Arguments property of the WScript object returns a collection of the command line parameters. A FileSystemObject object is needed for all the file I/O operations like processing paths, dealing with drives, or creating a file. The object has to be created prior to use, by calling CreateObject which is also a method of WScript.

  Dim sPath, sFilename
  Dim oArgs, oFS, oDrive, oRegExp, oMatches

  ShowStartMsg

  Set oArgs = WScript.Arguments
  Set oFS = CreateObject("Scripting.FileSystemObject")

The first check I perform is whether there is only one command line parameter and this parameter equals /?. I get the number of elements in the WshArguments collection by calling Count, and access elements by using their index (starting with 0), so usage does not differ from any other collection. If both conditions are true, ShowHelp is called to display some information on how to use the script. The parameter value true tells ShowHelp to cancel the execution of the script by calling WScript.Quit.

  If oArgs.Count = 1 Then
    If oArgs(0) = "/?" Then
      ShowHelp true
    End If
  End If

Then I check if more than two parameters were passed from the command line. Because the script supports only two parameters (which are both optional), this would be an invalid input. The Sub ShowMsg is defined as Sub ShowMsg(sMsg, bShowHelpHint, bExit) where sMsg is a string to be displayed on the console. bShowHelpHint determines whether a hint on how to start Overwrite.vbs to get help will be shown, and bExit determines if the script should quit after the message was displayed.

  If oArgs.Count > 2 Then
    ShowMsg "ERROR: Invalid command line parameters" & _ 
            " (too many parameters specified)", true, true
  End If

After that, I check if a path has been provided by the user. If he or she did, I call GetAbsolutePathName for further use. GetAbsolutePathName translates relative path names into absolute path names. It does not matter for making the script work but always using absolute path names when telling the user what is done makes the script's output unambiguous. If no path was specified, I initialize sPath with an empty string.

  If oArgs.Count > 0 Then
    sPath = oFS.GetAbsolutePathName(oArgs(0))
  Else
    sPath = ""
  End If

The next step is to verify that the given path exists. FolderExists does this for me. All I need to do is to pass the path I want to verify, in this case the content of sPath. A backslash is amended to a user-specified path in case it was omitted by the user. This allows getting a valid path to the file we are going to write by combining the path and the filename (sPath & sFilename).

  If oFS.FolderExists(sPath) Then
    WScript.Echo "Checking folder " & Chr(34) & sPath & Chr(34) & ": OK"
    If Right(sPath, 1) <> "\" Then
      sPath = sPath & "\"
    End If
  Else
    WScript.Echo "Checking folder " & Chr(34) & sPath & Chr(34) & ": FAILED"
    sPath = sDefaultDir
    WScript.Echo "INFO: Using default folder " & Chr(34) & sPath & Chr(34)
  End If

After we know that the path is okay, the filename needs verification. This is done in three steps. The first step is to analyze the command line parameters. If a filename was specified by the user and it is not empty, this filename is copied into sFilename (an empty filename provided by the user is treated as an error condition). If no filename was specified, the default filename (see Declarations) is copied into sFilename.

  If oArgs.Count = 2 Then
    sFilename = oArgs(1)
    If sFilename = "" Then
      ShowMsg "ERROR: Filename must not be empty", true, true
    End If
  Else
    sFilename = sDefaultFilename
    WScript.Echo "INFO: Using default filename " & Chr(34) & sFilename & Chr(34)
  End If

Second, we need to ensure that the filename does not contain any invalid characters. This is done by using a regular expression. After creating an instance of the RegExp class, I set the Pattern property to my regular expression which contains all the forbidden characters. Then, I call Execute and pass sFilename as the parameter. The return value is a Matches collection. Because I just want to know if my filename contains any invalid characters, I don't bother with checking the collection's content but only with how many elements it contains. If it does not contain any elements at all, the filename is valid.

  Set oRegExp = new RegExp
  oRegExp.Pattern = "[\\\/\:\*\?\" & Chr(34) & "\<\>\|]"
  Set oMatches = oRegExp.Execute(sFilename)
  If oMatches.Count = 0 Then
    WScript.Echo "Validating filename: OK"
  Else
    WScript.Echo "Validating filename: FAILED"
    ShowMsg "ERROR: Filename must not contain the following characters:"_
      & " \ / : * ? " & Chr(34) & " < > |", true, true
  End If

Because the user might enter the name of an existing file and I really don't want to delete user data or program files, I finally make sure that the specified file does not exist by calling the FileExists method of FileSystemObject.

  If oFS.FileExists(sPath & sFilename) = False Then
    WScript.Echo "Ensuring that file " & Chr(34) & sFilename & Chr(34) &_
      " does not exist: OK"
  Else
    WScript.Echo "Ensuring that file " & Chr(34) & sFilename & Chr(34) &_
      " does not exist: FAILED"
    ShowMsg "ERROR: File " & Chr(34) & _
            sPath & sFilename & Chr(34) & " already exists", true, true    
  End If

Now, I make sure that my target is a NTFS partition. Why is that? Because I wanted to write a simple script which just writes one big file. Using FAT file systems implies a maximum file size of 4 GB (in case of FAT32). The free space however might be larger than that and I didn't want to create multiple files (in fact I don't even use FAT file systems anymore) to keep the script as simple as possible.

To get this information, we need a Drive object. And to get that, we simply call GetDrive and pass the return value of GetDriveName as its parameter (passing the whole path would lead to an exception when it does not point to the root directory). Then we just check if the FileSystem property is set to NTFS.

  Set oDrive = oFS.GetDrive(oFS.GetDriveName(sPath))
  If UCase(oDrive.FileSystem) = "NTFS" Then
    WScript.Echo "Checking for NTFS: OK"  
  Else
    WScript.Echo "Checking for NTFS: FAILED"
    ShowMsg "ERROR: " & oDrive.FileSystem & _
            " file system not supported", true, true
  End If

The last thing we want to prevent is that someone starts writing random data over a network. Therefore, we use the DriveType property of our Drive object to get the drive type, and only proceed if it is a fixed or a removable drive.

  Select Case oDrive.DriveType
    Case 1, 2
      WScript.Echo "Checking drive type: OK"
    Case Else
      WScript.Echo "Checking drive type: FAILED"
      Select Case oDrive.DriveType
        Case 3
          ShowMsg "ERROR: Network drives are not supported", true, true
        Case 4
          ShowMsg "ERROR: CD-ROM drives are not supported", true, true
        Case 5
          ShowMsg "ERROR: RAM Disk drives are not supported", true, true
        Case Else
          ShowMsg "ERROR: Unkown drives are not supported", true, true
      End Select
  End Select

The last check eventually verifies that there is any free space we are allowed to write to. This is not necessarily the free space of the partition if quotas are active. This information is returned by the FreeSpace property.

  If oDrive.FreeSpace > 0 Then
    WScript.Echo "Checking for free space: OK"
  Else
    WScript.Echo "Checking for free space: FAILED"
    WScript.Echo "INFO: No free space available (no action required)"
    ShowMsg "INFO: Exiting Overwrite Script...", false, true    
  End If

Now that we know that everything is in place, we can actually start filling the drive with junk. Main only writes some output to the console, calls CreateGarbageFile, and deletes the garbage file by calling DeleteFile.

  WScript.Echo "Creating garbage file " & Chr(34) & _ 
               sPath & sFilename & Chr(34) & "..."
  CreateGarbageFile sPath & sFilename, oFS
  WScript.Echo "Garbage file successfully created!"
  WScript.Echo "INFO: " & oDrive.AvailableSpace & _
               " byte(s) remained which could not be overwritten"
  WScript.Echo "Deleting garbage file..."
  oFS.DeleteFile sPath & sFilename
  WScript.Echo "Garbage file successfully deleted!"
  WScript.Echo "Exiting Overwrite Script..."
  WScript.Quit

Sub CreateGarbageFile(sAbsFilename, oFS)

CreateGarbageFile expects two parameters. sAbsFilename is the filename including the full path and oFS is a FileSystemObject object. The local variable bSngByteBlock is set to true when the function enters the single byte block mode, while sBlock is the string that is written to disk. The Drive object was introduced when discussing Main, and oFile is a TextStream object. This object is used to actually write data to disk and is created by calling the CreateTextFile method of the FileSystemObject class. The first parameter of that call is the filename. The second one tells the function not to overwrite files and the third one means that 8-bit ASCII will be used (the alternative would be Unicode).

  Dim bSngByteBlock
  Dim sBlock
  Dim oFile, oDrive

  bSngByteBlock = false
  Set oDrive = oFS.GetDrive(oFS.GetDriveName(sAbsFilename))
  Set oFile = oFS.CreateTextFile(sAbsFilename, false, false)

Now, a new data block is created by calling GenGargabeBlock using the default block size (see Declarations). The second parameter allows the function to write output to the console. Then error handling is turned on. This is necessary because there may be problems when the disk is almost full and only a few kilobytes of free space remains. In this situation, an attempt to write data using the TextStream object may fail. Therefore, an error handler is needed. Furthermore, the single byte block allows writing as much data to disk as possible (or in other words, help to delay the occurrence of the exception).

  sBlock = GenGarbageBlock(lStartBlockSize, true)
  On Error Resume Next

A loop is used to continuously write the junk block into the file. The stop criterion is met when there is no free disk space available anymore. However, there are two exceptions. When there is still disk space left but it is smaller than our block of junk, the function switches to single byte block mode. It will then create a new one byte string with every cycle and write it to disk. As described before, an exception may occur when writing, although there is still disk space left. In this case, the Err object is used to determine if an exception occurred. If there was an exception, a message is displayed and the loop is terminated.

  Do While oDrive.FreeSpace > 0
    If oDrive.FreeSpace < lStartBlockSize Then
      If bSngByteBlock = false Then
        WScript.Echo "INFO: Falling back to single byte block"
        bSngByteBlock = true
      End If
      sBlock = GenGarbageBlock(1, false)
    End If
    oFile.Write sBlock
    If Err.Number <> 0 Then
      WScript.Echo "WARNING: Error " & Chr(34) & Err.Description & Chr(34) & " ("_
        & Err.Number & ") occured while writing garbage file"
      Exit Do
    End If
  Loop

The last task is to clean up. That means deactivating error handling and closing our beloved garbage file.

  On Error GoTo 0
  oFile.Close

Function GenGarbageBlock(lSize, bShowMsg)

GenGarbageBlock is responsible for creating a string with random information. Of course, the function Rnd only returns pseudo random numbers. In fact, it uses an algorithm that returns something that looks random, but if you always use the same initial value, you will always get the same numerical series.

  Dim lCounter

  If bShowMsg = True Then
    WScript.Echo "Creating new random data block (" & lSize & " bytes)..."
  End If

  GenGarbageBlock = ""
  Randomize
  For lCounter = 1 To lSize
    GenGarbageBlock = GenGarbageBlock & Chr(Int ((255 + 1) * Rnd))
  Next

  If bShowMsg = True Then
    WScript.Echo "Data block complete"
    WScript.Echo "Continue writing garbage file..."
  End If

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here