擦除 NTFS 分区上的空闲空间数据






4.13/5 (5投票s)
2004年9月5日
8分钟阅读

63108

691
一个VBScript脚本,通过创建一个包含随机数据且(大部分)与空闲空间大小相同的文件,覆盖NTFS分区上的空闲磁盘空间,以防止文件恢复。
引言
本周我订购了一台新电脑,以替换我家里的一台私人机器。由于我仍在犹豫如何处理旧电脑,而出售它是一个选项,所以我认为擦除两个硬盘上的所有旧数据可能是一个好主意。问题是格式化并不能真正做到这一点,留下以后可能恢复文件的可能性。所以,我写了这个小脚本,你不会想在情报部门使用它(你稍后会明白为什么),但它应该足以用于家庭使用。因为它是一个命令行脚本,所以必须通过CScript运行。
CScript Overwrite.vbs [path] [filename]
全局声明
基本思想是创建一个文件,该文件占用分区上的所有空闲空间并仅包含随机数据。因此,我的脚本以一些默认设置开始,然后调用Main
。
Option Explicit
Const sDefaultDir = "C:\"
Const sDefaultFilename = "overwrite.garbage"
Const lStartBlockSize = 32768
Call Main
Option Explicit
强制您明确声明要使用的变量。这有助于避免因打字错误而意外创建的变量,而这些错误可能非常难以调试。常量包含默认目录、默认文件名和块大小。目录和文件名应该不言自明。块大小定义了每次调用TextStream.Write
在CreateGarbageFile
中写入的字节数。在尝试了一些块大小之后,我决定使用一个32千字节的块(= 32 * 1024字节 = 215字节),因为一个更大的块在200MHz处理器上创建会花费太多时间,而一个32Kb的块即使在2GHz处理器上创建也需要几秒钟。
Sub Main()
这个Sub Main
完成了所有关于满足先决条件的工作,并且还以一些局部声明和初始化开始。
变量`sPath`和`sFilename`将存储实际值,这些值最终可以是默认值,也可以是作为命令行参数传递的值。其他变量将用于各种对象。`ShowStartMsg`是一个简单的方法,它只是使用`WScript.Echo`来显示一个静态启动消息。
最后两行更有趣。`WScript`对象的`Arguments`属性返回命令行参数的集合。`FileSystemObject`对象是所有文件I/O操作所必需的,例如处理路径、处理驱动器或创建文件。该对象必须在使用前通过调用`CreateObject`来创建,`CreateObject`也是`WScript`的一个方法。
Dim sPath, sFilename
Dim oArgs, oFS, oDrive, oRegExp, oMatches
ShowStartMsg
Set oArgs = WScript.Arguments
Set oFS = CreateObject("Scripting.FileSystemObject")
我执行的第一个检查是,是否只有一个命令行参数,并且该参数等于`/?`。我通过调用`Count`来获取`WshArguments`集合中的元素数量,并通过使用它们的索引(从0开始)访问元素,因此用法与其他任何集合都没有区别。如果两个条件都为真,则调用`ShowHelp`以显示有关如何使用脚本的一些信息。参数值`true`告诉`ShowHelp`通过调用`WScript.Quit`来取消脚本的执行。
If oArgs.Count = 1 Then
If oArgs(0) = "/?" Then
ShowHelp true
End If
End If
然后我检查命令行是否传递了两个以上的参数。因为脚本只支持两个参数(都是可选的),所以这将是一个无效的输入。Sub ShowMsg
被定义为Sub ShowMsg(sMsg, bShowHelpHint, bExit)
,其中`sMsg`是一个要在控制台上显示的字符串。`bShowHelpHint`决定是否显示如何启动*Overwrite.vbs*以获取帮助的提示,而`bExit`决定在显示消息后脚本是否应该退出。
If oArgs.Count > 2 Then
ShowMsg "ERROR: Invalid command line parameters" & _
" (too many parameters specified)", true, true
End If
之后,我检查用户是否提供了路径。如果提供了,我将调用`GetAbsolutePathName`以供进一步使用。`GetAbsolutePathName`将相对路径名转换为绝对路径名。这对于使脚本工作并不重要,但当告诉用户正在做什么时,始终使用绝对路径名可以使脚本的输出明确无误。如果未指定路径,我将`sPath`初始化为空字符串。
If oArgs.Count > 0 Then
sPath = oFS.GetAbsolutePathName(oArgs(0))
Else
sPath = ""
End If
下一步是验证给定路径是否存在。`FolderExists`为我做了这件事。我所需要做的就是传递我想要验证的路径,在本例中是`sPath`的内容。如果用户省略了反斜杠,则会在用户指定的路径后面附加一个反斜杠。这允许通过组合路径和文件名(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
知道路径没问题后,就需要验证文件名。这分三步完成。第一步是分析命令行参数。如果用户指定了文件名且不为空,则将此文件名复制到`sFilename`中(用户提供的空文件名被视为错误条件)。如果未指定文件名,则将默认文件名(参见声明)复制到`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
其次,我们需要确保文件名不包含任何无效字符。这通过使用正则表达式来完成。在创建`RegExp`类的一个实例后,我将`Pattern`属性设置为我的正则表达式,其中包含所有禁止的字符。然后,我调用`Execute`并将`sFilename`作为参数传递。返回值为`Matches`集合。因为我只想知道我的文件名是否包含任何无效字符,所以我不关心检查集合的内容,只关心它包含多少个元素。如果它根本不包含任何元素,则文件名是有效的。
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
因为用户可能会输入现有文件的名称,而我真的不想删除用户数据或程序文件,所以我最终通过调用`FileSystemObject`的`FileExists`方法来确保指定的文件不存在。
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
现在,我确保我的目标是 NTFS 分区。为什么呢?因为我想编写一个简单的脚本,它只写入一个大文件。使用 FAT 文件系统意味着最大文件大小为 4 GB(如果是 FAT32)。但是,空闲空间可能大于此,我不想创建多个文件(实际上我甚至不再使用 FAT 文件系统)以使脚本尽可能简单。
为了获取这些信息,我们需要一个`Drive`对象。要获取它,我们只需调用`GetDrive`并将其参数设置为`GetDriveName`的返回值(传递整个路径会导致异常,如果它不指向根目录)。然后我们只需检查`FileSystem`属性是否设置为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
我们最后要防止的是有人通过网络写入随机数据。因此,我们使用`Drive`对象的`DriveType`属性来获取驱动器类型,并且只有当它是固定驱动器或可移动驱动器时才继续。
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
最后一步检查最终验证是否有任何允许我们写入的空闲空间。如果配额处于活动状态,这不一定是分区的空闲空间。此信息由`FreeSpace`属性返回。
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
现在我们知道一切就绪,我们可以开始向驱动器填充垃圾数据了。Main
只向控制台写入一些输出,调用CreateGarbageFile
,并通过调用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`需要两个参数。`sAbsFilename`是包含完整路径的文件名,`oFS`是一个`FileSystemObject`对象。当函数进入单字节块模式时,局部变量`bSngByteBlock`被设置为`true`,而`sBlock`是写入磁盘的字符串。`Drive`对象在讨论Main
时介绍过,`oFile`是一个`TextStream`对象。此对象用于实际将数据写入磁盘,并通过调用`FileSystemObject`类的`CreateTextFile`方法创建。该调用的第一个参数是文件名。第二个参数告诉函数不要覆盖文件,第三个参数表示将使用8位ASCII(替代方案是Unicode)。
Dim bSngByteBlock
Dim sBlock
Dim oFile, oDrive
bSngByteBlock = false
Set oDrive = oFS.GetDrive(oFS.GetDriveName(sAbsFilename))
Set oFile = oFS.CreateTextFile(sAbsFilename, false, false)
现在,通过使用默认块大小(参见声明)调用GenGargabeBlock
来创建新的数据块。第二个参数允许函数将输出写入控制台。然后打开错误处理。这是必要的,因为当磁盘几乎已满且只剩下几千字节的空闲空间时,可能会出现问题。在这种情况下,尝试使用TextStream
对象写入数据可能会失败。因此,需要一个错误处理程序。此外,单字节块允许尽可能多地将数据写入磁盘(或者换句话说,有助于延迟异常的发生)。
sBlock = GenGarbageBlock(lStartBlockSize, true)
On Error Resume Next
循环用于不断将垃圾块写入文件。当没有可用的空闲磁盘空间时,满足停止条件。但是,有两种例外情况。当仍然有磁盘空间但它小于我们的垃圾块时,函数会切换到单字节块模式。然后,它将在每个循环中创建一个新的单字节字符串并将其写入磁盘。如前所述,即使仍然有磁盘空间,在写入时也可能发生异常。在这种情况下,`Err`对象用于确定是否发生了异常。如果发生了异常,则显示一条消息并终止循环。
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
最后的任务是清理。这意味着停用错误处理并关闭我们心爱的垃圾文件。
On Error GoTo 0
oFile.Close
Function GenGarbageBlock(lSize, bShowMsg)
`GenGarbageBlock`负责创建一个包含随机信息的字符串。当然,函数`Rnd`只返回伪随机数。实际上,它使用一种算法,返回看起来随机的东西,但如果你总是使用相同的初始值,你将总是得到相同的数值序列。
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