web-dev-qa-db-fra.com

Write-Host vs Write-Information dans PowerShell 5

Il est bien connu que Write-Host est diabolique . Dans PowerShell 5, Write-Information est ajouté et est considéré comme remplaçant Write-Host.

Mais vraiment, lequel est le meilleur?
Write-Host est mauvais car il n'utilise pas de pipeline, le message d'entrée ne peut donc pas être réutilisé.
Mais, que Write-Host fait-il simplement pour afficher quelque chose dans la console, non? Dans quel cas doit-on réutiliser l'entrée?
Quoi qu'il en soit, si nous voulons vraiment réutiliser l'entrée, pourquoi ne pas écrire quelque chose comme ceci:

$foo = "Some message to be reused like saving to a file"
Write-Host $foo
$foo | Out-File -Path "D:\foo.log"

Un autre inconvénient de Write-Host est que, Write-Host peut spécifier la couleur d'affichage des messages dans la console en utilisant -ForegroundColor et -BackgroundColor.

De l'autre côté, en utilisant Write-Information, le message d'entrée peut être utilisé n'importe où via le pipeline No.6. Et n'a pas besoin d'écrire les codes supplémentaires comme j'écris ci-dessus. Mais le côté obscur de ceci est que, si nous voulons écrire des messages sur la console et également enregistrés dans le fichier, nous devons le faire:

# 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 peu redondant je pense.

Je ne connais qu'un petit aspect de cette "vs" chose, et il doit y avoir quelque chose hors de mon esprit. Alors, y a-t-il autre chose qui puisse me faire croire que Write-Information est meilleur que Write-Host, veuillez laisser vos bonnes réponses ici.
Je vous remercie.

20
wontasia

Les cmdlets Write-* vous permettent de canaliser la sortie de votre code PowerShell de manière structurée, de sorte que vous puissiez facilement distinguer les messages de gravité différente les uns des autres.

  • Write-Host: affiche les messages destinés à un utilisateur interactif sur la console. Contrairement aux autres cmdlets Write-*, celle-ci n'est ni appropriée ni destinée à des fins d'automatisation/redirection. Pas mal, juste différent.
  • Write-Output: écrivez la sortie "normale" du code dans le flux de sortie par défaut ("succès") ("STDOUT").
  • Write-Error: écrivez les informations d'erreur dans un flux séparé ("STDERR").
  • Write-Warning: écrivez les messages que vous considérez comme des avertissements (c'est-à-dire des choses qui ne sont pas des échecs, mais quelque chose que l'utilisateur devrait surveiller) dans un flux séparé.
  • Write-Verbose: écrivez les informations que vous jugez plus verbeuses que la sortie "normale" dans un flux séparé.
  • Write-Debug: écrivez les informations que vous jugez utiles pour déboguer votre code dans un flux séparé.

Write-Information n'est que la continuation de cette approche. Il vous permet d'implémenter des niveaux de journalisation dans votre sortie (Debug, Verbose, Information, Warning, Error) tout en maintenant le flux de sortie de succès disponible pour une sortie normale.

Quant à savoir pourquoi Write-Host est devenu une enveloppe autour de Write-Information: je ne connais pas la raison réelle de cette décision, mais je suppose que c’est parce que la plupart des gens ne comprennent pas vraiment comment fonctionne Write-Host, c’est-à-dire à quoi cela sert ne devrait pas être utilisé pour.


À ma connaissance, il n’existe pas d’approche généralement acceptée ou recommandée pour la connexion à PowerShell. Vous pouvez par exemple implémenter une fonction de journalisation unique telle que @JeremyMontgomery suggérée dans sa réponse:

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'

ou un ensemble de fonctions de journalisation (une pour chaque niveau de journalisation):

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'

Une autre option consiste à créer un objet de journalisation personnalisé:

$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 toute façon, vous pouvez externaliser le code de connexion en

  • en le mettant dans un fichier script séparé et dot-sourcing this file:

    . 'C:\path\to\logger.ps1'
    
  • en le mettant dans un module et en important ce module:

    Import-Module Logger
    
29
Ansgar Wiechers

PowerShell concerne l'automatisation. 

Parfois, vous exécutez un script plusieurs fois par jour et vous ne voulez pas voir la sortie tout le temps.

Write-Host n'a aucune possibilité de masquer la sortie. Il est écrit sur la console, quoi qu'il arrive.

Avec Write-Information, vous pouvez spécifier le paramètre -InformationAction dans le script. Avec ce paramètre, vous pouvez spécifier si vous voulez voir les messages (-InformationAction Continue) ou non (-InformationAction SilentlyContinue)

Edit: Et veuillez utiliser "Some Message" | out-file D:\foo.log pour la journalisation, et ni Write-Host ni Write-Information

4
MicroScripter

Voici une version générique d'une fonction de journalisation plus spécialisée que j'ai utilisée récemment pour un script.

Le scénario à suivre est que lorsque je dois effectuer une tâche planifiée, je crée généralement un script générique ou une fonction dans un module qui effectue le "travail intensif", puis un script d'appel qui gère les spécificités du travail, comme obtenir des arguments depuis une configuration XML, une journalisation, des notifications, etc.

Le script interne utilise Write-Error , Write-Warning et Write-Verbose , le script appelant redirige tous les flux de sortie du pipeline vers cette fonction, qui enregistre les messages dans une Fichier CSV avec un horodatage, un niveau et un message.

Dans ce cas, il était ciblé sur PoSh v.4, j'utilise donc essentiellement Write-Verbose en remplacement de Write-Information, mais la même idée. Si je devais utiliser Write-Host dans Some-Script.ps1 (voir l'exemple) au lieu de Write-Verbose ou Write-Information, la fonction Add-LogEntry ne capturerait pas et n'enregistrerait pas le message. Si vous souhaitez utiliser ceci pour capturer plus de flux de manière appropriée, ajoutez des entrées à l'instruction switch pour répondre à vos besoins.

Le commutateur -PassThru dans ce cas était essentiellement un moyen de traiter exactement ce que vous avez mentionné à propos de l'écriture dans un fichier journal en plus de la sortie sur la console (ou sur une autre variable, ou dans le pipeline). Dans cette implémentation, j'ai ajouté une propriété "Level" à l'objet, mais j'espère que vous pourrez voir le point. Mon cas d'utilisation était de transmettre les entrées du journal à une variable afin de pouvoir rechercher les erreurs et de les utiliser dans une notification SMTP si une erreur se produisait.

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) }
  }
}

Exemple d'utilisation serait

& $PSScriptRoot\Some-Script.ps1 -Param $Param -Verbose *>&1 | Add-LogEntry -Path $LogPath -PassThru

Le commutateur -PassThru doit essentiellement écrire l’objet d’information sur la console si vous ne capturez pas le résultat dans une variable ou ne le transmettez pas à un autre canal.

0
webward