Salida estructurada en PowerShell
Abstract
Esta lección introduce la idea de generar salida estructurada en PowerShell, un paso esencial para construir scripts reutilizables y fáciles de integrar con otros comandos. En lugar de imprimir texto sin formato, aprenderás a devolver datos en estructuras que PowerShell y otras herramientas pueden interpretar, visualizar y filtrar con facilidad.
Comenzamos explorando los diccionarios ( ) como
forma básica de agrupar pares clave–valor, útiles para almacenar configuraciones o pasar
parámetros mediante splatting. Luego avanzamos hacia [hashtable] [hashtable] , que permite producir resultados con propiedades reales y una presentación tabular
consistente. La lección culmina con un pequeño ejercicio práctico donde crearás un
comando que simula una comprobación de conexión y devuelve un objeto con el resultado de
éxito o fallo, reforzando la utilidad de esta representación estructurada.
[PSCustomObject] [PSCustomObject]
Diccionarios
Diccionarios en PowerShell
En PowerShell, los diccionarios se crean con la sintaxis
. Son tablas de hash que agrupan pares
clave–valor
y permiten acceder a cada valor por su nombre.
@{ Clave = Valor } @{ Clave = Valor }
#Requires -Version 7.5
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateNotNullOrWhiteSpace()]
[string] $FirstName,
[Parameter(Mandatory)]
[ValidateNotNullOrWhiteSpace()]
[string] $LastName
)
@{
FirstName = $FirstName
LastName = $LastName
} Detalles clave
- La sintaxis es
. Cada par está separado por punto y coma o salto de línea.@{ Clave1 = Valor1; Clave2 = Valor2 }@{ Clave1 = Valor1; Clave2 = Valor2 } - Las claves son únicas; si se repite una, su valor anterior se sobrescribe.
- No se garantiza el orden de inserción de los elementos.
Uso
dibs/scripts/models/ $result = ./New-Person.ps1 -FirstName Miles -LastName Edgeworth
# Acceder a los valores
$result.FirstName # Miles
$result["LastName"] # Edgeworth
# Mutar el diccionario (agregar y modificar valores)
$result.Age = 25
# Mostrar el nuevo estado del diccionario
$result Detalles clave
- Tipo: el resultado es un
, que almacena los pares clave–valor.[hashtable][hashtable] - Acceso: usa
o$h.Clave$h.Clavepara leer valores.$h["Clave"]$h["Clave"] - Mutabilidad: puedes agregar o modificar elementos con
(incluso si no existe) y eliminarlos con$h.Clave = Valor$h.Clave = Valor.Remove-Item -Key "Clave"Remove-Item -Key "Clave"
Splatting: pasar diccionarios como parámetros
En PowerShell puedes «desempaquetar» un
como parámetros nombrados usando el operador
[hashtable] [hashtable] (splatting). Esto mejora legibilidad, evita
líneas largas y facilita mantener scripts.
@ @
# Diccionario de parámetros
$copyParams = @{
Path = './data/input.txt'
Destination = './backup'
Force = $true
Verbose = $true
}
# Splatting: pasa las claves como parámetros nombrados
Copy-Item @copyParams
# Mutabilidad: se puede modificar antes de reutilizar
$copyParams.Recurse = $true
Copy-Item @copyParamsCopy-Item -Path './data/input.txt' -Destination './backup' -Force -Verbose
Copy-Item -Path './data/input.txt' -Destination './backup' -Force -Verbose -Recurse Detalles clave
-
crea un diccionario (@{ Clave = Valor }@{ Clave = Valor }).[hashtable][hashtable] -
aplica sus pares como parámetros nombrados del cmdlet.@diccionario@diccionario - Los valores
se interpretan como switches de presencia o ausencia.[bool][bool]
Importante
[PSCustomObject] [PSCustomObject] .
Continuación de líneas con ` `
` `
Una alternativa a usar splatting para separar líneas largas es usar el
carácter de «escape» ( ) al final
de la línea. Sin embargo, esto
no se recomienda fuera de casos muy puntuales, ya que es propenso a
errores difíciles de detectar. El caracter especial ` ` debe ser el último de la línea.
` `
# Esto funciona
Write-Output `
"Hello, World!"
# Pero...
Write-Output ` # ...esto ya no
"Hello, World!" ` ` (y antes de
# # ) en la segunda llamada hace que falle.
PowerShell recomienda otras formas de continuar líneas, puedes encontrar más información en la documentación oficial.
Objetos personalizados
[PSCustomObject] [PSCustomObject] @{
[PSCustomObject]@{
FirstName = $FirstName
LastName = $LastName
} Detalles clave
[PSCustomObject]@{ ... } [PSCustomObject]@{ ... } : Es un «type
accelerator» 1 que convierte un [hashtable] [hashtable] en una instancia de [PSCustomObject] [PSCustomObject] . Observa que lo
único que cambia respecto del ejemplo con un diccionario es el prefijo [PSCustomObject] [PSCustomObject] . Dentro sigues declarando propiedades con pares Nombre = Valor Nombre = Valor . El resultado es un objeto con propiedades reales (no solo entradas en un
diccionario), apto para mostrarse en tabla y para interoperar con el ecosistema de
cmdlets de salida.
Beneficios de [PSCustomObject] [PSCustomObject] frente a
[hashtable] [hashtable] 2
[PSCustomObject] [PSCustomObject] [hashtable] [hashtable]
Aunque las son útiles como estructuras
clave–valor,
[hashtable] [hashtable] ofrece ventajas importantes al generar
salida estructurada o devolver datos desde cmdlets. Además, encaja mejor con el
modelo de encadenamiento de comandos (pipeline) que veremos más adelante.
[PSCustomObject] [PSCustomObject]
- Salida tabular predecible: las propiedades se muestran como columnas y conservan el orden en que fueron declaradas.Diferencia práctica:
vs[hashtable][hashtable][PSCustomObject][PSCustomObject]# Hashtable: mapa clave–valor (no tiene propiedades reales) $pokemonHT = @{ Name = "Toxtricity" Types = @("Electric", "Poison") } # Intentar seleccionar columnas por nombre no produce el resultado esperado Format-Table -Property Name, Types -InputObject $pokemonHT # PSCustomObject: convierte claves en propiedades reales (ideal para salida) $pokemonPSCO = [PSCustomObject]$pokemonHT # Ahora sí: columnas y valores aparecen como se espera Format-Table -Property Name, Types -InputObject $pokemonPSCOEste comportamiento coincide con lo descrito por la comunidad: un hashtable no se comporta como un objeto con propiedades al formatear columnas específicas; convertir aevita esa sorpresa.[PSCustomObject][PSCustomObject] - Control de presentación (TypeNames y propiedades por defecto): a los
se les puede asignar un TypeName y definir qué propiedades se muestran por defecto, mejorando la legibilidad sin perder datos. Para más detalles sobre cómo el sistema de tipos extiende estos comportamientos, consulta:[PSCustomObject][PSCustomObject]Explorar cómo utilizarTypes.ps1xmlGet-Help about_Types.ps1xml - Compatibilidad con el pipeline: muchos comandos esperan objetos con propiedades claras para seleccionar campos por nombre y aplicar formateadores. Los
están pensados para encajar en ese modelo. No entraremos en detalles aquí; basta con saber que más adelante esta elección facilita el flujo de trabajo.[PSCustomObject][PSCustomObject]
Importante
[PSCustomObject] [PSCustomObject] para mantener la
simplicidad y centrarnos en la forma de los datos. Si necesitas reglas más estrictas,
métodos, herencia o contratos de tipos más claros para herramientas y módulos, considera
migrar a clases.
Clases y salida tipada
PowerShell permite definir clases para modelar datos con propiedades y
métodos, aprovechando conceptos conocidos de OOP (encapsulación y herencia). Usarlas
mejora el autocompletado del editor y ayuda a detectar errores de tipos al escribir.
Para describir el tipo de salida de una función
existe el atributo (útil para
documentación y tooling), pero a nivel de script no podemos declarar un tipo
de salida explícito (esta es una ventaja de usar funciones o módulos que puedes
considerar más adelante).
[OutputType()] [OutputType()]
A continuación definimos una clase en un
módulo ([Person] [Person] .psm1) y luego la usamos desde un script (.ps1)
para crear y devolver un objeto.
class (módulo .psm1) class Person {
[string] $FirstName
[string] $LastName
Person([string] $first, [string] $last) {
$this.FirstName = $first
$this.LastName = $last
}
[string] ToString() {
return '{0} {1}' -f $this.FirstName, $this.LastName
}
}.psm1 es un módulo de PowerShell: puede exportar
tipos (clases), funciones u otros elementos para ser reutilizados. Aquí sólo
definimos la clase [Person] [Person] .
A diferencia de los scripts y funciones, los métodos deben tener un
return return explícito para devolver valores.
.ps1) using module ./Person.psm1
#Requires -Version 7.5
param(
[Parameter(Mandatory)]
[ValidateNotNullOrWhiteSpace()]
[string] $FirstName,
[Parameter(Mandatory)]
[ValidateNotNullOrWhiteSpace()]
[string] $LastName
)
# Función local al script (no se exporta): deja el flujo claro y permite reutilizar internamente.
function Script:New-Person {
[OutputType([Person])]
param ()
[Person]::new($FirstName, $LastName)
}
# Devolver la instancia tipada como salida del script
New-Person-
carga el módulo con la claseusing module ./Person.psm1using module ./Person.psm1.[Person][Person] -
El prefijo
hace queScript:Script:sea local al script (no se exporta).New-PersonNew-Person -
La salida del script es el objeto
recién creado.[Person][Person]
scripts/models/ $person = ./New-PersonClass.ps1 -FirstName Naoki -LastName Urasawa
$person.ToString() # "Naoki Urasawa"
$person.FirstName # "Naoki"
$person.LastName # "Urasawa"Para aprender más consulta la documentación oficial:
Get-Help about_Classes
Ejercicio: Comando con resultado de éxito o fallo
Requisitos
Diseña un script llamado que
simule la comprobación de conectividad hacia un servidor y devuelva un resultado
estructurado. El script debe aceptar un único parámetro: Test-ConnectionSummary.ps1 Test-ConnectionSummary.ps1 .
[string] $Address [string] $Address
La salida debe ser un objeto con tres
propiedades:
[PSCustomObject] [PSCustomObject]
-
Address: el nombre recibido como parámetro. -
Status: unque indique si la conexión fue exitosa o fallida.[bool][bool] -
CheckedAt: la fecha y hora de la comprobación (usa).Get-DateGet-Date
No es necesario conectarse realmente; puedes simular el resultado.
Notas
Para simular la conexión, usa una heurística simple: devolver éxito si el address cumple
la expresión regular
. 3 '^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*(?::\d+)?$' '^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*(?::\d+)?$'
Uso esperado
$res1 = ./Test-ConnectionSummary.ps1 -Address "toshihiko.kifuken.jp"
$res2 = ./Test-ConnectionSummary.ps1 -Address "shokujinki@net"
Format-Table -InputObject $res1, $res2Address Status CheckedAt
------- ------ ---------
toshihiko.kifuken.jp True 10/17/2025 8:55:44 PM
shokujinki@net False 10/17/2025 8:55:44 PM Solución
#Requires -Version 7.5
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateNotNullOrWhiteSpace()]
[string] $Address
)
[PSCustomObject]@{
Address = $Address
Status = $Address -match '^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*(?::\d+)?$'
CheckedAt = Get-Date
} [PSCustomObject] [PSCustomObject] . No se usa manejo de errores; la decisión de éxito/fallo se basa únicamente
en la expresión regular.
Conclusiones
En esta lección vimos cómo modelar datos en PowerShell para producir salida estructurada. Partimos con diccionarios ( [hashtable] [hashtable] ) —útiles para valores transitorios y splatting— y luego pasamos a
[PSCustomObject] [PSCustomObject] , que convierte claves en propiedades
reales y ofrece una presentación tabular consistente. También señalamos, de
forma breve, que esta elección facilita el encadenamiento de comandos que veremos más
adelante. La idea práctica: usa diccionarios para configurar/parametrizar, y
[PSCustomObject] [PSCustomObject] cuando quieras devolver datos.
Puntos clave
- Diccionarios:
; mutables, acceso por punto o índice, sin garantía de orden de inserción.@{ Clave = Valor }@{ Clave = Valor } - Splatting: usa
para pasar diccionarios como parámetros; mejora legibilidad y evita líneas largas duplicadas.@params@params -
:[PSCustomObject][PSCustomObject]crea objetos con propiedades reales; la salida se muestra en columnas de forma predecible.[PSCustomObject]@{ ... }[PSCustomObject]@{ ... } - Type accelerators:
es un acelerador; es decir, una forma abreviada de instanciar el tipo adecuado para salida estructurada.[PSCustomObject][PSCustomObject] - Cuándo usar qué: diccionarios para configuración y parámetros;
para devolver resultados y que otras herramientas/comandos los consuman fácilmente.[PSCustomObject][PSCustomObject]
¿Qué nos llevamos?
Prefiere [PSCustomObject] [PSCustomObject] cuando el objetivo sea comunicar datos: obtendrás columnas claras, nombres de campos estables y objetos
listos para ser reutilizados. Reserva los diccionarios para
armar llamadas y configurar comandos. Con este criterio simple tus scripts
serán más legibles hoy y, cuando conozcas el encadenamiento de comandos, gran parte del
trabajo ya estará hecho.
Comparativas y otros ecosistemas
¿Con ganas de más?
Referencias recomendadas
- “ Everything you wanted to know about PSCustomObject ” en Microsoft Learn
Ofrece una guía completa —desde lo básico hasta lo avanzado— sobre el uso de
en PowerShell para crear datos estructurados. Explica cómo convertir diccionarios en objetos, agregar o quitar propiedades dinámicamente, clonar instancias y definir comportamientos personalizados mediante[PSCustomObject][PSCustomObject]oAdd-MemberAdd-Member.Update-TypeDataUpdate-TypeDataTambién aborda la importancia del type system de PowerShell: cómo asignar
, establecer propiedades por defecto para mejorar la presentación en consola y definir tipos personalizados reutilizables en funciones y scripts. Finalmente, el texto enfatiza que dominarPSTypeNamePSTypeNamepermite producir salidas limpias, coherentes y tipadas, facilitando la interoperabilidad y el mantenimiento de los scripts.[PSCustomObject][PSCustomObject]
Notas
-
Los type accelerators son atajos definidos por PowerShell que permiten instanciar
tipos .NET sin escribir su nombre completo. Por ejemplo,
es un acelerador de[PSCustomObject][PSCustomObject]. Existen muchos otros, como[System.Management.Automation.PSObject][System.Management.Automation.PSObject]para[hashtable][hashtable]o[System.Collections.Hashtable][System.Collections.Hashtable]para[ValidateSet][ValidateSet]. Puedes aprender más sobre ellos con:[System.Management.Automation.ValidateSetAttribute][System.Management.Automation.ValidateSetAttribute]VolverExplorar los type accelerators# Mostrar la lista completa de type accelerators disponibles [PSObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::Get # Obtener documentación adicional Get-Help about_Type_Accelerators - Discusión de referencia: Why PS Custom Object? Volver
-
-
: etiqueta inicial (letras, dígitos, guiones).'[A-Za-z0-9-]+''[A-Za-z0-9-]+' -
: cero o más etiquetas separadas por puntos (soporta'(?:\.[A-Za-z0-9-]+)*''(?:\.[A-Za-z0-9-]+)*'localhostysub.example.com). -
: opcional'(?::\d+)?''(?::\d+)?':puerto(uno o más dígitos). -
Ancla
para que coincida toda la cadena.'^...$''^...$'
-
Coincide:
example.com,localhost,sub.domain.co,example.com:8080,192.168.1.1:3000 -
No coincide:
::1(IPv6), cadenas con espacios o caracteres inválidos.
VolverPrecaución
Es intencionalmente permisiva: no valida rangos de octetos IPv4 (p. ej.999.999.999.999pasaría) ni cumple estrictamente RFC para nombres de host. Úsala sólo como heurística ilustrativa. Para validación robusta (IPv6 completo, rangos de puerto 0–65535, reglas exactas RFC) conviene usar parsers/librerías o validaciones adicionales (por ejemplo utilizando el type accelerator).[IPAddress][IPAddress] -