当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 使用 PowerShell 查找僵尸文件

作者:精品下载站 日期:2024-12-14 20:38:37 浏览:13 分类:玩电脑

使用 PowerShell 查找僵尸文件


[玩转系统] 使用 PowerShell 查找僵尸文件

在我们深入探讨这个问题之前,让我强调一下。我的许多帖子本质上都是为了教育。我经常以一个场景(例如查找 0 字节文件)作为教授概念和技术的起点。完成一项任务几乎总是有多种方法。我的代码从来不打算按原样在生产中运行。相反,你应该拿走我如何做某事以及为什么。概念和技术比解决表面目标更重要。

使用CIM

对于此任务,我想我应该使用 Get-CimInstance。 您可能没有意识到,但是您可以查询一个 WMI 类来查找名为 CIM_Datafile。在大多数情况下,老实说,我不确定哪种情况不会出现这种情况,所有文件都在 CIM 存储库中注册。以下是其中一个对象的外观:

Status                : OK
Name                  : c:\work\samples\w.txt
Caption               : c:\work\samples\w.txt
Description           : c:\work\samples\w.txt
InstallDate           : 10/9/2020 3:35:49 PM
AccessMask            : 18809343
Archive               : True
Compressed            : False
CompressionMethod     : 
CreationClassName     : CIM_LogicalFile
CreationDate          : 10/9/2020 3:35:49 PM
CSCreationClassName   : Win32_ComputerSystem
CSName                : PROSPERO
Drive                 : c:
EightDotThreeFileName : c:\work\samples\w.txt
Encrypted             : False
EncryptionMethod      : 
Extension             : txt
FileName              : w
FileSize              : 0
FileType              : Text Document
FSCreationClassName   : Win32_FileSystem
FSName                : NTFS
Hidden                : False
InUseCount            : 
LastAccessed          : 10/30/2020 11:49:34 AM
LastModified          : 10/9/2020 6:56:31 PM
Path                  : \work\samples\
Readable              : True
System                : False
Writeable             : True
Manufacturer          : 
Version               : 
PSComputerName        : 

假设我想查找 C:\work\samples 中文件大小为 0 的所有文件。我可以编写一个 WMI 过滤器,如下所示:

"Drive='c:' AND path = '\work\samples\' AND filesize=0"

创建 WMI/CIM 查询时,您需要转义反斜杠,因此路径值 \work\samples\ 变为 \work\samples\。另请记住,WMI/CIM 查询中的运算符是旧版运算符,而不是 PowerShell 运算符。

在PowerShell中,我们强调早期过滤的重要性。如果命令提供了一种过滤方法,那么就使用它。早期过滤几乎总是比获取所有内容然后通过管道传输到 Where-Object 更好。这并不是说使用Where -Object 是一件坏事。有时您别无选择,或者故意需要通过管道传输到Where-Object。只要确保您知道为什么使用Where-Object即可。

这是我的简单测试。

[玩转系统] 使用 PowerShell 查找僵尸文件

我知道我的基本查询有效并且得到了我期望的结果。现在,我想退后一步,将所有 0 长度的文件归档到 C:\Work 中。

Get-CimInstance -ClassName CIM_Datafile -Filter "Drive='c:' AND path = '\work\' AND filesize=0" | Select-Object Name,FileSize

[玩转系统] 使用 PowerShell 查找僵尸文件

嗯。该查询在 C:\Work 的根目录中找到了该文件,但没有找到其他文件。 WMI 查询越具体,通常运行速度越快。但是,在这种情况下,我需要使用通配符稍微扩展查询。我想查找路径以 \\work 开头的文件。

Get-CimInstance -ClassName CIM_Datafile -Filter "Drive='c:' AND path LIKE '\work\%' AND filesize=0" | Select-Object Name,FileSize

在 WMI/Cim 查询中,使用 % 作为通配符代替传统的 *。另请注意,我将运算符更改为 LIKE。运算符不区分大小写。我使用大写,这样它们就会脱颖而出。这种方法的一个显着缺点是它比使用“等于”比较慢得多。但它最终应该会起作用。

[玩转系统] 使用 PowerShell 查找僵尸文件

注意事项和提示

使用 WMI/CIM 查询时,需要记住一些事项。首先,不能保证每个对象(在本例中为文件)都已在 CIM 存储库中注册。理论上,您的查询可能会丢失文件。在此特定用例中,您始终可以使用 Get-ChildItem 枚举文件夹。

当我从事此工作时,我发现如果文件夹实际上使用重新解析点,则查询需要相当长的时间,甚至可能无法按预期工作。例如,我的 C:\Scripts 文件夹实际上是 OneDrive 的链接。

PS C:\> get-item c:\scripts | Select Target

Target
------
{D:\OneDrive\Scripts}

我发现如果我在查询中使用 C:\Scripts,我得到的结果不完整。但如果我使用 D:\OneDrive\Scripts,我会更快地得到预期的结果。

加快该过程的另一种方法是限制 Get-CimInstance 需要获取的属性。这是另一种类型的过滤。我第一次在 C:\Work 中搜索 0 字节文件花了 8 分钟。但由于我只需要一些属性,我可以改进我的命令:

Get-CimInstance -ClassName CIM_Datafile -Filter "Drive='c:' AND path LIKE '\work\%' AND filesize=0" -property Name,FileSize | Select-Object Name,FileSize

我得到了相同的结果,但这次用了 39 秒。我承认大型查询有时会花费很长时间,有时运行速度比您预期的要快。尽管作为规则,使用 LIKE 总是会减慢查询速度。

创建函数

考虑到所有这些,我编写了一个函数来查找给定路径中的 0 字节文件。

Function Get-ZeroLengthFiles {
    [CmdletBinding()]
    [alias("gzf", "zombie")]
    Param(
        [Parameter(Position = 0)]
        [ValidateScript( { Test-Path -Path $_ })]
        [string]$Path = ".",
        [switch]$Recurse
    )
    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)"
        #select a subset of properties which speeds things up
        $get = "Name", "CreationDate", "LastModified", "FileSize"

        $cimParams = @{
            Classname   = "CIM_DATAFILE"
            Property    = $get
            ErrorAction = "Stop"
            Filter      = ""
        }
    } #begin
    Process {
         Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Using specified path $Path"
         #test if folder is using a link or reparse point
         if ( (Get-Item -path $Path).Target) {
             $target =  (Get-Item -path $Path).Target
            Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] A reparse point was detected pointing towards $target"
            #re-define $path to use the target
            $Path = $Target
         }
        #convert the path to a file system path
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Converting $Path"
        $cPath = Convert-Path $Path
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Converted to $cPath"

        #trim off any trailing \ if cPath is other than a drive root like C:\
        if ($cpath.Length -gt 3 -AND $cpath -match "\$") {
            $cpath = $cpath -replace "\$", ""
        }

        #parse out the drive
        $drive = $cpath.Substring(0, 2)
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Using Drive $drive"

        #get the folder path from the first \
        $folder = $cpath.Substring($cpath.IndexOf("\")).replace("\", "\")
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Using folder $folder (escaped)"

        if ($folder -match "\w+" -AND $PSBoundParameters.ContainsKey("Recurse")) {
            #create the filter to use the wildcard for recursing
            $filter = "Drive='$drive' AND Path LIKE '$folder\%' AND FileSize=0"
        }
        elseif ($folder -match "\w+") {
            #create an exact path pattern
            $filter = "Drive='$drive' AND Path='$folder\' AND FileSize=0"
        }
        else {
            #create a root drive filter for a path like C:\
            $filter = "Drive='$drive' AND Path LIKE '\%' AND FileSize=0"
        }

        #add the filter to the parameter hashtable
        $cimParams.filter = $filter
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Looking for zero length files with filter $filter"

        #initialize a counter to keep track of the number of files found
        $i=0
        Try {
            Write-Host "Searching for zero length files in $cpath. This might take a few minutes..." -ForegroundColor magenta
            #find files matching the query and create a custom object for each
            Get-CimInstance @cimParams | ForEach-Object {
                #increment the counter
                $i++

                #create a custom object
                [PSCustomObject]@{
                    PSTypeName   = "cimZeroLengthFile"
                    Path         = $_.Name
                    Size         = $_.FileSize
                    Created      = $_.CreationDate
                    LastModified = $_.LastModified
                }
            }
        }
        Catch {
            Write-Warning "Failed to run query. $($_.exception.message)"
        }
        if ($i -eq 0) {
            #display a message if no files were found
            Write-Host "No zero length files were found in $cpath." -ForegroundColor yellow
        }
        else {
             Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Found $i matching files"
        }
    } #process
    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"
    } #end
}

我希望这些评论能够解释我在做什么以及为什么。我什至为本赛季添加了一个有趣的命令别名。

[玩转系统] 使用 PowerShell 查找僵尸文件

在函数中,我将自定义对象写入管道,以便可以在 PowerShell 中使用结果。

[玩转系统] 使用 PowerShell 查找僵尸文件

我递归地运行 C:\ 的函数,大约花了 41 秒才找到 1774 个文件。这对我来说似乎非常有效!

概括

在处理任何任务时,始终从可以在控制台中运行的简单命令开始。如果您无法从提示符成功运行命令,那么您将很难围绕它编写函数或脚本。即使这个函数不是我先写的。当使用 CIM cmdlet 并尝试提出优雅且高效的查询时尤其如此。

如果您想了解有关 PowerShell 脚本编写的更多信息,请考虑获取《PowerShell 脚本编写和工具制作》的副本。如果您对我的做法或原因有任何疑问,请随时发表评论。

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯