Как ускорить получение Powershell Get-Childitem над UNC

DIR или GCI медленнее в Powershell, но быстро в CMD. Есть ли способ ускорить это?

В CMD.exe после задержки подсекции это срабатывает так же быстро, как CMD-окно может поддерживать

dir \\remote-server.domain.com\share\folder\file*.*

В Powershell (v2) после 40 + секундной задержки это отвечает заметной медлительностью (возможно, 3-4 строки в секунду)

gci \\remote-server.domain.com\share\folder\file*.*

Я пытаюсь проверять журналы на удаленном сервере, поэтому, возможно, есть более быстрый подход.

get-childitem \\$s\logs -include $filemask -recurse | select-string -pattern $regex

Ответы

Ответ 1

Здесь является хорошим объяснением того, почему Get-ChildItem медленнее Ли Холмсом. Если вы обратите внимание на комментарий от "Anon 11 Mar 2010 11:11 AM" в нижней части страницы, его решение может сработать для вас.

Код анона:

# SCOPE: SEARCH A DIRECTORY FOR FILES (W/WILDCARDS IF NECESSARY)
# Usage:
# $directory = "\\SERVER\SHARE"
# $searchterms = "filname[*].ext"
# PS> $Results = Search $directory $searchterms

[reflection.assembly]::loadwithpartialname("Microsoft.VisualBasic") | Out-Null

Function Search {
  # Parameters $Path and $SearchString
  param ([Parameter(Mandatory=$true, ValueFromPipeline = $true)][string]$Path,
  [Parameter(Mandatory=$true)][string]$SearchString
  )
  try {
    #.NET FindInFiles Method to Look for file
    # BENEFITS : Possibly running as background job (haven't looked into it yet)

    [Microsoft.VisualBasic.FileIO.FileSystem]::GetFiles(
    $Path,
    [Microsoft.VisualBasic.FileIO.SearchOption]::SearchAllSubDirectories,
    $SearchString
    )
  } catch { $_ }

}

Ответ 2

Хорошо, вот как я это делаю, и, похоже, работает.

$files = cmd /c "$GETFILESBAT \\$server\logs\$filemask"
foreach( $f in $files ) {
    if( $f.length -gt 0 ) {
        select-string -Path $f -pattern $regex | foreach-object { $_ }
    }
}

Тогда $ GETFILESBAT указывает на это:

@dir /a-d /b /s %1
@exit

Я пишу и удаляю этот BAT файл из сценария PowerShell, поэтому я предполагаю, что это решение только для PowerShell, но оно не использует только PowerShell.

Мои предварительные показатели эффективности показывают, что это в одиннадцать тысяч раз быстрее.

Я тестировал gci vs cmd dir и FileIO.FileSystem.GetFiles из ссылки @Shawn Melton , ссылка.

Суть в том, что для ежедневного использования на локальных дисках GetFiles является самым быстрым. Безусловно. CMD DIR респектабельно. Как только вы вводите более медленное сетевое соединение со многими файлами, CMD DIR немного быстрее, чем GetFiles. Затем Get-ChildItem... вау, это варьируется от не очень плохого до ужасного, в зависимости от количества задействованных файлов и скорости соединения.

Некоторые тестовые прогоны. Я переместил GCI в тестах, чтобы убедиться, что результаты были последовательными.

10 итераций сканирования c:\windows\temp для файлов *.tmp

.\test.ps1 "c:\windows\temp" "*.tmp" 10
GetFiles ... 00:00:00.0570057
CMD dir  ... 00:00:00.5360536
GCI      ... 00:00:01.1391139

GetFiles в 10 раз быстрее CMD dir, что само по себе более чем в 2 раза быстрее GCI.

10 итераций сканирования c:\windows\temp для файлов *.tmp с рекурсией

.\test.ps1 "c:\windows\temp" "*.tmp" 10 -recurse
GetFiles ... 00:00:00.7020180
CMD dir  ... 00:00:00.7644196
GCI      ... 00:00:04.7737224

GetFiles немного быстрее, чем CMD dir, и оба почти в 7 раз быстрее, чем GCI.

10 итераций сканирования локального сервера в другом домене на наличие файлов журнала приложений

.\test.ps1 "\\closeserver\logs\subdir" "appname*.*" 10
GetFiles ... 00:00:00.3590359
CMD dir  ... 00:00:00.6270627
GCI      ... 00:00:06.0796079

GetFiles примерно в 2 раза быстрее, чем CMD dir, сам по себе в 10 раз быстрее, чем GCI.

Одна итерация сканирования удаленного сервера в другом домене на наличие файлов журналов приложений со многими файлами

.\test.ps1 "\\distantserver.company.com\logs\subdir" "appname.2011082*.*"
CMD dir  ... 00:00:00.3340334
GetFiles ... 00:00:00.4360436
GCI      ... 00:11:09.5525579

CMD dir быстрее всего отправляется на удаленный сервер со многими файлами, но GetFiles находится довольно близко. С другой стороны, GCI работает в пару тысяч раз медленнее.

Две итерации сканирования удаленного сервера в другом домене на наличие файлов журнала приложений со многими файлами

.\test.ps1 "\\distantserver.company.com\logs\subdir" "appname.20110822*.*" 2
CMD dir  ... 00:00:00.9360240
GetFiles ... 00:00:01.4976384
GCI      ... 00:22:17.3068616

Более или менее линейное увеличение с увеличением итераций теста.

Одна итерация сканирования на удаленном сервере в другом домене файлов журналов приложений с меньшим количеством файлов

.\test.ps1 "\\distantserver.company.com\logs\othersubdir" "appname.2011082*.*" 10
GetFiles ... 00:00:00.5304170
CMD dir  ... 00:00:00.6240200
GCI      ... 00:00:01.9656630

Здесь GCI не так уж и плох, GetFiles в 3 раза быстрее, а CMD dir очень близок.

Заключение

GCI нужна опция -raw или -fast, которая не пытается сделать так много. Между тем, GetFiles - это здоровая альтернатива, которая лишь иногда немного медленнее, чем CMD dir, и обычно быстрее (из-за появления CMD.exe?).

Для справки, здесь код test.ps1.

param ( [string]$path, [string]$filemask, [switch]$recurse=$false, [int]$n=1 )
[reflection.assembly]::loadwithpartialname("Microsoft.VisualBasic") | Out-Null
write-host "GetFiles... " -nonewline
$dt = get-date;
for($i=0;$i -lt $n;$i++){
  if( $recurse ){ [Microsoft.VisualBasic.FileIO.FileSystem]::GetFiles( $path,
      [Microsoft.VisualBasic.FileIO.SearchOption]::SearchAllSubDirectories,$filemask
    )  | out-file ".\testfiles1.txt"}
  else{ [Microsoft.VisualBasic.FileIO.FileSystem]::GetFiles( $path,
      [Microsoft.VisualBasic.FileIO.SearchOption]::SearchTopLevelOnly,$filemask
    )  | out-file ".\testfiles1.txt" }}
$dt2=get-date;
write-host $dt2.subtract($dt)
write-host "CMD dir... " -nonewline
$dt = get-date;
for($i=0;$i -lt $n;$i++){
  if($recurse){
    cmd /c "dir /a-d /b /s $path\$filemask" | out-file ".\testfiles2.txt"}
  else{ cmd /c "dir /a-d /b $path\$filemask" | out-file ".\testfiles2.txt"}}
$dt2=get-date;
write-host $dt2.subtract($dt)
write-host "GCI... " -nonewline
$dt = get-date;
for($i=0;$i -lt $n;$i++){
  if( $recurse ) {
    get-childitem "$path\*" -include $filemask -recurse | out-file ".\testfiles0.txt"}
  else {get-childitem "$path\*" -include $filemask | out-file ".\testfiles0.txt"}}
$dt2=get-date;
write-host $dt2.subtract($dt)

Ответ 3

Я попробовал некоторые из предложенных методов с большим количеством файлов (~ 190.000). Как упоминалось в комментарии Кайла, GetFiles здесь не очень полезен, потому что он нужен почти навсегда.

cmd dir был лучше, чем Get-ChildItems на моих первых тестах, но, похоже, GCI ускоряется, если вы используете параметр -Force. При этом необходимое время было примерно таким же, как для cmd dir.

P.S.: В моем случае мне пришлось исключить большинство файлов из-за их расширения. Это было сделано с помощью -Exclude в gci и с |, где в других командах. Таким образом, результаты только для поиска файлов могут немного отличаться.

Ответ 4

Здесь интерактивный читатель, который анализирует cmd/c dir (который может обрабатывать unc пути) и соберет 3 наиболее важных свойства для большинства людей: полный путь, размер, временная метка

использование будет что-то вроде $files_with_details = $faster_get_files.GetFileList($unc_compatible_folder)

и есть вспомогательная функция для проверки объединенного размера $faster_get_files.GetSize($files_with_details)

$faster_get_files = New-Module -AsCustomObject -ScriptBlock {
    #$DebugPreference = 'Continue' #verbose, this will take figuratively forever
    #$DebugPreference = 'SilentlyContinue'
    $directory_filter = "Directory of (.+)"
    $file_filter = "(\d+/\d+/\d+)\s+(\d+:\d+ \w{2})\s+([\d,]+)\s+(.+)" # [1] is day, [2] is time (AM/PM), [3] is size,  [4] is filename
    $extension_filter = "(.+)[\.](\w{3,4})" # [1] is leaf, [2] is extension
    $directory = ""
    function GetFileList ($directory = $this.directory)
    {
        if ([System.IO.Directory]::Exists($directory))
        {
            # Gather raw file list
            write-Information "Gathering files..."
            $files_raw = cmd /c dir $folder\*.* /s/a-d

            # Parse file list
            Write-Information "Parsing file list..."
            $directory = $folder
            $files_with_details = foreach ($line in $files_raw)
            {
                Write-Debug "starting line {$($line)}"
                Switch -regex ($line)
                {
                    $this.directory_filter
                    {
                        $directory = $matches[1]
                        break
                    }
                    $this.file_filter
                    {
                        Write-Debug "parsing matches {$($matches.value -join ";")}"
                        $date     = $matches[1]
                        $time     = $matches[2] # am/pm style
                        $size     = $matches[3]
                        $filename = $matches[4]

                        # we do a second match here so as to not append a fake period to files without an extension, otherwise we could do a single match up above
                        Write-Debug "parsing extension from {$($filename)}"
                        if ($filename -match $this.extension_filter) 
                        {
                            $file_leaf = $matches[1]
                            $file_extension = $matches[2]
                        }
                        else
                        {
                            $file_leaf = $filename
                            $file_extension = ""
                        }
                        [pscustomobject][ordered]@{
                            "fullname"  = [string]"$($directory)\$($filename)"
                            "filename"  = [string]$filename
                            "folder"    = [string]$directory
                            "file_leaf" = [string]$file_leaf
                            "extension" = [string]$file_extension
                            "date"      = get-date "$($date) $($time)"
                            "size"      = [int]$size
                        }
                        break
                    } 
                } # finish directory/file test
            } # finish all files
            return $files_with_details
        } #finish directory exists test
        else #directory doesn't exist
        {throw("Directory not found")}
    }
    function GetSize($files_with_details)
    {
        $combined_size = ($files_with_details|measure -Property size -sum).sum
        $pretty_size_gb = "$([math]::Round($combined_size / 1GB, 4)) GB"
        return $pretty_size_gb
    }
    Export-ModuleMember -Function * -Variable *
}