Deje de usar Write-Host — Flujos de salida de PowerShell y diseño de registros
· Actualizado el: · Go Komura · PowerShell, Windows, Registros, Mejora operativa, Automatización, Scripts, Diseño, Mantenibilidad
«El script funciona, pero cuando falla no sabemos qué pasó» — esta es la consulta más frecuente sobre scripts de PowerShell puestos en producción. Y cuando se investiga la causa, casi siempre se llega a la misma estructura: el estado del proceso solo se expresaba mediante Write-Host, y en una ejecución nocturna sin nadie mirando la pantalla no queda ningún rastro.
PowerShell dispone de seis tipos de flujos de salida, y cada uno tiene un destinatario distinto: si devuelve un valor, si está pensado para que lo lea una persona, o si solo hace falta durante una investigación. Si escribe el código teniendo esto en cuenta, el mismo script se comporta de forma amable en ejecución interactiva y de forma legible por máquina en ejecución desatendida. Por el contrario, si vuelca todo en Write-Host, solo aumenta la información que ni sirve como valor ni queda registrada.
En este artículo repasamos el papel de los seis flujos, la posición correcta de Write-Host, el mecanismo por el que se contamina el valor de retorno de una función, la forma de controlar el nivel de detalle desde el llamador y, por último, un formato de registro estructurado útil en producción.
1. Antes que nada, la conclusión
- Los flujos de salida de PowerShell son seis. Éxito (1), error (2), advertencia (3), detallado (4), depuración (5) e información (6); cada uno puede redirigirse por su número.
*>representa todos los flujos.1 - Desde PowerShell 5.0,
Write-Hostescribe en el flujo de información. Por eso ahora es posible redirigirlo con6>o capturarlo con-InformationVariable. Antes de esa versión no se podía ni capturar ni suprimir.2 Write-Hostestá pensado únicamente para «mostrar algo a una persona». No sirve para devolver valores. El valor que se pasa a la canalización (pipeline) se escribe conWrite-Output(o una salida sin comando).23- Una función devuelve todos los objetos que se generan en su interior, tenga o no una sentencia
return. La práctica habitual para descartar salidas no deseadas es$null = ....4 - El progreso se muestra con
Write-Progress, y el avance del proceso conWrite-Verbose. La visualización de progreso no es un flujo de datos redirigible.5 - Una función con
[CmdletBinding()]obtiene automáticamente parámetros comunes como-Verbose,-Debugo-InformationAction. Lo correcto es diseñarla para que sea el llamador quien decida el nivel de detalle.67 - Vale la pena recordar los valores predeterminados.
$VerbosePreference,$DebugPreferencee$InformationPreferencevalen SilentlyContinue;$WarningPreferencey$ErrorActionPreferencevalen Continue.8 - Si quiere analizar los registros más adelante, estructúrelos (un JSON por línea). Si además quiere reproducir el aspecto de la pantalla, combine esto con
Start-Transcript.9
La versión de referencia de este artículo y las diferencias en 5.1
En muchos departamentos de sistemas, Windows PowerShell 5.1 sigue siendo la versión predominante. Antes de continuar, aclaremos en qué versión se basa cada afirmación de este artículo.
| Descripción | Versión de referencia | En Windows PowerShell 5.1 |
|---|---|---|
Los seis flujos de salida y la redirección numerada, *> |
Igual en 5.1 y en PowerShell 7 | Se aplica sin cambios1 |
Write-Host escribe en el flujo de información (número 6) y puede capturarse con 6> o -InformationVariable |
Desde PowerShell 5.0 (capítulo 3) | 5.1 es posterior a 5.0, así que se aplica sin cambios2 |
Write-Information y -InformationAction / -InformationVariable |
Desde PowerShell 5.0 (capítulo 4) | Se aplica sin cambios7 |
Que la función devuelva todo lo que genera internamente y la supresión con $null = ... |
No depende de la versión (capítulo 5) | Se aplica sin cambios4 |
El tratamiento de 2>&1 en comandos externos (comandos nativos) |
Se describe como el comportamiento de PowerShell 7.4 en adelante (capítulo 6) | No se aplica. Compruebe siempre, en la versión que va a ejecutar, cómo se clasifica por tipo la salida de comandos externos |
Controlar Write-Progress con el parámetro común -ProgressAction |
Desde PowerShell 7.45 | No está disponible. Se controla con $ProgressPreference (el capítulo 8 está escrito con este enfoque) |
| El código de ejemplo distribuido al final del artículo | Verificado ejecutándolo en PowerShell 7.6 (14 pruebas de Pester) | — |
En resumen, los capítulos 2 a 5 y el capítulo 7 se pueden usar tal cual en 5.1. Los dos únicos puntos donde hay que prestar atención a la versión son la redirección de comandos externos (capítulo 6) y el método de control de la visualización de progreso (capítulo 8).
2. Los seis flujos y sus destinatarios
Empecemos con un panorama de qué comando de escritura llega a dónde.
flowchart LR
O["Write-Output / salida sin comando"]
OTH["Write-Error / Write-Warning<br/>Write-Verbose / Write-Debug<br/>Write-Information / Write-Host"]
S1["1 Flujo de éxito"]
S26["2 Error / 3 Advertencia / 4 Detallado<br/>5 Depuración / 6 Información"]
PIPE["Pasa al procesamiento posterior<br/>Canalización o asignación a variable"]
HOST["Aparece en pantalla<br/>Que aparezca por defecto depende de la variable de preferencia"]
FILE["Se puede conservar en un archivo<br/>Redirección numerada o variable de captura"]
O --> S1
OTH --> S26
S1 --> PIPE
S1 --> FILE
S26 --> HOST
S26 --> FILE
Solo el flujo de éxito pasa al procesamiento posterior. Los otros cinco, o aparecen en pantalla, o se capturan mediante redirección o parámetros -*Variable, pero nunca entran en la canalización. Si se equivoca en la bifurcación de la izquierda (con qué comando escribir), el error siempre acaba manifestándose: el valor no llega, o no queda registrado. Los números se usan para especificar la redirección, como en 3> warnings.log (capítulo 6).
| N.º | Flujo | Comando que escribe | Destinatario previsto | Preferencia predeterminada |
|---|---|---|---|---|
| 1 | Éxito (Success) | Write-Output / salida sin comando |
El procesamiento posterior (canalización) | — |
| 2 | Error (Error) | Write-Error / lanzar una excepción |
Persona + monitorización | Continue |
| 3 | Advertencia (Warning) | Write-Warning |
Persona | Continue |
| 4 | Detallado (Verbose) | Write-Verbose |
Persona que está investigando | SilentlyContinue |
| 5 | Depuración (Debug) | Write-Debug |
Persona desarrolladora | SilentlyContinue |
| 6 | Información (Information) | Write-Information / Write-Host |
Persona + registro | SilentlyContinue |
Que el valor predeterminado del flujo de información sea SilentlyContinue y que aun así Write-Host aparezca en pantalla no es una contradicción. Write-Host es la única excepción: Microsoft Learn indica expresamente que «la variable de preferencia $InformationPreference y el parámetro común -InformationAction no afectan a los mensajes de Write-Host».2 Es decir, Write-Information no aparece por defecto, pero Write-Host sí se muestra incluso por defecto, y solo se puede suprimir con -InformationAction Ignore o mediante la redirección 6>.
Lo más importante de esta tabla es la primera fila. El flujo de éxito no es el lugar para escribir «mensajes dirigidos a personas». Si escribe ahí una cadena pensada para que la lea alguien, en el momento en que encadene esa función con |, un texto inesperado se colará en el procesamiento posterior.
function Get-KsTargetFile {
Write-Output "Buscando los archivos objetivo..." # [MAL] se mezcla con el valor de retorno
Get-ChildItem -Path $path -Filter '*.csv'
}
# El llamador espera un arreglo de FileInfo, pero al principio llega una cadena
$files = Get-KsTargetFile
$files[0].FullName # → vacío (porque el primer elemento es una cadena)
Lo correcto es enviar los informes de progreso al flujo detallado o al flujo de información.
function Get-KsTargetFile {
[CmdletBinding()]
param([string] $Path)
Write-Verbose "Buscando los archivos objetivo: $Path" # solo se muestra si se indica -Verbose
Get-ChildItem -Path $Path -Filter '*.csv' # el valor de retorno es únicamente FileInfo
}
3. ¿Es Write-Host «malo»?
En su momento se popularizó la afirmación de que «no hay que usar Write-Host», pero en el PowerShell actual la situación ha cambiado. Desde PowerShell 5.0, Write-Host está implementado como una escritura en el flujo de información (número 6), por lo que se puede redirigir con 6> o capturar con -InformationVariable.2 La crítica de entonces, «solo se puede mostrar en pantalla y no se puede recuperar después», ya no es válida. Sin embargo, como se mencionó en el capítulo 2, aunque el valor predeterminado del flujo de información sea SilentlyContinue, la visualización de Write-Host no desaparece por eso: no le afectan $InformationPreference ni -InformationAction (la única excepción es -InformationAction Ignore, que sí suprime también la salida de Write-Host). Que «ahora se pueda capturar» y que «aparezca en pantalla por defecto» son dos cosas compatibles.2
Aun así, su uso está limitado a casos concretos.
Situaciones en las que Write-Host es adecuado
- En herramientas de uso interactivo, cuando quiere mostrar títulos o separadores en color (
-ForegroundColor) - Cuando quiere comunicar al usuario «qué va a hacer este script a continuación»
- Cuando el objetivo no es un valor del proceso, sino la propia presentación decorativa
Situaciones en las que no debe usar Write-Host
- Cuando quiere pasar un valor como retorno de una función (→
Write-Output) - Cuando quiere dejar un registro operativo que se analizará después (→ registro estructurado, o
Write-Information) - Cuando quiere que el llamador pueda alternar entre mostrar u ocultar el mensaje (→
Write-Verbose)
En un script de ejecución desatendida, para empezar no hay ningún destino donde mostrar nada. Un script que expresa su estado únicamente con Write-Host se convierte, en el momento en que se ejecuta desde el Programador de tareas, en «un script del que no se sabe nada». Ese es el sentido del título de este artículo.
4. Delegar el control al llamador — [CmdletBinding()] y los parámetros comunes
El verdadero valor de Write-Verbose está en que es el llamador quien decide si se muestra o no. Basta con añadir [CmdletBinding()] a la función para que queden disponibles automáticamente parámetros comunes como -Verbose, -Debug, -WarningAction, -InformationAction o -ErrorAction.67
function Invoke-KsImport {
[CmdletBinding()]
param(
[Parameter(Mandatory)] [string] $CsvPath
)
Write-Verbose "Inicio de la importación: $CsvPath" # no aparece por defecto
Write-Information "Importación: $CsvPath" -Tags 'KsImport' # no aparece por defecto (pero se puede capturar)
$rows = Import-Csv -Path $CsvPath
if ($rows.Count -eq 0) {
Write-Warning "$CsvPath no tiene nada que importar" # aparece por defecto
return
}
Write-Verbose "Se procesarán $($rows.Count) filas"
$rows | ForEach-Object { ConvertTo-KsRecord $_ } # esto es lo único que se devuelve
}
# Ejecución normal: solo se muestra la advertencia, y el retorno son los registros
$records = Invoke-KsImport -CsvPath 'D:\in\orders.csv'
# Durante una investigación: también se quiere ver el avance
$records = Invoke-KsImport -CsvPath 'D:\in\orders.csv' -Verbose
# Se captura solo el flujo de información en una variable, para volcarlo luego a un archivo de registro
$records = Invoke-KsImport -CsvPath 'D:\in\orders.csv' -InformationVariable info
$info | ForEach-Object { $_.MessageData } | Add-Content -Path $logPath
Es habitual encontrarse implementaciones que crean su propia variable $LogLevel y ramifican con sentencias if, pero apoyarse en el mecanismo estándar resulta más breve y comunica mejor la intención a quien lo lea. Que al añadir -Verbose aparezca el detalle es un conocimiento compartido por cualquiera que use PowerShell.
Por cierto, variables de preferencia como $VerbosePreference afectan al ámbito actual y a los ámbitos hijos.8 Si llama a una función con -Verbose, los cmdlets que se invocan dentro de ella también empiezan a producir salida detallada, por lo que a veces obtiene más salida de la esperada.
5. Cuando se contamina el valor de retorno de una función — una trampa propia de PowerShell
Las funciones de PowerShell devuelven todos los objetos generados en su interior, aunque no exista una sentencia return explícita.4 Se trata de una característica muy potente, pero también es la que más incidentes provoca.
function New-KsWorkFolder {
param([string] $Path)
New-Item -Path $Path -ItemType Directory # [TRAMPA] el DirectoryInfo se mezcla en el retorno
$list = [System.Collections.Generic.List[string]]::new()
$list.Add('log') # [TRAMPA 2] .Add() devuelve void, así que no causa daño
$sb = [System.Text.StringBuilder]::new()
$sb.Append('x') # [TRAMPA 3] el propio StringBuilder se devuelve
return $Path
}
$p = New-KsWorkFolder -Path 'D:\work' # $p termina siendo un arreglo de 3 elementos (DirectoryInfo, StringBuilder, string)
La solución consiste en «descartar la salida no deseada». Hay tres formas de escribirlo, pero $null = ... es la más ligera.
$null = New-Item -Path $Path -ItemType Directory # recomendado
New-Item -Path $Path -ItemType Directory | Out-Null # más lento por el uso de la canalización
[void] $sb.Append('x') # se usa a menudo con métodos de .NET
Este comportamiento se detecta de inmediato en cuanto se escribe una prueba. La importancia de las pruebas que «fijan la forma del valor de retorno» se trata en «Organización de pruebas de PowerShell con Pester».
6. Redirección y captura
Los flujos se pueden redirigir individualmente por número.1
.\Invoke-NightlyExport.ps1 3> warnings.log # solo las advertencias a un archivo aparte
.\Invoke-NightlyExport.ps1 4>&1 | Tee-Object -FilePath run.log # fusiona el flujo detallado con el de éxito
.\Invoke-NightlyExport.ps1 *> all.log # todos los flujos a un único archivo
.\Invoke-NightlyExport.ps1 2>&1 | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] }
> sobrescribe y >> añade al final. Ahora bien, tenga presente que al fusionar flujos mediante redirección, los tipos se mezclan. En el ejemplo anterior, al fusionar el flujo de error de un script o función de PowerShell, ese elemento sigue siendo un ErrorRecord, así que se puede clasificar por tipo tal como se muestra arriba.
En cambio, 2>&1 con un programa externo (comando nativo) es harina de otro costal. A partir de PowerShell 7.4, la salida redirigida se trata como flujo de bytes, y tras la fusión se convierte en cadenas de texto, por lo que la clasificación por ErrorRecord ya no funciona. Si necesita distinguir stdout de stderr en un comando externo, recíbalos por separado en lugar de fusionarlos (véase «Cómo invocar correctamente un exe externo desde PowerShell»).
La forma más segura de saber si la separación funciona como espera es probarla usted mismo una vez. Puede comprobarlo con un fragmento corto que genere salida tanto en el flujo de éxito como en el de advertencia.
# Solo la advertencia va a un archivo aparte. En pantalla solo queda 'Datos'
& { Write-Output 'Datos'; Write-Warning 'Aviso' } 3> warnings.log
Get-Content warnings.log # contiene el mensaje de advertencia; 'Datos' no aparece
# Todos los flujos a un único archivo
& { Write-Output 'Datos'; Write-Warning 'Aviso'; Write-Verbose 'Detalle' -Verbose } *> all.log
Get-Content all.log # contiene las tres salidas juntas
Si a pesar de usar 3> el archivo warnings.log está vacío, es que ese mensaje no se escribió en el flujo de advertencia (si se escribió con Write-Host, es 6>). La forma más rápida de diagnosticar «esperaba que apareciera en el registro y no aparece» es empezar por estas dos líneas.
Por cierto, *> es cómodo, pero al juntarlo todo, luego resulta difícil volver a separarlo por flujo. Si quiere agregarlo de forma automática, escriba cada flujo en un archivo distinto, o use el registro estructurado del siguiente capítulo.
Si quiere conservar por completo el rastro de la ejecución, Start-Transcript es una opción sencilla. Registra en un archivo de texto los comandos y la salida de la sesión, de modo que después puede reproducir «qué había en pantalla en aquel momento».9
Start-Transcript -Path "C:\Logs\export_$(Get-Date -f yyyyMMdd_HHmmss).log" -Append
try { Invoke-KsExport }
finally { Stop-Transcript }
7. Registros que se puedan analizar más tarde — registros estructurados
Un registro pensado para que lo lea una persona y otro pensado para que lo agregue una máquina son cosas distintas. Cuando quiere saber «cuántas veces apareció este error el mes pasado», un texto de formato libre se convierte en una competencia de resistencia con grep. Si lo escribe como un JSON por línea (JSON Lines), la agregación se resuelve enteramente con PowerShell.
function Write-KsLog {
[CmdletBinding()]
param(
[Parameter(Mandatory)] [ValidateSet('INFO','WARN','ERROR')] [string] $Level,
[Parameter(Mandatory)] [string] $Message,
[hashtable] $Data,
[string] $Path = $script:KsLogPath
)
$entry = [ordered]@{
ts = (Get-Date).ToString('o') # ISO 8601: facilita ordenar y cotejar
level = $Level
message = $Message
script = $MyInvocation.ScriptName
host = $env:COMPUTERNAME
user = $env:USERNAME
}
# La información adicional se guarda bajo data. Si se mezclara en el nivel
# superior, cuando el llamador pasara claves como level o user, sobrescribiría
# los campos básicos
if ($Data) { $entry['data'] = $Data }
# -Compress lo deja en una sola línea. Para añadir se usa Add-Content (UTF-8)
$entry | ConvertTo-Json -Compress -Depth 5 | Add-Content -Path $Path -Encoding utf8
# La visualización para personas se apoya en el mecanismo estándar
# (aparece en pantalla pero no devuelve ningún valor)
switch ($Level) {
# No se especifica -ErrorAction. Si se fijara aquí, el llamador no
# podría usar -ErrorAction Stop para convertirlo en un error terminante
'ERROR' { Write-Error $Message }
'WARN' { Write-Warning $Message }
default { Write-Verbose $Message }
}
}
# Ejemplo de uso
Write-KsLog -Level INFO -Message 'Importación completada' -Data @{ rows = 1250; file = 'orders.csv'; ms = 4210 }
# → {"ts":"...","level":"INFO","message":"Importación completada","script":"...","host":"...","user":"...",
# "data":{"rows":1250,"file":"orders.csv","ms":4210}}
La agregación quedaría así.
Get-Content 'C:\Logs\ks.log' |
ForEach-Object { $_ | ConvertFrom-Json } |
Where-Object { $_.level -eq 'ERROR' -and [datetime]$_.ts -ge (Get-Date).AddDays(-30) } |
Group-Object message | Sort-Object Count -Descending | Select-Object Count, Name
También existe la opción de apoyarse en la infraestructura estándar de registro de Windows (el registro de eventos y ETW). Si piensa en integrarse con herramientas de monitorización o en recopilar datos de varios equipos, esa opción resulta más ventajosa. La comparación de diseño se recoge en «Registro de eventos y ETW de Windows frente a registros estructurados», y la gestión de generaciones de archivos de registro en «PowerShell aplicado — investigación, archivado y generación de informes de registros».
8. Cómo tratar la visualización de progreso
Write-Progress utiliza la función de visualización de progreso del host, y no es un flujo de datos redirigible.5 Es decir, no se puede conservar en un registro. En una ejecución desatendida no hay ningún lugar donde mostrarlo, y además, según el entorno, el coste de actualizar el progreso puede no ser despreciable.
# Desactiva la visualización de progreso al principio de un script de ejecución desatendida
$ProgressPreference = 'SilentlyContinue'
Si quiere dejar constancia del progreso en el registro operativo, resulta más práctico escribir en el flujo detallado solo los hitos.
$i = 0
foreach ($row in $rows) {
$i++
if ($i % 100 -eq 0) { Write-Verbose "$i / $($rows.Count) filas completadas" }
...
}
9. Reglas prácticas de uso habitual (tabla de decisión)
| Información que quiere mostrar | Qué usar | Motivo |
|---|---|---|
| Valor que se pasa al procesamiento posterior | Write-Output / salida sin comando |
El flujo de éxito es exclusivo para datos3 |
| Avance del proceso (solo se quiere ver al investigar) | Write-Verbose |
El llamador lo controla con -Verbose7 |
| Suceso que se quiere registrar como parte de la operación | Write-Information + registro estructurado |
Se puede capturar con -InformationVariable2 |
| Presentación decorativa de una herramienta interactiva | Write-Host |
Pasa por el flujo de información, así que también se puede capturar2 |
| Situación prevista pero que merece atención | Write-Warning |
Se muestra por defecto y se captura con -WarningVariable8 |
| Fallo | Write-Error / throw |
Consulte el artículo dedicado sobre gestión de errores |
| Estado interno durante el desarrollo | Write-Debug |
Solo cuando se añade -Debug7 |
| Progreso | Write-Progress (solo en modo interactivo) |
No queda en el registro. Desactívelo en ejecución desatendida5 |
| Todo el rastro de la ejecución | Start-Transcript |
Úselo junto con su propio registro, como respaldo9 |
10. Resumen
- La salida de PowerShell se divide en seis flujos. El flujo de éxito es exclusivo para datos; si mezcla mensajes dirigidos a personas, rompe el valor de retorno.
- Desde PowerShell 5.0,
Write-Hostescribe en el flujo de información, así que se puede capturar, pero no sirve para devolver valores ni para el registro operativo. - Una función devuelve todo lo que genera en su interior. La práctica habitual es descartar la salida no deseada con
$null = .... - Si añade
[CmdletBinding()]y usaWrite-Verbose/Write-Information, puede delegar el control del nivel de detalle al llamador. Es más breve que una variable de nivel de registro propia y comunica mejor la intención. - Si va a agregar el registro más adelante, hágalo como registro estructurado con un JSON por línea. Si el objetivo es reproducir lo que apareció en pantalla, combínelo con
Start-Transcript. - La visualización de progreso no es un flujo de datos, así que no queda en el registro. En ejecución desatendida, lo más práctico es desactivarla con
$ProgressPreference = 'SilentlyContinue'.
Descarga del código de ejemplo
El código tratado en este artículo se distribuye ya empaquetado y listo para ejecutarse. Incluye el registro estructurado con un JSON por línea y la forma de escribir funciones que no contaminan el valor de retorno.
Descargar el código de ejemplo (zip)
Los ejemplos de este artículo se han verificado ejecutándolos realmente en PowerShell 7.6 (14 pruebas de Pester). Si ejecuta Invoke-SampleTests.ps1, incluido en el zip, podrá reproducir la misma verificación en su propio equipo.
# Análisis de sintaxis + análisis estático + pruebas de Pester
./Invoke-SampleTests.ps1
Los valores de configuración (rutas, nombres de servidor, ID de inquilino, etc.) son solo ejemplos. No los ejecute tal cual en un entorno de producción: adáptelos al entorno de su organización.
Artículos relacionados
- Gestión de errores y diseño de reintentos en PowerShell — de la trampa en la que try/catch no funciona a las reglas habituales de exit code y reintentos
- Diseño de argumentos y modularización de scripts de PowerShell — de un «script que funciona» a un «script que se puede entregar a otra persona»
- PowerShell aplicado — automatizar de forma segura la investigación, el archivado y la generación de informes de registros
- Registro de eventos y ETW de Windows frente a registros estructurados
- Organización de pruebas de PowerShell con Pester — un patrón práctico para que los scripts operativos sean más difíciles de romper
- Requisitos mínimos de un logger personalizado y lista de verificación para pruebas de integración
Áreas de consultoría relacionadas
KomuraSoft LLC se ocupa de revisar el diseño de registros de scripts operativos, de resolver la situación de «no saber por qué falló» y de preparar la infraestructura de registro que conecta con la monitorización y la agregación.
- Consultoría técnica y revisión de diseño
- Investigación de fallos y análisis de causas
- Modificación y mantenimiento de software Windows existente
- Contacto
Referencias
-
Microsoft Learn, about_Redirection. Sobre los flujos de éxito, error, advertencia, detallado, depuración e información de PowerShell, identificados cada uno por un número; la redirección a archivo con
>y>>; la fusión con otro flujo medianten>&1; y la redirección de todos los flujos con*>. ↩ ↩2 ↩3 -
Microsoft Learn, Write-Host. Sobre cómo, desde PowerShell 5.0, Write-Host pasó a funcionar como envoltorio de Write-Information que escribe en el flujo de información, lo que permitió redirigirlo con 6>; sobre que la variable de preferencia $InformationPreference y el parámetro común -InformationAction no afectan a los mensajes de Write-Host (la excepción es -InformationAction Ignore), por lo que se muestra en pantalla incluso por defecto; sobre la decoración con -ForegroundColor / -BackgroundColor; y sobre que la salida no se pasa a la canalización. En relación con esto, Write-Information trata la escritura explícita en el flujo de información y su clasificación mediante -Tags. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, Write-Output. Sobre el envío de objetos al flujo de éxito (la canalización), y sobre que el resultado de una expresión se genera igual aunque no se llame explícitamente. ↩ ↩2
-
Microsoft Learn, about_Return. Sobre que las funciones de PowerShell devuelven al llamador todos los objetos generados en su interior, tengan o no una sentencia return, y sobre que return es la sintaxis para devolver un valor y salir del ámbito actual a la vez. ↩ ↩2 ↩3
-
Microsoft Learn, Write-Progress. Sobre cómo se muestra el avance de un comando mediante la visualización de progreso del host, sobre el control de esa visualización con $ProgressPreference, y sobre que desde PowerShell 7.4 también se puede controlar con el parámetro común -ProgressAction. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, about_Functions_CmdletBindingAttribute. Sobre cómo una función avanzada con el atributo [CmdletBinding()] se comporta igual que un cmdlet compilado, y sobre la disponibilidad automática de los parámetros comunes. ↩ ↩2
-
Microsoft Learn, about_CommonParameters. Sobre el comportamiento de -Verbose / -Debug / -WarningAction / -InformationAction / -ErrorAction y de los parámetros -*Variable correspondientes, y su relación con las variables de preferencia. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, about_Preference_Variables. Sobre que el valor predeterminado de $VerbosePreference, $DebugPreference e $InformationPreference es SilentlyContinue, y el de $WarningPreference y $ErrorActionPreference es Continue; sobre el control de la visualización de progreso mediante $ProgressPreference; y sobre que estas variables se aplican al ámbito actual y a los ámbitos hijos. ↩ ↩2 ↩3
-
Microsoft Learn, Start-Transcript. Sobre el registro en un archivo de texto de los comandos y la salida de consola de la sesión, sobre añadir al final con -Append, y sobre detener el registro con Stop-Transcript. ↩ ↩2 ↩3
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
Dónde mirar cuando un script de PowerShell es lento — claves de arrays, pipeline y cruces de datos
Analizamos las causas típicas de la lentitud en PowerShell: += en arrays, pipeline vs. foreach, cruces con tablas hash, E/S de archivos y...
Procesamiento paralelo en PowerShell — Cuándo usar ForEach-Object -Parallel y cuándo usar jobs
Diferencias entre ForEach-Object -Parallel, Start-ThreadJob y Start-Job, uso de $using:, ThrottleLimit y cuándo el paralelismo resulta má...
Cómo llamar correctamente a un exe externo desde PowerShell — la trampa de las comillas en argumentos, los códigos de salida y la codificación de caracteres
Al llamar a robocopy o a un EXE interno desde PowerShell, los argumentos se corrompen, no se obtiene el código de salida o la salida se v...
El manejo seguro de credenciales en PowerShell — Cómo desterrar las contraseñas en texto plano de los scripts
Organiza el procedimiento para migrar las contraseñas en texto plano de scripts de PowerShell a un almacenamiento seguro: SecureString, D...
Diseño de parámetros y modularización de scripts de PowerShell — de un «script que funciona» a un «script que se puede entregar»
Explicamos cómo llevar un script de PowerShell a una calidad entregable: param, [CmdletBinding()], validación, pipeline, -WhatIf, módulos...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Servicios relacionados con este tema
El artículo está directamente relacionado con los siguientes servicios.
Desarrollo de aplicaciones para Windows
Aplicaciones empresariales, integración de dispositivos y herramientas de comunicación, de los requisitos al desarrollo.
Preguntas frecuentes
Preguntas habituales en las consultas sobre el tema del artículo.
- ¿No se debe usar Write-Host?
- No es que esté prohibido, sino que su uso es limitado; esa es la interpretación correcta. Desde PowerShell 5.0, Write-Host escribe en el flujo de información (número 6), lo que permite redirigirlo con 6> o capturarlo con -InformationVariable. Aun así, es un comando que parte de la premisa de mostrarse siempre en pantalla por defecto, y no sirve para hacer fluir un valor por la canalización. La distinción de uso queda así: Write-Host para presentaciones decorativas que va a leer una persona en una herramienta interactiva; Write-Verbose o Write-Information para el avance del proceso o información complementaria; y Write-Output (o una salida sin comando) para el valor que se pasa al procesamiento posterior.
- El valor de retorno de mi función se mezcla con valores que no esperaba.
- Es porque las funciones de PowerShell devuelven todos los objetos generados en su interior, aunque no exista una sentencia return explícita. Si invoca sin capturar comandos o métodos que devuelven un valor, como New-Item o el .Append() de StringBuilder, ese valor de retorno fluye por el flujo de éxito y llega al llamador. En cambio, los métodos cuyo retorno es void, como el .Add() de List[T], no generan ninguna salida, así que no hace falta suprimirlos. La solución consiste en descartar la salida no deseada con $null = ..., añadir | Out-Null, o convertirla con [void]. En cuanto a rendimiento, $null = ... es la forma más ligera de escribirlo.
- Quiero poder activar o desactivar el registro detallado de un script en tiempo de ejecución.
- Escriba el avance intermedio del proceso con Write-Verbose y añada [CmdletBinding()] a la función. Con eso basta para que solo se muestre cuando el llamador indica -Verbose. Si quiere que se muestre siempre, establezca $VerbosePreference = 'Continue' al principio del script. De la misma manera, Write-Debug se controla desde el llamador con -Debug, y Write-Warning con -WarningAction. En lugar de crear su propia variable de nivel de registro, apoyarse en el mecanismo estándar de PowerShell comunica mejor la intención a quien lea el código después.
- ¿Cómo puedo conservar en un archivo todo, incluidos el registro detallado y las advertencias?
- Hay tres formas según el uso que le quiera dar. Si simplemente quiere volcar todos los flujos a un archivo, redirija con *>. Si quiere conservar por completo, como rastro de la ejecución, lo que apareció en pantalla, Start-Transcript resulta sencillo. Si quiere analizarlo más adelante mediante un programa, lo más seguro es escribir un registro estructurado de un JSON por línea con su propia función de registro, y en ese caso vale la pena combinarlo también con la transcripción, como respaldo.
- ¿Se puede conservar en un archivo de registro lo que muestra Write-Progress?
- No se puede. La visualización de progreso es una función de presentación del host y se trata de forma distinta a los flujos de datos redirigibles. En una ejecución desatendida no hay ningún destino donde mostrarlo, así que conviene tratar el progreso por separado de la información que se conserva en el registro. En ejecución desatendida, detener la propia visualización de progreso con $ProgressPreference = 'SilentlyContinue' puede incluso acelerar de forma notable el proceso, según el entorno. Si quiere dejar constancia del progreso en el registro, resulta más práctico escribir con Write-Verbose solo los hitos, como «completadas 50 de 100 filas».
Perfil del autor
Página de presentación del autor del artículo.
Go Komura
Representante de KomuraSoft LLC
Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.