Pipelines II: Pipeline-awareness
Metadatos de la lección
- Autoría:
- Ignacio Slater-Muñoz
- Última actualización:
- 27 de marzo de 2026
Cambios recientes:
-
8880728· 27 de marzo de 2026 · ✨ feat(notes): add abstract slots and Python structured-output lesson ( GitLab / GitHub ) -
677f4ea· 26 de marzo de 2026 · 🔖📝 chore(release): bump version to 0.13.1 and update changelog ( GitLab / GitHub ) -
90aede1· 26 de marzo de 2026 · ✨📚 feat(fonts,lessons): add Arrow primitive and sharpen pipeline-aware lesson ( GitLab / GitHub )
Encuentra el código de la lección:
Abstract
En esta lección aprenderás a escribir código pipeline-aware que se comporta como cmdlets reales: reciben objetos desde el pipeline, procesan cada entrada de forma incremental y emiten resultados que siguen siendo componibles. Esto aplica a scripts, funciones, filtros personalizados y cualquier componente que participar en automatización.
Trabajarás con , begin begin y process process , además de
estrategias de binding por valor y por nombre de propiedad para conectar
productores distintos sin romper el flujo de objetos.
end end
Crear scripts compatibles con el pipeline
Pipeline-awareness
En PowerShell, se logra con tres bloques: uno para inicialización (si es necesaria), otro para procesar cada objeto que llega, y otro para limpieza final (si es necesaria).
-
: se ejecuta una sola vez al inicio. Úsalo solo si necesitas inicializar recursos costosos (una conexión, un contador, una colección). Si no hay setup, omítelo.beginbegin -
: el corazón. Se invoca para cada objeto que llega por el pipeline. Aquí transformas, validas, o calculas; y emites un objeto que sigue fluyendo.processprocess -
: se ejecuta una sola vez al cierre. Úsalo para liberar recursos o generar un resumen final. Si no hay cleanup, omítelo.endend
Punto clave
processprocess — los bloques beginbegin y endend son opcionales.
Tip
- Emite objetos, no texto: usa
o instancias de clases, no[PSCustomObject][PSCustomObject]ni formatos. El objeto que emites sigue fluyendo hacia el siguiente cmdlet.Write-HostWrite-Host - Serializa solo al final: formatea (JSON, tablas, CSV) después de procesar todo el pipeline, nunca en el medio. Early formatting rompe la capacidad de composición.
- Procesa incrementalmente: cada invocación de
maneja un objeto, permitiendo streaming de datos enormes sin llenar memoria.processprocess
Para ver esto en acción, veamos un ejemplo mínimo. El siguiente script cuenta objetos mientras llegan:
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
[int] $Number
)
begin { $count = 0 }
process { $count++ }
end { [PSCustomObject]@{ ItemsProcessed = $count } }
Al ejecutar , el script recibe cinco
números uno a uno en 1..5 | ./Get-Count.ps11..5 | ./Get-Count.ps1, suma el contador en cada
invocación, y al final emite un objeto con propiedades nombradas. Nota que el resultado
es estructurado y reutilizable, no texto sin formato.
processprocess
ByValue: pasando valores directamente por el pipeline
Veamos un ejemplo sencillo de enlace ByValue. Este script recibe
números desde el pipeline, los duplica y usa para mostrar cuándo se ejecuta cada bloque.
Write-Verbose Write-Verbose
#Requires -Version 7.5
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline, Mandatory)]
[int] $Number
)
begin {
Set-StrictMode -Version 3.0
Write-Verbose '[begin] Starting pipeline...'
$count = 0
}
process {
Write-Verbose "[process] Processing number: $Number"
$count++
[PSCustomObject]@{
Original = $Number
Doubled = $Number * 2
}
}
end {
Write-Verbose "[end] Processed $count numbers in total."
} Detalles clave
-
: indica que el parámetroValueFromPipelineValueFromPipelinepuede recibir valores que vienen desde el pipeline. En este caso, el valor entrante se enlaza directamente a$Number$Numberporque PowerShell puede asociarlo o convertirlo al tipo$Number$Number. A esto se le llama enlace (o binding) by value. Antes de cada ejecución de[int][int], PowerShell intenta realizar ese enlace para el objeto entrante. Eso permite escribirprocessprocesssin construir listas ni llamar manualmente al script para cada elemento.1..5 | ./Get-DoubledNumber.ps11..5 | ./Get-DoubledNumber.ps1 - Emisión de objetos en
: la última expresión del bloqueprocessprocessemite unprocessprocesscon las propiedades[PSCustomObject][PSCustomObject]yOriginalOriginal. Ese objeto sigue fluyendo por el pipeline y puede ser filtrado, ordenado, exportado o serializado.DoubledDoubledno emite ese objeto: sus mensajes viajan por el verbose stream, mientras queWrite-VerboseWrite-Verbosetrabaja sobre el output stream que contiene los objetos reales.Where-ObjectWhere-Object
1..5 |
./Get-DoubledNumber.ps1 -Verbose |
Where-Object { $_.Doubled -gt 5 }scripts/pipeline VERBOSE: [begin] Starting pipeline...
VERBOSE: [process] Processing number: 1
...
VERBOSE: [process] Processing number: 5
VERBOSE: [end] Processed 5 numbers in total.
Original Doubled
-------- -------
3 6
4 8
5 10
En la salida se observan los mensajes VERBOSE (diagnóstico del ciclo de
ejecución) y, debajo, el resultado real: objetos con propiedades Original y Doubled. La opción es solo ilustrativa: -Verbose -Verbose escribe en el verbose stream, no en el
mismo flujo que los objetos emitidos por Write-Verbose Write-Verbose . Por eso process process filtra los objetos del pipeline y no los mensajes
de diagnóstico.
Where-Object Where-Object
ByPropertyName: un comando, múltiples productores
funciona cuando PowerShell
puede enlazar directamente el valor u objeto entrante al parámetro.
ValueFromPipeline ValueFromPipeline entra en juego
cuando no quieres enlazar el objeto completo, sino extraer propiedades
concretas del objeto entrante.
ValueFromPipelineByPropertyName ValueFromPipelineByPropertyName
A diferencia del ejemplo anterior, aquí lo importante no es el valor en sí, sino cómo se llaman las propiedades del objeto que entra al pipeline. En cada iteración, PowerShell toma un único objeto entrante y trata de rellenar los parámetros buscando nombres compatibles en ese objeto. No combina propiedades de productores distintos automáticamente.
-
ByValue: PowerShell intenta enlazar directamente el objeto o valor entrante al parámetro. -
ByPropertyName: PowerShell intenta rellenar el parámetro buscando una propiedad compatible en el objeto entrante.
Ejemplo: validar integridad de archivos. Recibiremos objetos que expongan una ruta y, opcionalmente, un hash esperado, usando nombres de propiedad compatibles.
#Requires -Version 7.5
[CmdletBinding()]
param(
[Parameter(ValueFromPipelineByPropertyName, Mandatory)]
[Alias('FullName', 'LiteralPath')]
[string] $Path,
[Parameter(ValueFromPipelineByPropertyName)]
[Alias('Hash')]
[string] $ExpectedHash,
[ValidateSet('SHA1', 'SHA256', 'SHA384', 'SHA512', 'MD5')]
[string] $Algorithm = 'SHA256'
)
begin {
Set-StrictMode -Version 3.0
}
process {
$actual = (Get-FileHash -LiteralPath $Path -Algorithm $Algorithm).Hash
[PSCustomObject]@{
Path = $Path
Algorithm = $Algorithm
ActualHash = $actual
ExpectedHash = $ExpectedHash
Match = if ($ExpectedHash) { $actual -eq $ExpectedHash } else { $null }
}
}En este ejemplo, PowerShell puede resolver el binding así:
-
path,Path,FullNameoLiteralPath→Path -
hashoHash→ExpectedHash
Detalles clave
-
: indica que PowerShell debe rellenar el parámetro buscando una propiedad en el objeto que llega por el pipeline. En este cmdlet,ValueFromPipelineByPropertyNameValueFromPipelineByPropertyNamePathyExpectedHashpueden obtenerse directamente desde propiedades del productor, sin consumir el objeto completo ni escribir «adaptadores» con.Select-ObjectSelect-Object -
: permite declarar nombres alternativos que PowerShell acepta para el binding por propiedad. Por ejemplo, si el productor entregaAlias()Alias()FullName(como) o[FileInfo][FileInfo]LiteralPath, el parámetroPathigual se rellena. De forma análoga,ExpectedHashpuede venir comoHashsin necesidad de renombrar el productor. -
:(Get-FileHash ...).Hash(Get-FileHash ...).Hashdevuelve un objeto. Al acceder a la propiedadGet-FileHashGet-FileHashextraemos solo el valor del hash como string, que luego se compara con.Hash.Hashpara calcularExpectedHashExpectedHash.MatchMatch
Lo importante es que la salida se mantiene como objetos estructurados
(Path, ActualHash, ExpectedHash, Match), lo que permite seguir componiendo el pipeline: filtrar por Match, ordenar, exportar a JSON/CSV, etc.
Si el objeto entrante no trae una propiedad compatible con , el parámetro queda sin valor y
ExpectedHash ExpectedHash se emite como Match Match .
$null $null
El mismo comando, dos fuentes de objetos distintas
La ventaja de es que el comando no queda
amarrado a un único productor. Puede recibir objetos de orígenes distintos,
siempre que cada objeto tenga propiedades con nombres compatibles (o aliases
compatibles).
ValueFromPipelineByPropertyName ValueFromPipelineByPropertyName
En este ejemplo, el productor entrega una ruta y (opcionalmente) un hash
esperado. Como ExpectedHash no es obligatorio, el comando
también puede usarse solo para calcular hashes. La idea central aquí es mostrar
dos fuentes de objetos distintas: JSON y CLIXML.
ConvertFrom-Json ConvertFrom-Json (binding
desde path y hash) # Productor 1: manifiesto JSON (propiedades path y hash)
Get-Content tests/checksums.json |
ConvertFrom-Json |
./pipeline/Test-FileHash.ps1 |
Where-Object { $_.Match -eq $false } |
Select-Object -First 10 Nota rápida sobre el manifiesto JSON
En JSON basta con exponer propiedades compatibles (path y
hash) para que el binding por nombre funcione sin
adaptadores. Puedes revisar ejemplos completos en el repositorio de scripts de DIBS.
Import-CliXml Import-CliXml
(binding desde propiedades exportadas)
# Productor 2: CLIXML (objetos exportados por PowerShell)
Import-CliXml tests/checksums.xml |
./pipeline/Test-FileHash.ps1 |
Where-Object { $_.Match -eq $false } |
Select-Object -First 10 ¿Qué es CLIXML?
CLIXML («Common Language Infrastructure XML» ) es el formato que
PowerShell (y otros lenguajes de .NET) usa para serializar y deserializar
objetos manteniendo su estructura (propiedades) de forma confiable.
Típicamente se genera con
y se lee con Export-CliXml Export-CliXml .
Import-CliXml Import-CliXml
Igual que para JSON, puedes encontrar archivos CLIXML de ejemplo en el repositorio de scripts de DIBS.
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
<Obj RefId="0">
<TN RefId="0">
<T>System.Management.Automation.PSCustomObject</T>
<T>System.Object</T>
</TN>
<MS>
<S N="Path">tests/fixtures/alpha.txt</S>
<S N="ExpectedHash">F19...</S>
</MS>
</Obj>
<Obj RefId="1">
<TNRef RefId="0" />
<MS>
<S N="Path">tests/fixtures/beta.txt</S>
<S N="ExpectedHash">196...</S>
</MS>
</Obj>
...
</Objs>
Como ExpectedHash es opcional, el mismo comando también puede
usarse solo para calcular hashes y seguir procesando esos resultados en el
pipeline.
Orden de resolución de nombres
Cuando PowerShell intenta rellenar parámetros con , sigue este orden:
ValueFromPipelineByPropertyName ValueFromPipelineByPropertyName
- Coincidencia exacta con el nombre del parámetro (por ejemplo,
Path). - Coincidencia con algún alias declarado con
(por ejemplo,Alias()Alias()FullNameopath). - Si existe ambigüedad (más de un parámetro podría recibir la misma propiedad), PowerShell lanza un error de binding.
El binding por nombre es case-insensitive: Path,
path o PATH se consideran equivalentes.
Ejercicio:
Auditoría de servicios — validar cumplimiento desde JSON y CSV
Requisitos
En entornos reales, la “línea base” de configuración suele almacenarse en distintos formatos según el origen: APIs exportan JSON, mientras que equipos operativos muchas veces mantienen planillas CSV.
El objetivo no es solo validar servicios, sino demostrar que un mismo cmdlet puede
integrarse con productores distintos gracias a .
ValueFromPipelineByPropertyName ValueFromPipelineByPropertyName
En este ejercicio diseña un flujo con estos bloques:
- Entrada:
- Lea un manifiesto en JSON.
- Lea un manifiesto equivalente en CSV.
-
- Procesamiento:
- Pase ambos al mismo cmdlet
Test-ServiceCompliance.ps1. - Filtre solo los incumplimientos.
- Distinga en el reporte entre servicio ausente y servicio presente con estado incorrecto.
- Si
devuelve múltiples coincidencias, emita un resultado por cada servicio.Get-Service -Name $NameGet-Service -Name $Name - Intente resolver el ejercicio sin adaptadores intermedios de renombrado (por ejemplo, sin
antes del cmdlet).Select-ObjectSelect-Object
-
- Salida:
- Genere un reporte final en JSON.
- La salida debe mantener el mismo esquema tanto para entrada JSON como para entrada CSV.
-
El manifiesto define qué servicios deberían estar en determinado estado:
[
{ "name": "AppMgmt", "expectedStatus": "Stopped" },
{ "name": "Appinfo", "expectedStatus": "Stopped" },
{ "name": "NONEXISTENT_SERVICE", "expectedStatus": "Running" },
{ "name": "ALG", "expectedStatus": "Running" },
{ "name": "AppReadiness", "expectedStatus": "Stopped" },
{ "name": "BITS", "expectedStatus": "Stopped" }
]"NAME","EXPECTED_STATUS"
"AppMgmt","Stopped"
"Appinfo","Stopped"
"NONEXISTENT_SERVICE","Running"
"ALG","Running"
"AppReadiness","Stopped"
"BITS","Stopped"[
{
"Name": "NONEXISTENT_SERVICE",
"ExpectedStatus": "Running",
"Status": null,
"Exists": false,
"Match": false,
"NonComplianceType": "MissingService"
}
] Notas
: lee un archivo CSV y
convierte cada fila en un objeto donde los encabezados de columna se
transforman en propiedades.
Import-Csv Import-Csv
Legible por máquina: mantén el flujo como objetos mientras procesas.
Usa solo al final, cuando vayas
a guardar o exportar el reporte.
ConvertTo-Json ConvertTo-Json
Puedes consultar el estado de un servicio con: . Esto devuelve una matriz de servicios (si el nombre es ambiguo), uno solo (si
existe) o Get-Service -Name $Name -ErrorAction SilentlyContinueGet-Service -Name $Name -ErrorAction SilentlyContinue (si no existe).
$null$null
Uso esperado
# Productor 1: JSON
Get-Content tests/services.json |
ConvertFrom-Json |
./pipeline/Test-ServiceCompliance.ps1 |
# ...
# Productor 2: CSV
Import-Csv -Path tests/services.csv |
./pipeline/Test-ServiceCompliance.ps1 |
# ... Hints
- Serializa solo al final. Emite objetos mientras procesas; usa
solo al cierre del pipeline.ConvertTo-JsonConvertTo-Json - Asegúrate de que tu objeto emita claramente si es "MissingService" o "WrongStatus" para que quien consume el reporte entienda por qué falló.
-
es útil para servicios ambiguos (0, 1, o múltiples coincidencias).ForEach-ObjectForEach-Object
Solución
#Requires -Version 7.5
[CmdletBinding()]
param(
[Parameter(ValueFromPipelineByPropertyName, Mandatory)]
[string] $Name,
[Parameter(ValueFromPipelineByPropertyName, Mandatory)]
[Alias('expected_status')]
[string] $ExpectedStatus
)
begin {
Set-StrictMode -Version 3.0
}
process {
$services = @(Get-Service -Name $Name -ErrorAction SilentlyContinue)
if ($services.Count -eq 0) {
[PSCustomObject]@{
Name = $Name
ExpectedStatus = $ExpectedStatus
Status = $null
Exists = $false
Match = $false
NonComplianceType = 'MissingService'
}
}
else {
$services | ForEach-Object {
$isMatch = $_.Status -eq $ExpectedStatus
[PSCustomObject]@{
Name = $_.Name
ExpectedStatus = $ExpectedStatus
Status = $_.Status
Exists = $true
Match = $isMatch
NonComplianceType = if ($isMatch) { $null } else { 'WrongStatus' }
}
}
}
}Import-Csv -Path ./tests/services.csv |
./pipeline/Test-ServiceCompliance.ps1 |
Where-Object { -not $_.Match } |
Select-Object Name, ExpectedStatus, Status, NonComplianceType |
ConvertTo-Json |
Set-Content -Path "non-compliant.json"Conclusiones
La esencia de pipeline-awareness es procesar datos en streaming: cada objeto que llega se transforma y emite inmediatamente, sin acumular. Esto requiere tres disciplinas:
- Emitir objetos, no texto: mantén datos estructurados dentro del pipeline para que otros comandos puedan componerse.
- Serializar al final: formatea (JSON, tablas) solo después de haber procesado todo, no en el medio del flujo.
- Reutilizar por contrato: define parámetros con aliases para que el cmdlet funcione con múltiples productores sin adaptadores intermedios.
Cuando combinas y
ValueFromPipeline ValueFromPipeline , tu mismo
cmdlet se vuelve agnóstico respecto al origen de datos. Eso es lo que hace que
PowerShell escale: pequeños comandos que cooperan a través de objetos bien
definidos.
ValueFromPipelineByPropertyName ValueFromPipelineByPropertyName
Puntos clave
-
es el corazón;processprocessybeginbeginson opcionales.endend - Emite objetos, no strings formateados.
- Serializa solo al final del pipeline, nunca en el medio.
- Usa aliases en parámetros para que
funcione con múltiples productores.ValueFromPipelineByPropertyNameValueFromPipelineByPropertyName
¿Qué nos llevamos?
Pipeline-awareness transforma tus scripts de herramientas aisladas a componentes que cooperan. Pensar en streaming, en objetos reutilizables y en contratos claros entre productor y consumidor permite construir automatizaciones más consistentes, más fáciles de combinar y más sencillas de extender sin reescribir cada etapa del flujo.
La clave es pensar en contratos reutilizables entre componentes, no en casos específicos.
¿Con ganas de más?
Referencias recomendadas
- “The pipeline, deeper” (pp. 114-131) en Learn PowerShell in a month of lunches, fourth edition: Covers windows, linux, and MacOS por Travis PlunkEl capítulo explica cómo PowerShell liga objetos en el pipeline tanto por valor como por nombre de propiedad, muestra adaptadores con
o paréntesis y contiene ejemplos (hashes, servicios, módulos, Azure) para que quien lee evalúe si necesita dominar estos patrones antes de invertir tiempo en la lectura.Select-ObjectSelect-Object
Notas
- Término propio. Volver