Можно ли завершить или остановить конвейер PowerShell из фильтра? - PullRequest
10 голосов
/ 10 ноября 2008

Я написал простой фильтр PowerShell, который проталкивает текущий объект по конвейеру, если его дата находится между указанной датой начала и окончания. Объекты, поступающие по конвейеру, всегда располагаются в порядке возрастания даты, поэтому, как только эта дата превысит указанную дату окончания, я знаю, что моя работа завершена, и я хотел бы сообщить конвейеру, что вышестоящие команды могут отказаться от своей работы, чтобы конвейер могу закончить свою работу. Я читаю некоторые очень большие файлы журнала, и мне часто захочется просмотреть только часть журнала. Я почти уверен, что это невозможно, но я хотел спросить, чтобы быть уверенным.

Ответы [ 8 ]

5 голосов
/ 19 июня 2015

Возможно разорвать конвейер с чем угодно, что в противном случае могло бы прервать внешний цикл или вообще остановить выполнение скрипта (например, выдать исключение). Тогда решение заключается в том, чтобы обернуть конвейер в цикл, который можно разорвать, если вам нужно остановить конвейер. Например, приведенный ниже код вернет первый элемент из конвейера, а затем прервет конвейер, нарушив внешний цикл do-while:

do {
    Get-ChildItem|% { $_;break }
} while ($false)

Эта функция может быть включена в такую ​​функцию, где последняя строка выполняет то же самое, что и выше:

function Breakable-Pipeline([ScriptBlock]$ScriptBlock) {
    do {
        . $ScriptBlock
    } while ($false)
}

Breakable-Pipeline { Get-ChildItem|% { $_;break } }
3 голосов
/ 19 марта 2012

Вы можете выдать исключение при завершении конвейера.

gc demo.txt -ReadCount 1 | %{$num=0}{$num++; if($num -eq 5){throw "terminated pipeline!"}else{write-host $_}}

или

Посмотрите на этот пост о том, как завершить конвейер:
http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx

3 голосов
/ 10 ноября 2008

Невозможно остановить вышестоящую команду из нижестоящей команды. Она продолжит отфильтровывать объекты, не соответствующие вашим критериям, но первая команда обработает все, что было задано для обработки.

Обходной путь будет заключаться в том, чтобы выполнить дополнительную фильтрацию для вышестоящего командлета или функции / фильтра. Работа с файлами журналов делает его немного более сложным, но, возможно, использование Select-String и регулярного выражения для фильтрации нежелательных дат может работать для вас.

Если вы не знаете, сколько строк вы хотите взять и откуда, весь файл будет считан для проверки шаблона.

2 голосов
/ 10 ноября 2008

Не уверен насчет ваших точных потребностей, но, возможно, стоит потратить время на Log Parser , чтобы узнать, не можете ли вы использовать запрос для фильтрации данных до того, как он попадет в канал.

1 голос
/ 19 февраля 2016

Если вы хотите использовать непубличных участников, вот способ остановить конвейер. Это имитирует то, что делает select-object. invoke-method (псевдоним im) - это функция для вызова закрытых методов. select-property (псевдоним selp) - это функция для выбора (аналогично select-object) непубличных свойств - однако она автоматически действует как -ExpandProperty, если найдено только одно соответствующее свойство. (Я написал select-property и invoke-method на работе, поэтому не могу поделиться с ними исходным кодом).

# Get the system.management.automation assembly
$script:smaa=[appdomain]::currentdomain.getassemblies()|
         ? location -like "*system.management.automation*"
# Get the StopUpstreamCommandsException class 
$script:upcet=$smaa.gettypes()| ? name -like "*StopUpstreamCommandsException *"

function stop-pipeline {
    # Create a StopUpstreamCommandsException
    $upce = [activator]::CreateInstance($upcet,@($pscmdlet))

    $PipelineProcessor=$pscmdlet.CommandRuntime|select-property PipelineProcessor
    $commands = $PipelineProcessor|select-property commands
    $commandProcessor= $commands[0]

    $ci = $commandProcessor|select-property commandinfo
    $upce.RequestingCommandProcessor | im set_commandinfo @($ci)

    $cr = $commandProcessor|select-property commandruntime
    $upce.RequestingCommandProcessor| im set_commandruntime @($cr)

    $null = $PipelineProcessor|
        invoke-method recordfailure @($upce, $commandProcessor.command)

    if ($commands.count -gt 1) {
      $doCompletes = @()
      1..($commands.count-1) | % {
        write-debug "Stop-pipeline: added DoComplete for $($commands[$_])"
        $doCompletes += $commands[$_] | invoke-method DoComplete -returnClosure
      }
      foreach ($DoComplete in $doCompletes) {
        $null = & $DoComplete
      }
    }

    throw $upce
}

РЕДАКТИРОВАТЬ: за комментарий mklement0:

Вот ссылка на блог чернил Nivot со сценарием в модуле «poke», который аналогичным образом предоставляет доступ непубличным участникам.

Что касается дополнительных комментариев, у меня нет значимых на данный момент. Этот код просто имитирует то, что показывает декомпиляция select-object. Оригинальные комментарии MS (если таковые имеются), конечно, не в декомпиляции. Честно говоря, я не знаю назначения различных типов, которые использует функция. Достижение такого уровня понимания, вероятно, потребует значительных усилий.

Мое предложение: возьмите модуль тыка Ойсина. Настройте код для использования этого модуля. А потом попробуй. Если вам нравится, как он работает, используйте его и не беспокойтесь о том, как он работает (вот что я сделал).

Примечание: я не изучал "poke" на любой глубине, но я думаю, что он не имеет ничего подобного -returnClosure. Однако добавить это должно быть легко, как это:

if (-not $returnClosure) {
  $methodInfo.Invoke($arguments)
} else {
  {$methodInfo.Invoke($arguments)}.GetNewClosure()
}
1 голос
/ 10 ноября 2008

Попробуйте эти фильтры, они заставят конвейер останавливаться после первого объекта - или первых n элементов - и сохранят их в переменной; вам нужно передать имя переменной, если вы не выталкиваете объект (ы), но не можете быть присвоены переменной.

filter FirstObject ([string]$vName = '') {
 if ($vName) {sv $vName $_ -s 1} else {$_}
 break
}

filter FirstElements ([int]$max = 2, [string]$vName = '') {
 if ($max -le 0) {break} else {$_arr += ,$_}
 if (!--$max) {
  if ($vName) {sv $vName $_arr -s 1} else {$_arr}
  break
 }
}

# can't assign to a variable directly
$myLog = get-eventLog security | ... | firstObject

# pass the the varName
get-eventLog security | ... | firstObject myLog
$myLog

# can't assign to a variable directly
$myLogs = get-eventLog security | ... | firstElements 3

# pass the number of elements and the varName
get-eventLog security | ... | firstElements 3 myLogs
$myLogs

####################################

get-eventLog security | % {
 if ($_.timegenerated -lt (date 11.09.08) -and`
  $_.timegenerated -gt (date 11.01.08)) {$log1 = $_; break}
}

#
$log1
0 голосов
/ 17 января 2016

Вот - несовершенная - реализация командлета Stop-Pipeline (требуется PS v3 +), с благодарностью адаптированного из этого ответа :

#requires -version 3
Filter Stop-Pipeline {
  $sp = { Select-Object -First 1 }.GetSteppablePipeline($MyInvocation.CommandOrigin)
  $sp.Begin($true)
  $sp.Process(0)
}

# Example
1..5 | % { if ($_ -gt 2) { Stop-Pipeline }; $_ } # -> 1, 2

Предостережение : Я не до конца понимаю, как это работает, хотя в основном он использует способность Select -First преждевременно останавливать конвейер (PS v3 +). Однако в этом случае есть одно принципиальное отличие от того, как Select -First завершает конвейер: downstream командлеты (команды позднее в конвейере) не получают возможность запуска их end блоков.
Следовательно, агрегирующие командлеты (те, которые должны получать все входные данные перед созданием вывода, такие как Sort-Object, Group-Object и Measure-Object) не будут производить вывод, если размещены позже в том же трубопроводе ; e.g.:

# !! NO output, because Sort-Object never finishes.
1..5 | % { if ($_ -gt 2) { Stop-Pipeline }; $_ } | Sort-Object

Справочная информация, которая может привести к лучшему решению:

Благодаря PetSerAl мой ответ здесь показывает, как создать то же исключение, которое Select-Object -First использует внутренне для остановки командлетов восходящего потока.

Однако, исключение выдается изнутри командлета, который сам подключен к конвейеру для остановки , что здесь не так:

Stop-Pipeline, как используется в приведенных выше примерах, не подключен к конвейеру, который должен быть остановлен (есть только включающий блок ForEach-Object (%)), поэтому вопрос : Как можно генерировать исключение в контексте целевого конвейера?

0 голосов
/ 16 ноября 2008

Другой вариант - использовать параметр -file в операторе switch. Использование -file будет читать файл по одной строке за раз, и вы можете использовать break для немедленного выхода без чтения остальной части файла.

switch -file $someFile {
  # Parse current line for later matches.
  { $script:line = [DateTime]$_ } { }
  # If less than min date, keep looking.
  { $line -lt $minDate } { Write-Host "skipping: $line"; continue }
  # If greater than max date, stop checking.
  { $line -gt $maxDate } { Write-Host "stopping: $line"; break }
  # Otherwise, date is between min and max.
  default { Write-Host "match: $line" }
}
...