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