Как ускорить получение 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 *
}