powershell 尝试加快目录/文件的递归检查

xcitsw88  于 2023-03-12  发布在  Shell
关注(0)|答案(2)|浏览(113)

我正在尝试编写一个powershell脚本,它将递归地遍历一个目录,并报告任何具有“非”继承权限的文件或目录。
我觉得我离这个很近了

Get-ChildItem -path 'C:\xyzzy' -Recurse | ForEach-Object {
    $fullpath = $_.FullName
        
    (get-acl -path $_.FullName).Access | ForEach-Object {
       if ($_.IsInherited -ne 'True') {
         write-output $fullpath
       }
    }
}

这看起来很有效,但是在一个大的目录树上运行起来非常慢。有人有什么建议可以加快这个速度吗?
谢谢!

qc6wkl3g

qc6wkl3g1#

下面的代码应该比您当前所做的要快得多,它使用Queue<T>而不是Get-ChildItem来遍历目录,并且由于您使用的是PowerShell 5.1,因此可以使用FileInfoDirectoryInfoGetAccessControl()示例方法来代替Get-Acl
最后在这里:

(Get-Acl -Path $_.FullName).Access | ForEach-Object {
    if ($_.IsInherited -ne $true) {
        Write-Output $fullpath
    }
}

由于您只对在存在任何非继承规则的情况下获取文件或文件夹的绝对路径感兴趣,因此只要满足条件,就应停止上述循环,无需枚举所有规则。

注意,此代码包括隐藏文件和文件夹

$queue = [System.Collections.Generic.Queue[System.IO.DirectoryInfo]]::new()
$queue.Enqueue('C:\xyzzy')

class FilterAccessRules {
    static [string] NotInherited([System.IO.FileSystemInfo] $Instance) {
        # enumerate each Access Rule
        foreach($rule in $Instance.GetAccessControl().Access) {
            # if this rule is not Inherited
            if(-not $rule.IsInherited) {
                # there is no need to keep checking next rules
                # output the absolute path and end here
                return $Instance.FullName
            }
        }

        # else, output null
        return $null
    }
}

while($queue.Count) {
    $target = $queue.Dequeue()

    try {
        if($path = [FilterAccessRules]::NotInherited($target)) {
            $path
        }
    }
    catch { }

    try {
        $enum = $target.EnumerateFileSystemInfos()
    }
    catch {
        # if this folder could not be enumerated,
        # go next
        continue
    }

    foreach($item in $enum) {
        if($item -is [System.IO.FileInfo]) {
            if($path = [FilterAccessRules]::NotInherited($item)) {
                $path
            }
            continue
        }

        $queue.Enqueue($item)
    }
}
toiithl6

toiithl62#

要提供Santiago Squarzon's helpful answer的替代方案:
它可能不那么快,但在实践中可能足够快,并且具有概念上更简单和更简洁的优点:

Get-ChildItem -PipelineVariable item -LiteralPath C:\xyzzy -Recurse |
  Get-Acl |
  Where-Object   { $_.Access.IsInherited -contains $false } |
  ForEach-Object { $item.FullName }
  • 上面的代码避免了为每个输入文件 * 调用Get-Acl * 一次,而是使用一个直接从管道获取输入的 * 单个 * 调用,这将大大加快操作速度。
  • $_.Access.IsInherited使用member-access enumeration返回由.Access返回的所有规则中的所有.Inherited属性值,并且-contains $false测试是否至少有一个值为$false
  • 请注意,使用公共-PipelineVariable参数来存储当前文件系统项,作为Get-ChildItem在自选变量$item中的输出,使其可用于后面管道段中的脚本块。
  • 注:这里并不一定需要-PipeLineVariable,但它构成了完整路径(.FullName属性),而Get-Acl输出对象本身具有(ETS).Path属性,其值以PowerShell provider前缀开头,即Microsoft.PowerShell.Core\FileSystem::,必须将其删除(您可以使用$_.Path -replace '^[^:]+::'执行此操作)

如果您不关心 * 流式 * 输出-特别是查看 * 正在生成的 * 输出-您可以进一步加快上述操作,如下所示:

(
  Get-ChildItem -PipelineVariable item -LiteralPath C:\xyzzy -Recurse |
  Get-Acl
).Where({ $_.Access.IsInherited -contains $false }).Path -replace '^[^:]+::'

相关问题