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
} ¿Qué acabamos de hacer?
- 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 ¿Qué acabamos de hacer?
- 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 ¿Qué acabamos de hacer?
-
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
} ¿Qué acabamos de hacer?
[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] -