variable - Write-Host vs Write-Information en PowerShell 5
write out powershell (5)
Es bien sabido que Write-Host
es malo. En PowerShell 5
, Write-Information
se agrega y se considera que reemplaza a Write-Host
.
Pero, en serio, ¿cuál es mejor?
Write-Host
es malvado porque no utiliza canalización, por lo que el mensaje de entrada no puede reutilizarse.
Pero, ¿qué hace Write-Host
para mostrar algo en la consola, verdad? ¿En qué caso reutilizaremos la entrada?
De todos modos, si realmente queremos reutilizar la entrada, ¿por qué no escribir algo como esto?
$foo = "Some message to be reused like saving to a file"
Write-Host $foo
$foo | Out-File -Path "D:/foo.log"
Otra desventaja de Write-Host
es que, Write-Host
puede especificarse en qué color se muestran los mensajes en la consola usando -ForegroundColor
y -BackgroundColor
.
Por otro lado, al usar Write-Information
, el mensaje de entrada se puede usar en cualquier lugar que queramos a través de la tubería No.6. Y no necesita escribir los códigos adicionales como escribo arriba. Pero el lado oscuro de esto es que, si queremos escribir mensajes en la consola y también guardarlos en el archivo, tenemos que hacer esto:
# Always set the $InformationPreference variable to "Continue"
$InformationPreference = "Continue";
# if we don''t want something like this:
# ======= Example 1 =======
# File Foo.ps1
$InformationPreference = "Continue";
Write-Information "Some Message"
Write-Information "Another Message"
# File AlwaysRunThisBeforeEverything.ps1
./Foo.ps1 6>"D:/foo.log"
# ======= End of Example 1 =======
# then we have to add ''6>"D:/foo.log"'' to every lines of Write-Information like this:
# ======= Example 2 =======
$InformationPreference = "Continue";
Write-Information "Some Message" 6>"D:/foo.log"
Write-Information "Another Message" 6>"D:/foo.log"
# ======= End of Example 2 =======
Un poco redundante, creo.
Solo conozco un pequeño aspecto de esta cosa "vs", y debe haber algo fuera de mi mente. Entonces, ¿hay algo más que pueda hacerme creer que Write-Information
es mejor que Write-Host
favor, deje sus amables respuestas aquí.
Gracias.
Aquí hay una versión genérica de una función de registro más especializada que utilicé recientemente para un script mío.
El escenario para esto es que cuando necesito hacer algo como una tarea programada, normalmente creo un script genérico o una función en un módulo que hace el "trabajo pesado" y luego un script de llamada que maneja los detalles específicos del trabajo en particular, como Obtención de argumentos de una configuración XML, registro, notificaciones, etc.
La secuencia de comandos interna utiliza Write-Error , Write-Warning y Write-Verbose , la secuencia de comandos de la llamada redirige todos los flujos de salida a través de la tubería a esta función, que captura los mensajes en un archivo csv con una marca de tiempo, nivel y mensaje.
En este caso, estaba dirigido a PoSh v.4, así que básicamente estoy usando Write-Verbose como un complemento para Write-Information, pero la misma idea. Si tuviera que usar Write-Host en Some-Script.ps1 (ver ejemplo) en lugar de Write-Verbose, o Write-Information, la función Add-LogEntry no capturaría ni registraría el mensaje. Si desea usar esto para capturar más transmisiones de manera apropiada, agregue entradas a la instrucción de cambio para satisfacer sus necesidades.
El conmutador -PassThru en este caso fue básicamente una forma de abordar exactamente lo que mencionó sobre la escritura en un archivo de registro además de la salida a la consola (o a otra variable, o en el proceso). En esta implementación, he agregado una propiedad "Nivel" al objeto, pero espero que pueda ver el punto. Mi caso de uso para esto fue pasar las entradas del registro a una variable para que pudieran verificarse los errores y usarse en una notificación SMTP si ocurría un error.
function Add-LogEntry {
[CmdletBinding()]
param (
# Path to logfile
[Parameter(ParameterSetName = ''InformationObject'', Mandatory = $true, Position = 0)]
[Parameter(ParameterSetName = ''Normal'', Mandatory = $true, Position = 0)]
[String]$Path,
# Can set a message manually if not capturing an alternate output stream via the InformationObject parameter set.
[Parameter(ParameterSetName = ''Normal'', Mandatory = $true)]
[String]$Message,
# Captures objects redirected to the output channel from Verbose, Warning, and Error channels
[ValidateScript({ @("VerboseRecord", "WarningRecord", "ErrorRecord") -Contains $_.GetType().name })]
[Parameter(ParameterSetName = ''InformationObject'', Mandatory = $true, ValueFromPipeline = $true)]
$InformationObject,
# If using the message parameter, must specify a level, InformationObject derives level from the object.
[ValidateSet("Information", "Warning", "Error")]
[Parameter(ParameterSetName = ''Normal'', Mandatory = $true, Position = 2)]
[String]$Level,
# Forward the InformationObject down the pipeline with additional level property.
[Parameter(ParameterSetName = ''InformationObject'', Mandatory = $false)]
[Switch]$PassThru
)
Process {
# If using an information object, set log entry level according to object type.
if ($PSCmdlet.ParameterSetName -eq "InformationObject") {
$Message = $InformationObject.ToString()
# Depending on the object type, set the error level,
# add entry to cover "Write-Information" output here if needed
switch -exact ($InformationObject.GetType().name) {
"VerboseRecord" { $Level = "Information" }
"WarningRecord" { $Level = "Warning" }
"ErrorRecord" { $Level = "Error" }
}
}
# Generate timestamp for log entry
$Timestamp = (get-date).Tostring("yyyy/-MM/-dd/_HH/:mm/:ss.ff")
$LogEntryProps = @{
"Timestamp" = $Timestamp;
"Level" = $Level;
"Message" = $Message
}
$LogEntry = New-Object -TypeName System.Management.Automation.PSObject -Property $LogEntryProps
$LogEntry | Select-Object Timestamp, Level, Message | Export-Csv -Path $Path -NoTypeInformation -Append
if ($PassThru) { Write-Output ($InformationObject | Add-Member @{Level = $Level } -PassThru) }
}
}
Ejemplo de uso sería
& $PSScriptRoot/Some-Script.ps1 -Param $Param -Verbose *>&1 | Add-LogEntry -Path $LogPath -PassThru
El conmutador -PassThru esencialmente debe escribir el objeto de información en la consola si no captura la salida en una variable o si se la pasa a otra cosa.
Debo admitir que odio el registro de PowerShell y todos los comandos Write-*
... Así que comienzo todos mis scripts con la misma función:
function logto{ ## Outputs data to Folder tree
Param($D,$P,$F,$C,$filename)
$LogDebug = $false
$FDomain =[System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$SCRdir = $MyInvocation.ScriptName
$FDNSName = $FDomain.Name
$RealFile = $F
if($ScriptName -eq $null){
$ScriptName = "/LogTo/"
}
## if there is a time stamp defined make it part of the directory
if($GlobalRunTime){
$Flocaldrive = $env:SystemDrive + "/" + $FDNSName + $ScriptName + $GlobalRunTime + "/"
If ($LogDebug) {Write-host "Set path to $Flocaldrive" -foregroundcolor Magenta}
}else{
$Flocaldrive = $env:SystemDrive + "/" + $FDNSName + $ScriptName
If ($LogDebug) {Write-host "Set path to $Flocaldrive" -foregroundcolor Magenta}
}
## do not write null data
if ($D -eq $null) {
If ($LogDebug) {Write-host "$RealFile :Received Null Data Exiting Function" -foregroundcolor Magenta}
Return
}
## if no path is chosen default to
if ($P -eq $null) {
$PT = $Flocaldrive
If ($LogDebug) {Write-host "Path was Null, setting to $PT" -foregroundcolor Magenta}
}else{
$PT = $Flocaldrive + $P
If ($LogDebug) {Write-host "Path detected as $p, setting path to $PT" -foregroundcolor Magenta}
}
## anything with no file goes to Catchall
If ($RealFile-eq $null) {
If ($LogDebug) {Write-host "$D :attempting to write to Null file name, redirected out to Catchall" -foregroundcolor Magenta}
$RealFile= "/Catchall.txt"
}
##If color is blank DONT write to screen
if ($C -eq $null) {
If ($LogDebug) {Write-host "Color was blank so not writing to screen" -foregroundcolor Magenta}
}else{
If ($LogDebug) {Write-host "Attempting to write to console in $C" -foregroundcolor Magenta}
write-host $D -foregroundcolor $C
}
###### Write standard format
$DataFile = $PT + $RealFile## define path with File
## Check if path Exists if not create it
If (Test-Path $PT) {
If ($LogDebug) {Write-host "$PT :Directory Exists" -foregroundcolor Magenta}
}else{
New-Item $PT -type directory | out-null ## if directory does not exist create it
If ($LogDebug) {Write-host "Creating directory $PT" -foregroundcolor Magenta}
}
## If file exist if not create it
If (Test-Path $DataFile) { ## If file does not exist create it
If ($LogDebug) {Write-host "$DataFile :File Exists" -foregroundcolor Magenta}
}else{
New-Item $DataFile -type file | out-null ## if file does not exist create it, we cant append a null file
If ($LogDebug) {Write-host "$DataFile :File Created" -foregroundcolor Magenta}
}
## Write our data to file
$D | out-file -Filepath $DataFile -append ## Write our data to file
## Write to color coded files
if ($C -ne $null) {
$WriteSumDir = $Flocaldrive + "Log/Sorted"
$WriteSumFile = $WriteSumDir + "/Console.txt"
## Check if path Exists if not create it
If (Test-Path $WriteSumDir) {
If ($LogDebug) {Write-host "$WriteSumDir :Directory Exists" -foregroundcolor Magenta}
}else{
New-Item $WriteSumDir -type directory | out-null ## if directory does not exist create it
If ($LogDebug) {Write-host "Creating directory $WriteSumDir" -foregroundcolor Magenta}
}
## If file does not exist create it
If (Test-Path $WriteSumFile) {
If ($LogDebug) {Write-host "$WriteSumFile :File Exists" -foregroundcolor Magenta}
}else{
New-Item $WriteSumFile -type file | out-null ## if file does not exist create it, we cant append a null file
If ($LogDebug) {Write-host "$WriteSumFile :File Created" -foregroundcolor Magenta}
}
## Write our data to file
$D | out-file -Filepath $WriteSumFile -append ## write everything to same file
## Write our data to color coded file
$WriteColorFile = $WriteSumDir + "/$C.txt"
If (Test-Path $WriteColorFile) { ## If file does not exist create it
If ($LogDebug) {Write-host "$WriteColorFile :File Exists" -foregroundcolor Magenta}
}else{
New-Item $WriteColorFile -type file | out-null ## if file does not exist create it, we cant append a null file
If ($LogDebug) {Write-host "$WriteColorFile :File Created" -foregroundcolor Magenta}
}
## Write our data to Color coded file
$D | out-file -Filepath $WriteColorFile -append ## write everything to same file
}
## If A return was not specified
If($filename -ne $null){
Return $DataFile
}
}
Los cmdlets Write-*
permiten canalizar la salida de su código de PowerShell de una manera estructurada, para que pueda distinguir fácilmente entre sí mensajes de diferente gravedad.
-
Write-Host
: muestra mensajes a un usuario interactivo en la consola. A diferencia de los otros cmdletsWrite-*
este no es adecuado ni está pensado para fines de automatización / redirección. No malvado, solo diferente. -
Write-Output
: escriba la salida "normal" del código en el flujo de salida predeterminado (correcto) ("STDOUT"). -
Write-Error
escritura: escriba la información del error en una secuencia separada ("STDERR"). -
Write-Warning
escritura: escriba mensajes que considere advertencias (es decir, cosas que no sean fallas, pero algo que el usuario debería tener en cuenta) en una secuencia separada. -
Write-Verbose
: escriba información que considere más detallada que la salida "normal" a una secuencia separada. -
Write-Debug
: escriba la información que considere relevante para depurar su código en una secuencia separada.
Write-Information
es solo una continuación de este enfoque. Le permite implementar niveles de registro en su salida ( Debug
, información Verbose
, Information
, Warning
, Error
) y aún tener la secuencia de salida exitosa disponible para la salida regular.
En cuanto a por qué Write-Host
convirtió en una envoltura alrededor de Write-Information
: No sé la razón real de esta decisión, pero sospecho que es porque la mayoría de las personas no entienden cómo funciona realmente Write-Host
, es decir, qué puede hacer. ser utilizado para y para qué no debe ser utilizado.
Que yo sepa, no hay un enfoque generalmente aceptado o recomendado para iniciar sesión en PowerShell. Por ejemplo, podría implementar una única función de registro como @JeremyMontgomery sugirió en su respuesta:
function Write-Log {
Param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$Message,
[Parameter(Mandatory=$false, Position=1)]
[ValidateSet(''Error'', ''Warning'', ''Information'', ''Verbose'', ''Debug'')]
[string]$LogLevel = ''Information''
)
switch ($LogLevel) {
''Error'' { ... }
''Warning'' { ... }
''Information'' { ... }
''Verbose'' { ... }
''Debug'' { ... }
default { throw "Invalid log level: $_" }
}
}
Write-Log ''foo'' # default log level: Information
Write-Log ''foo'' ''Information'' # explicit log level: Information
Write-Log ''bar'' ''Debug''
o un conjunto de funciones de registro (una para cada nivel de registro):
function Write-LogInformation {
Param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$Message
)
...
}
function Write-LogDebug {
Param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$Message
)
...
}
...
Write-LogInformation ''foo''
Write-LogDebug ''bar''
Otra opción es crear un objeto de registrador personalizado:
$logger = New-Object -Type PSObject -Property @{
Filename = ''''
Console = $true
}
$logger | Add-Member -Type ScriptMethod -Name Log -Value {
Param(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$Message,
[Parameter(Mandatory=$false, Position=1)]
[ValidateSet(''Error'', ''Warning'', ''Information'', ''Verbose'', ''Debug'')]
[string]$LogLevel = ''Information''
)
switch ($LogLevel) {
''Error'' { ... }
''Warning'' { ... }
''Information'' { ... }
''Verbose'' { ... }
''Debug'' { ... }
default { throw "Invalid log level: $_" }
}
}
$logger | Add-Member -Type ScriptMethod -Name LogDebug -Value {
Param([Parameter(Mandatory=$true)][string]$Message)
$this.Log($Message, ''Debug'')
}
$logger | Add-Member -Type ScriptMethod -Name LogInfo -Value {
Param([Parameter(Mandatory=$true)][string]$Message)
$this.Log($Message, ''Information'')
}
...
Write-Log ''foo'' # default log level: Information
$logger.Log(''foo'') # default log level: Information
$logger.Log(''foo'', ''Information'') # explicit log level: Information
$logger.LogInfo(''foo'') # (convenience) wrapper method
$logger.LogDebug(''bar'')
De cualquier manera usted puede externalizar el código de registro por
colocándolo en un archivo de secuencia de comandos separado y dot-sourcing ese archivo:
. ''C:/path/to/logger.ps1''
Poniéndolo en un module e importando ese módulo:
Import-Module Logger
Para complementar la útil y completa respuesta de Ansgar :
Write-Host
convirtió (en esencia) en un envoltorio para
Write-Information -InformationAction Continue
en PSv5, probablemente porque:
permite suprimir o redirigir
Write-Host
mensajes deWrite-Host
, lo que no era posible anteriormente (en PowerShell 4 o inferior, laWrite-Host
omitió los flujos de PowerShell y los envió directamente al host),al tiempo que conserva la compatibilidad con versiones anteriores, ya que los mensajes se envían de forma predeterminada , a diferencia de
Write-Information
, cuyo comportamiento predeterminado es ser silencioso (porque respeta la variable de preferencia$InformationPreference
, cuyo valor predeterminado esSilentlyContinue
).
Si bien Write-Host
ahora es (PSv5 +) un poco inapropiado, ya no necesariamente escribe en el host , aún tiene una ventaja distinta sobre Write-Information
(como lo indica): puede producir una salida de color con -ForegroundColor
-BackgroundColor
y fondo de -BackgroundColor
.
La respuesta de Ansgar tiene la perspectiva de registro convencional cubierta, pero el cmdlet Start-Transcript
PowerShell puede servir como una alternativa integrada (ver más abajo).
En cuanto a su deseo de enviar mensajes al host mientras los captura en un archivo de registro :
Las transcripciones de la sesión de PowerShell, a través de Start-Transcript
y Stop-Transcript
, pueden darle lo que usted desea.
Como sugiere su nombre, las transcripciones capturan lo que se imprime en la pantalla (sin colorear), que, por lo tanto, por defecto incluye la salida exitosa , sin embargo.
Aplicado a tu ejemplo:
$null = Start-Transcript "D:/foo.log"
$InformationPreference = "Continue"
Write-Information "Some Message"
Write-Information "Another Message"
$null = Stop-Transcript
Lo anterior imprimirá mensajes tanto en la pantalla como en el archivo de transcripción; tenga en cuenta que, curiosamente, solo en el archivo tendrán el prefijo INFO:
(Por el contrario, Write-Warning
, Write-Verbose
y Write-Debug
- si está configurado para producir resultados - use el prefijo WARNING:
VERBOSE:
DEBUG:
tanto en la pantalla como en el archivo; de manera similar, Write-Error
produce "ruidoso" entrada multilínea tanto en pantalla como en el archivo.)
Tenga en cuenta una rareza (no tengo claro si se trata de un error o por diseño, observado en PSv5.1): la salida de Write-Information
muestra en el archivo de transcripción (pero no en la pantalla) incluso cuando $InformationPreference
se establece en SilentlyContinue
(el valor por defecto); La única forma de excluir Write-Information
salida de Write-Information
(a través de la variable de preferencia o el parámetro -InformationAction
) parece ser un valor de Ignore
, que silencia la salida de forma categórica, o, curiosamente, Continue
, en la que solo se imprime en la consola , como PetSerAl Señala.
En pocas palabras, puede usar Start-Transcript
como una aproximación conveniente e integrada de un servicio de registro, cuya verbosidad puede controlar desde el exterior a través de las variables de preferencias ( $InformationPreference
, $VerbosePreference
, ...) , con las siguientes Diferencias importantes respecto a la tala convencional:
En general, lo que se incluye en el archivo de transcripción también se envía a la consola (lo que generalmente podría considerarse un plus).
Sin embargo, la salida de éxito (salida de datos) también se envía de forma predeterminada a la transcripción, a menos que la capture o la elimine por completo, y no puede mantenerla fuera de la transcripción de forma selectiva:
Si lo captura o lo suprime, tampoco se mostrará en el host (la consola, por defecto) tampoco [1] .
Sin embargo, lo inverso es posible: puede enviar la salida solo a la transcripción (sin repetirla en la consola), a través de
Out-Default -Transcript
Thanks, PetSerAl ; p.ej,
''to transcript only'' | Out-Default -Transcript
En general, las redirecciones externas mantienen las secuencias fuera de la transcripción , con dos excepciones , a partir de Windows PowerShell v5.1 / PowerShell Core v6.0.0-beta.5:
- Salida de
Write-Host
, incluso si se utilizan las redirecciones6>
o*>
. - Salida de error, incluso si se utilizan las redirecciones
2>
o*>
.
Sin embargo, el uso de$ErrorActionPreference = ''SilentlyContinue''
/''Ignore''
mantiene los errores de no terminación fuera de la transcripción, pero no los de terminación .
- Salida de
Los archivos de transcripción no están orientados a la línea (hay un bloque de líneas de encabezado con información de invocación, y no hay garantía de que la salida producida por el script se limite a una línea), por lo que no puede esperar analizarlos línea por línea. manera.
[1] PetSerAl menciona la siguiente solución limitada y un tanto engorrosa (PSv5 +) para enviar la salida correcta solo a la consola, lo que excluye en particular el envío de la salida a través de la canalización o la captura:
''to console only'' | Out-String -Stream | ForEach-Object { $Host.UI.WriteLine($_) }
PowerShell es sobre la automatización.
A veces, ejecuta una secuencia de comandos varias veces al día y no desea ver la salida todo el tiempo.
Write-Host
no tiene posibilidad de ocultar la salida. Se escribe en la consola, pase lo que pase.
Con Write-Information
, puede especificar el parámetro -InformationAction
en el script. Con este parámetro, puede especificar si desea ver los mensajes ( -InformationAction Continue
) o no ( -InformationAction SilentlyContinue
)
Edición: Y por favor use "Some Message" | out-file D:/foo.log
"Some Message" | out-file D:/foo.log
para el registro, y Write-Host
o Write-Information