本文介绍了通过Powershell查找给定文件的完整路径的最快方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要编写一个Powershell代码段,以便在整个分区上尽快找到给定文件名的完整路径.

I need to write a Powershell snippet that finds the full path(s) for a given filename over a complete partition as fast as possible.

为了更好地进行比较,我在代码示例中使用了以下全局变量:

For the sake of better comparison, I am using this global variables for my code-samples:

$searchDir  = "c:\"
$searchName = "hosts"

我从使用Get-ChildItem的一小段开始就有了第一个基线:

I started with a small snippet using Get-ChildItem to have a first baseline:

"get-ChildItem"
$timer = [System.Diagnostics.Stopwatch]::StartNew()
$result = Get-ChildItem -LiteralPath $searchDir -Filter $searchName -File -Recurse -ea 0
write-host $timer.Elapsed.TotalSeconds "sec."

我的SSD上的运行时间为14,8581609秒.

The runtime on my SSD was 14,8581609 sec.

接下来,我尝试运行传统的DIR命令以查看改进:

Next, I tried running the classical DIR-command to see the improvements:

"dir"
$timer = [System.Diagnostics.Stopwatch]::StartNew()
$result = &cmd /c dir "$searchDir$searchName" /b /s /a-d
$timer.Stop()
write-host $timer.Elapsed.TotalSeconds "sec."

此过程完成了13,4713342秒. -不错,但是我们可以更快地得到它吗?

This finished in 13,4713342 sec. - not bad, but can we get it faster?

在第三次迭代中,我正在使用ROBOCOPY测试相同的任务.这里是代码示例:

In the third iteration I was testing the same task with ROBOCOPY. Here the code-sample:

"robocopy"
$timer = [System.Diagnostics.Stopwatch]::StartNew()

$roboDir = [System.IO.Path]::GetDirectoryName($searchDir)
if (!$roboDir) {$roboDir = $searchDir.Substring(0,2)}

$info = [System.Diagnostics.ProcessStartInfo]::new()
$info.FileName = "$env:windir\system32\robocopy.exe"
$info.RedirectStandardOutput = $true
$info.Arguments = " /l ""$roboDir"" null ""$searchName"" /bytes /njh /njs /np /nc /ndl /xjd /mt /s"
$info.UseShellExecute = $false
$info.CreateNoWindow = $true
$info.WorkingDirectory = $searchDir

$process = [System.Diagnostics.Process]::new()
$process.StartInfo = $info
[void]$process.Start()
$process.WaitForExit()

$timer.Stop()
write-host $timer.Elapsed.TotalSeconds "sec."

或更短的版本(基于良好的评论):

Or in a shorter version (based on the good comments):

"robocopy v2"
$timer = [System.Diagnostics.Stopwatch]::StartNew()
$fileList = (&cmd /c pushd $searchDir `& robocopy /l "$searchDir" null "$searchName" /ns /njh /njs /np /nc /ndl /xjd /mt /s).trim() -ne ''
$timer.Stop()
write-host $timer.Elapsed.TotalSeconds "sec."

它比DIR快吗?是的,一点没错!现在,运行时间降至3,2685551秒.进行此重大改进的主要原因是,在多个并行实例中,ROBOCOPY在多任务模式下以/mt-swich运行.但是即使没有这种涡轮开关,它也比DIR更快.

Was it faster than DIR? Yes, absolutely! The runtime is now down to 3,2685551 sec.Main reason for this huge improvement is the fact, that ROBOCOPY runs with the /mt-swich in multitask-mode in multiple parallel instances. But even without this turbo-switch is was faster than DIR.

任务完成了吗?并非如此-因为我的任务是创建一个Powershell脚本,以尽可能快的速度搜索文件,但是调用ROBOCOPY有点作弊.

Mission accomplished? Not really - because my task was, to create a powershell-script searching a file as fast as possible, but calling ROBOCOPY is a bit of cheating.

接下来,我想看看,使用[System.IO.Directory]将会有多快.首先尝试使用getFiles和getDirectory-calls.这是我的代码:

Next, I want to see, how fast we will be by using [System.IO.Directory]. First try was by using getFiles and getDirectory-calls. Here my code:

"GetFiles"
$timer = [System.Diagnostics.Stopwatch]::StartNew()

$fileList = [System.Collections.Generic.List[string]]::new()
$dirList = [System.Collections.Generic.Queue[string]]::new()
$dirList.Enqueue($searchDir)
while ($dirList.Count -ne 0) {
    $dir = $dirList.Dequeue()
    try {
        $files = [System.IO.Directory]::GetFiles($dir, $searchName)
        if ($files) {$fileList.addRange($file)}
        foreach($subdir in [System.IO.Directory]::GetDirectories($dir)) {
            $dirList.Enqueue($subDir)
        }
    } catch {}
}
$timer.Stop()
write-host $timer.Elapsed.TotalSeconds "sec."

这一次的运行时间为19,3393872秒.迄今为止最慢的代码.我们可以做得更好吗?现在,下面是一个带有Enumeration调用的代码片段,以进行比较:

This time the runtime was 19,3393872 sec. By far the slowest code. Can we get it better? Here now a code-snippet with Enumeration-calls for comparison:

"EnumerateFiles"
$timer = [System.Diagnostics.Stopwatch]::StartNew()

$fileList = [System.Collections.Generic.List[string]]::new()
$dirList = [System.Collections.Generic.Queue[string]]::new()
$dirList.Enqueue($searchDir)
while ($dirList.Count -ne 0) {
    $dir = $dirList.Dequeue()
    try {
        foreach($file in [System.IO.Directory]::EnumerateFiles($dir, $searchName)) {
            $fileList.add($file)
        }
        foreach ($subdir in [System.IO.Directory]::EnumerateDirectories($dir)) {
            $dirList.Enqueue($subDir)
        }
    } catch {}
}

$timer.Stop()
write-host $timer.Elapsed.TotalSeconds "sec."

只有19,2068545秒的运行时间,速度明显要快些.

It was only slighly faster with a runtime of 19,2068545 sec.

现在让我们看看是否可以通过Kernel32的直接WinAPI调用更快地获得它.这里的代码.让我们看看,这次有多快:

Now let's see if we can get it faster with direct WinAPI-calls from Kernel32.Here the code. Let's see, how fast it is this time:

"WinAPI"
add-type -Name FileSearch -Namespace Win32 -MemberDefinition @"
    public struct WIN32_FIND_DATA {
        public uint dwFileAttributes;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
        public uint nFileSizeHigh;
        public uint nFileSizeLow;
        public uint dwReserved0;
        public uint dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        public string cAlternateFileName;
    }

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern IntPtr FindFirstFile
      (string lpFileName, out WIN32_FIND_DATA lpFindFileData);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern bool FindNextFile
      (IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern bool FindClose(IntPtr hFindFile);
"@

$rootDir = 'c:'
$searchFile = "hosts"

$fileList = [System.Collections.Generic.List[string]]::new()
$dirList = [System.Collections.Generic.Queue[string]]::new()
$dirList.Enqueue($rootDir)
$timer = [System.Diagnostics.Stopwatch]::StartNew()

$fileData = new-object Win32.FileSearch+WIN32_FIND_DATA
while ($dirList.Count -ne 0) {
    $dir = $dirList.Dequeue()
    $handle = [Win32.FileSearch]::FindFirstFile("$dir\*", [ref]$fileData)
    [void][Win32.FileSearch]::FindNextFile($handle, [ref]$fileData)
    while ([Win32.FileSearch]::FindNextFile($handle, [ref]$fileData)) {
        if ($fileData.dwFileAttributes -band 0x10) {
            $fullName = [string]::Join('\', $dir, $fileData.cFileName)
            $dirList.Enqueue($fullName)
        } elseif ($fileData.cFileName -eq $searchFile) {
            $fullName = [string]::Join('\', $dir, $fileData.cFileName)
            $fileList.Add($fullName)
        }
    }
    [void][Win32.FileSearch]::FindClose($handle)
}

$timer.Stop()
write-host $timer.Elapsed.TotalSeconds "sec."

对我来说,这种方法的结果是非常负面的惊喜.运行时间为17,499286秒.这比System.IO调用快,但仍比简单的Get-ChildItem慢.

For me, the result of this approach was quite a negative surprise. The runtime is 17,499286 sec.This is faster than the System.IO-calls but still slower than a simple Get-ChildItem.

但是-仍然有希望接近ROBOCOPY带来的超快结果!对于Get-ChildItem,我们无法进行以多任务模式执行的调用,但对于对于Kernel32调用,我们可以选择使它成为递归函数,并通过嵌入式C#代码对PARALLEL foreach循环中所有子文件夹的每次迭代进行调用.但是该怎么做?

But - there is still hope to come close to the super-fast result from ROBOCOPY!For Get-ChildItem we cannot make the call being executes in multi-tasking mode, but for e.g. the Kernel32-calls we have the option to make this a recursive function an call each iteration over all subfolders in a PARALLEL foreach-loop via embedded C#-code. But how to do that?

有人知道如何将最后一个代码片段更改为使用parallel.foreach吗?即使结果可能不像ROBOCOPY那样快,我也想在此发布这种方法,以便为经典的文件搜索"提供完整的故事书.主题.

Does someone know how to change the last code-snippet to use parallel.foreach?Even if the result might not be that fast as ROBOCOPY I would like to post also this approach here to have a full storybook for this classic "file search" topic.

请让我知道如何执行并行代码部分.

Please let me know, how to do the parallel code-part.

更新:为了完整起见,我添加了在Powershell 7上运行的具有更智能的访问处理功能的GetFiles代码的代码和运行时:

Update:For completeness I am adding the code and runtime of the GetFiles-code running on Powershell 7 with smarter access-handling:

"GetFiles PS7"
$timer = [System.Diagnostics.Stopwatch]::StartNew()
$fileList = [system.IO.Directory]::GetFiles(
  $searchDir,
  $searchFile,
  [IO.EnumerationOptions] @{AttributesToSkip = 'ReparsePoint'; RecurseSubdirectories = $true; IgnoreInaccessible = $true}
)
$timer.Stop()
write-host $timer.Elapsed.TotalSeconds "sec."

我的系统上的运行时间为9,150673秒. -比DIR快,但仍比robocopy慢,在8个内核上执行多任务处理.

The runtime on my system was 9,150673 sec. - faster than DIR, but still slower than robocopy with multi-tasking on 8 cores.

更新#2:在试用了新的PS7功能之后,我想到了这个代码片段,它使用了我的第一个(但很丑?)并行代码方法:

Update #2:After playing around with the new PS7-features I came up with this code-snippet which uses my first (but ugly?) parallel code-approach:

"WinAPI PS7 parallel"
$searchDir  = "c:\"
$searchFile = "hosts"

add-type -Name FileSearch -Namespace Win32 -MemberDefinition @"
    public struct WIN32_FIND_DATA {
        public uint dwFileAttributes;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
        public uint nFileSizeHigh;
        public uint nFileSizeLow;
        public uint dwReserved0;
        public uint dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        public string cAlternateFileName;
    }

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern IntPtr FindFirstFile
      (string lpFileName, out WIN32_FIND_DATA lpFindFileData);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern bool FindNextFile
      (IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern bool FindClose(IntPtr hFindFile);
"@

$rootDir = $searchDir -replace "\\$"
$maxRunSpaces = [int]$env:NUMBER_OF_PROCESSORS
$fileList = [System.Collections.Concurrent.BlockingCollection[string]]::new()
$dirList = [System.Collections.Concurrent.BlockingCollection[string]]::new()
$dirList.Add($rootDir)
$timer = [System.Diagnostics.Stopwatch]::StartNew()

(1..$maxRunSpaces) | ForEach-Object -ThrottleLimit $maxRunSpaces -Parallel {
    $dirList = $using:dirList
    $fileList = $using:fileList
    $fileData = new-object Win32.FileSearch+WIN32_FIND_DATA
    $dir = $null
    if ($_ -eq 1) {$delay = 0} else {$delay = 50}
    if ($dirList.TryTake([ref]$dir, $delay)) {
        do {
            $handle = [Win32.FileSearch]::FindFirstFile("$dir\*", [ref]$fileData)
            [void][Win32.FileSearch]::FindNextFile($handle, [ref]$fileData)
            while ([Win32.FileSearch]::FindNextFile($handle, [ref]$fileData)) {
                if ($fileData.dwFileAttributes -band 0x10) {
                    $fullName = [string]::Join('\', $dir, $fileData.cFileName)
                    $dirList.Add($fullName)
                } elseif ($fileData.cFileName -eq $using:searchFile) {
                    $fullName = [string]::Join('\', $dir, $fileData.cFileName)
                    $fileList.Add($fullName)
                }
            }
            [void][Win32.FileSearch]::FindClose($handle)
        } until (!$dirList.TryTake([ref]$dir))
    }
}

$timer.Stop()
write-host $timer.Elapsed.TotalSeconds "sec."

现在,运行时非常接近robocopy-timing.实际上是4,0809719秒.

The runtime is now very close to the robocopy-timing. It is actually 4,0809719 sec.

不错,但是我仍在通过嵌入式C#代码寻找具有parallel.foreach-approach的解决方案,以使其也适用于Powershell v5.

Not bad, but I am still looking for a solution with a parallel.foreach-approach via embedded C# code to make it work also for Powershell v5.

更新#3:现在,这是我在并行运行空间中运行的Powershell 5的最终代码:

Update #3:Here is now my final code for Powershell 5 running in parallel runspaces:

$searchDir  = "c:\"
$searchFile = "hosts"

"WinAPI parallel"
add-type -Name FileSearch -Namespace Win32 -MemberDefinition @"
    public struct WIN32_FIND_DATA {
        public uint dwFileAttributes;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
        public uint nFileSizeHigh;
        public uint nFileSizeLow;
        public uint dwReserved0;
        public uint dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        public string cAlternateFileName;
    }

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern IntPtr FindFirstFile
      (string lpFileName, out WIN32_FIND_DATA lpFindFileData);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern bool FindNextFile
      (IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern bool FindClose(IntPtr hFindFile);
"@

$rootDir = $searchDir -replace "\\$"
$maxRunSpaces = [int]$env:NUMBER_OF_PROCESSORS
$fileList = [System.Collections.Concurrent.BlockingCollection[string]]::new()
$dirList = [System.Collections.Concurrent.BlockingCollection[string]]::new()
$dirList.Add($rootDir)
$timer = [System.Diagnostics.Stopwatch]::StartNew()

$runSpaceList = [System.Collections.Generic.List[PSObject]]::new()
$pool = [RunSpaceFactory]::CreateRunspacePool(1, $maxRunSpaces)
$pool.Open()

foreach ($id in 1..$maxRunSpaces) {
    $runSpace = [Powershell]::Create()
    $runSpace.RunspacePool = $pool
    [void]$runSpace.AddScript({
        Param (
            [string]$searchFile,
            [System.Collections.Concurrent.BlockingCollection[string]]$dirList,
            [System.Collections.Concurrent.BlockingCollection[string]]$fileList
        )
        $fileData = new-object Win32.FileSearch+WIN32_FIND_DATA
        $dir = $null
        if ($id -eq 1) {$delay = 0} else {$delay = 50}
        if ($dirList.TryTake([ref]$dir, $delay)) {
            do {
                $handle = [Win32.FileSearch]::FindFirstFile("$dir\*", [ref]$fileData)
                [void][Win32.FileSearch]::FindNextFile($handle, [ref]$fileData)
                while ([Win32.FileSearch]::FindNextFile($handle, [ref]$fileData)) {
                    if ($fileData.dwFileAttributes -band 0x10) {
                        $fullName = [string]::Join('\', $dir, $fileData.cFileName)
                        $dirList.Add($fullName)
                    } elseif ($fileData.cFileName -like $searchFile) {
                        $fullName = [string]::Join('\', $dir, $fileData.cFileName)
                        $fileList.Add($fullName)
                    }
                }
                [void][Win32.FileSearch]::FindClose($handle)
            } until (!$dirList.TryTake([ref]$dir))
        }
    })
    [void]$runSpace.addArgument($searchFile)
    [void]$runSpace.addArgument($dirList)
    [void]$runSpace.addArgument($fileList)
    $status = $runSpace.BeginInvoke()
    $runSpaceList.Add([PSCustomObject]@{Name = $id; RunSpace = $runSpace; Status = $status})
}

while ($runSpaceList.Status.IsCompleted -notcontains $true) {sleep -Milliseconds 10}
$pool.Close()
$pool.Dispose()

$timer.Stop()
$fileList
write-host $timer.Elapsed.TotalSeconds "sec."

总运行时间为4,8586134秒.比PS7版本慢一些,但仍比任何DIR或Get-ChildItem版本都快. ;-)

The overall runtime with 4,8586134 sec. is a bit slower than the PS7-version, but still much faster than any DIR or Get-ChildItem variation. ;-)

最终解决方案:终于我能够回答自己的问题了.这是最终代码:

Final Solution:Finally I was able to answer my own question. Here is the final code:

"WinAPI parallel.foreach"

add-type -TypeDefinition @"
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

public class FileSearch {
    public struct WIN32_FIND_DATA {
        public uint dwFileAttributes;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
        public uint nFileSizeHigh;
        public uint nFileSizeLow;
        public uint dwReserved0;
        public uint dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        public string cAlternateFileName;
    }

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern IntPtr FindFirstFile
      (string lpFileName, out WIN32_FIND_DATA lpFindFileData);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern bool FindNextFile
      (IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    public static extern bool FindClose(IntPtr hFindFile);

    static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

    public static class Globals {
        public static BlockingCollection<string> resultFileList {get;set;}
    }

    public static BlockingCollection<string> GetTreeFiles(string path, string searchFile) {
        Globals.resultFileList = new BlockingCollection<string>();
        List<string> dirList = new List<string>();
        searchFile = @"^" + searchFile.Replace(@".",@"\.").Replace(@"*",@".*").Replace(@"?",@".") + @"$";
        GetFiles(path, searchFile);
        return Globals.resultFileList;
    }

    static void GetFiles(string path, string searchFile) {
        path = path.EndsWith(@"\") ? path : path + @"\";
        List<string> dirList = new List<string>();
        WIN32_FIND_DATA fileData;
        IntPtr handle = INVALID_HANDLE_VALUE;
        handle = FindFirstFile(path + @"*", out fileData);
        if (handle != INVALID_HANDLE_VALUE) {
            FindNextFile(handle, out fileData);
            while (FindNextFile(handle, out fileData)) {
                if ((fileData.dwFileAttributes & 0x10) > 0) {
                    string fullPath = path + fileData.cFileName;
                    dirList.Add(fullPath);
                } else {
                    if (Regex.IsMatch(fileData.cFileName, searchFile, RegexOptions.IgnoreCase)) {
                        string fullPath = path + fileData.cFileName;
                        Globals.resultFileList.TryAdd(fullPath);
                    }
                }
            }
            FindClose(handle);
            Parallel.ForEach(dirList, (dir) => {
                GetFiles(dir, searchFile);
            });
        }
    }
}
"@

[fileSearch]::GetTreeFiles($searchDir, 'hosts')

现在,最终运行时间比robocopy快了3,2536388秒.我还在解决方案中添加了该代码的优化版本.

And the final runtime is now faster than robocopy with 3,2536388 sec.I also added an optimized version of that code in the solution.

推荐答案

这是我创建的最终代码.现在的运行时间为2,8627695秒.与所有子目录的Parallel.ForEach相比,将独占性限制为逻辑核的数量具有更好的性能.

This is the final code I created. Runtime is now 2,8627695 sec.Limiting the prallelism to the number of logical cores gave a better performance than doing a Parallel.ForEach for all subdirectories.

您可以将每次匹配的完整FileInfo-Object返回到生成的BlockingCollection中,而不是仅返回文件名.

Instead of returning only the filename, you can return the full FileInfo-Object per hit into the resulting BlockingCollection.

# powershell-sample to find all "hosts"-files on Partition "c:\"

cls
Remove-Variable * -ea 0
[System.GC]::Collect()
$ErrorActionPreference = "stop"

$searchDir  = "c:\"
$searchFile = "hosts"

add-type -TypeDefinition @"
using System;
using System.IO;
using System.Linq;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Text.RegularExpressions;

public class FileSearch {
    public struct WIN32_FIND_DATA {
        public uint dwFileAttributes;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
        public uint nFileSizeHigh;
        public uint nFileSizeLow;
        public uint dwReserved0;
        public uint dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        public string cAlternateFileName;
    }

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    static extern IntPtr FindFirstFile
        (string lpFileName, out WIN32_FIND_DATA lpFindFileData);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    static extern bool FindNextFile
        (IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
    static extern bool FindClose(IntPtr hFindFile);

    static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
    static BlockingCollection<string> dirList {get;set;}
    static BlockingCollection<string> fileList {get;set;}

    public static BlockingCollection<string> GetFiles(string searchDir, string searchFile) {
        bool isPattern = false;
        if (searchFile.Contains(@"?") | searchFile.Contains(@"*")) {
            searchFile = @"^" + searchFile.Replace(@".",@"\.").Replace(@"*",@".*").Replace(@"?",@".") + @"$";
            isPattern = true;
        }
        fileList = new BlockingCollection<string>();
        dirList = new BlockingCollection<string>();
        dirList.Add(searchDir);
        int[] threads = Enumerable.Range(1,Environment.ProcessorCount).ToArray();
        Parallel.ForEach(threads, (id) => {
            string path;
            IntPtr handle = INVALID_HANDLE_VALUE;
            WIN32_FIND_DATA fileData;
            if (dirList.TryTake(out path, 100)) {
                do {
                    path = path.EndsWith(@"\") ? path : path + @"\";
                    handle = FindFirstFile(path + @"*", out fileData);
                    if (handle != INVALID_HANDLE_VALUE) {
                        FindNextFile(handle, out fileData);
                        while (FindNextFile(handle, out fileData)) {
                            if ((fileData.dwFileAttributes & 0x10) > 0) {
                                string fullPath = path + fileData.cFileName;
                                dirList.TryAdd(fullPath);
                            } else {
                                if (isPattern) {
                                    if (Regex.IsMatch(fileData.cFileName, searchFile, RegexOptions.IgnoreCase)) {
                                        string fullPath = path + fileData.cFileName;
                                        fileList.TryAdd(fullPath);
                                    }
                                } else {
                                    if (fileData.cFileName == searchFile) {
                                        string fullPath = path + fileData.cFileName;
                                        fileList.TryAdd(fullPath);
                                    }
                                }
                            }
                        }
                        FindClose(handle);
                    }
                } while (dirList.TryTake(out path));
            }
        });
        return fileList;
    }
}
"@

$fileList = [fileSearch]::GetFiles($searchDir, $searchFile)
$fileList

这篇关于通过Powershell查找给定文件的完整路径的最快方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-21 17:20