started - ¿Es posible terminar o detener una tubería de PowerShell desde dentro de un filtro?
powershell scripts (8)
Aquí hay una implementación (imperfecta) de un cmdlet Stop-Pipeline
(requiere PS v3 +), adaptado con gratitud a partir de esta respuesta :
#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
Advertencia : no entiendo completamente cómo funciona, aunque fundamentalmente aprovecha la capacidad de Select -First
para detener la tubería prematuramente (PS v3 +). Sin embargo, en este caso hay una diferencia crucial respecto a cómo Select -First
termina la interconexión: los cmdlets descendentes (comandos posteriores en la interconexión) no tienen la posibilidad de ejecutar sus bloques end
.
Por lo tanto, la agregación de cmdlets (los que deben recibir todas las entradas antes de generar resultados, como Sort-Object
, Group-Object
y Measure-Object
) no producirán salida si se colocan más adelante en la misma interconexión ; p.ej:
# !! NO output, because Sort-Object never finishes.
1..5 | % { if ($_ -gt 2) { Stop-Pipeline }; $_ } | Sort-Object
Información de fondo que puede conducir a una mejor solución:
Gracias a PetSerAl , mi respuesta aquí muestra cómo producir la misma excepción que Select-Object -First
utiliza internamente para detener los cmdlets en sentido ascendente.
Sin embargo, allí se lanza la excepción desde el cmdlet que está conectado a la tubería para detenerse , que no es el caso aquí:
Stop-Pipeline
, como se usa en los ejemplos anteriores, no está conectado a la tubería que debe detenerse (solo se incluye el ForEach-Object
( %
)), por lo que la pregunta es: ¿cómo se puede lanzar la excepción en el contexto de la tubería objetivo?
He escrito un filtro simple de PowerShell que empuja el objeto actual por la tubería si su fecha se encuentra entre la fecha de inicio y la de finalización especificadas. Los objetos que salen de la tubería siempre están en orden de fecha ascendente, así que tan pronto como la fecha exceda la fecha de finalización especificada, sé que mi trabajo está hecho y me gustaría decir a la tubería que los comandos ascendentes pueden abandonar su trabajo para que la tubería puede terminar su trabajo. Estoy leyendo algunos archivos de registro muy grandes y con frecuencia quiero examinar solo una parte del registro. Estoy bastante seguro de que esto no es posible, pero quería preguntar para estar seguro.
Es posible romper una tubería con cualquier cosa que de otra manera rompería un bucle externo o detendría por completo la ejecución del script (como lanzar una excepción). La solución entonces es envolver la tubería en un bucle que puede romper si necesita detener la tubería. Por ejemplo, el código siguiente devolverá el primer elemento de la tubería y luego romperá la tubería rompiendo el ciclo de do-while externo:
do {
Get-ChildItem|% { $_;break }
} while ($false)
Esta funcionalidad se puede ajustar a una función como esta, donde la última línea logra lo mismo que arriba:
function Breakable-Pipeline([ScriptBlock]$ScriptBlock) {
do {
. $ScriptBlock
} while ($false)
}
Breakable-Pipeline { Get-ChildItem|% { $_;break } }
No es posible detener un comando en sentido ascendente desde un comando en sentido descendente ... continuará filtrando los objetos que no coinciden con sus criterios, pero el primer comando procesará todo lo que se configuró para procesar.
La solución alternativa consistirá en filtrar más en el cmdlet o la función / filtro en sentido ascendente. Trabajar con archivos de registro lo hace un poco más complicado, pero tal vez usar Select-String y una expresión regular para filtrar las fechas no deseadas podrían funcionar para usted.
A menos que sepa cuántas líneas quiere tomar y desde dónde, se leerá todo el archivo para verificar el patrón.
No estoy seguro de sus necesidades exactas, pero puede valer la pena ver Log Parser para ver si no puede usar una consulta para filtrar los datos antes de que lleguen al final.
Otra opción sería usar el parámetro -file
en una instrucción switch
. Usando -file
leerá el archivo una línea a la vez, y puede usar break
para salir inmediatamente sin leer el resto del archivo.
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" }
}
Pruebe estos filtros, forzarán a la tubería a detenerse después del primer objeto -o los primeros n elementos- y lo almacenan -ellos- en una variable; debe pasar el nombre de la variable, si no lo hace, los objetos se expulsan pero no se pueden asignar a una variable.
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
Puede lanzar una excepción al finalizar la interconexión.
gc demo.txt -ReadCount 1 | %{$num=0}{$num++; if($num -eq 5){throw "terminated pipeline!"}else{write-host $_}}
o
Mira esta publicación sobre cómo terminar una canalización:
http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx
Si está dispuesto a usar miembros no públicos aquí, hay una manera de detener la tubería. Imita lo select-object
hace el select-object
. invoke-method
(alias im
) es una función para invocar métodos no públicos. select-property
(alias selp
) es una función para seleccionar (similar a select-object) propiedades no públicas; sin embargo, actúa automáticamente como -ExpandProperty
si solo se encuentra una propiedad coincidente. (Escribí select-property
y invoke-method
en el trabajo, por lo que no puedo compartir el código fuente de esos).
# 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
}
EDIT: comentario de per mklement0:
Aquí hay un enlace al blog de Nivot ink sobre un script en el módulo "poke" que de manera similar da acceso a miembros no públicos.
En cuanto a los comentarios adicionales, no tengo los significativos en este punto. Este código simplemente imita lo que revela una descompilación del select-object
. Los comentarios originales de MS (si los hay) no están en la descompilación. Francamente, no sé el propósito de los diversos tipos que utiliza la función. Obtener ese nivel de comprensión probablemente requerirá una cantidad considerable de esfuerzo.
Mi sugerencia: obtener el módulo de poke de Oisin. Ajusta el código para usar ese módulo. Y luego pruébalo. Si te gusta la forma en que funciona, úsala y no te preocupes por cómo funciona (eso es lo que hice).
Nota: No he estudiado "poke" en ninguna profundidad, pero mi suposición es que no tiene nada como -returnClosure
. Sin embargo, agregar eso debería ser tan fácil como esto:
if (-not $returnClosure) {
$methodInfo.Invoke($arguments)
} else {
{$methodInfo.Invoke($arguments)}.GetNewClosure()
}