web-dev-qa-db-ja.com

PowerShell 5の書き込みホストと書き込み情報

Write-Hostが悪であることはよく知られています。 PowerShell 5では、Write-Informationが追加され、Write-Hostを置き換えると見なされます。

しかし、本当に、どちらが良いですか?
Write-Hostはパイプラインを使用しないため悪意があるため、入力メッセージを再利用することはできません。
しかし、Write-Hostは、コンソールに何かを表示するだけです。どのような場合に入力を再利用しますか?
とにかく、入力を本当に再利用したい場合は、次のように書いてみませんか。

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

Write-Hostのもう1つの短所は、Write-Host-ForegroundColorを使用して、-BackgroundColorがコンソールに表示されるメッセージの色を指定できることです。

反対側では、Write-Informationを使用することにより、No.6パイプラインを介して入力メッセージをどこでも使用できます。上に書いたような余分なコードを書く必要はありません。しかし、これの暗い面は、コンソールにメッセージを書き、ファイルにも保存したい場合、これをしなければならないということです:

# 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 =======

少し冗長だと思います。

私はこの「対」事柄のほんの少しの側面を知っているだけであり、私の心から何かがあるはずです。 Write-InformationWrite-Hostよりも優れていると私に信じさせることができる他のものはありますか、ここにあなたの親切な答えを残してください。
ありがとうございました。

22
wontasia

Write-*コマンドレットを使用すると、PowerShellコードの出力を構造化された方法でチャネル化できるため、重大度の異なるメッセージを簡単に区別できます。

  • Write-Host:コンソールで対話型ユーザーにメッセージを表示します。他のWrite-*コマンドレットとは異なり、このコマンドレットは自動化/リダイレクトの目的には適していません。悪ではなく、ただ違う。
  • Write-Output:コードの「通常」出力をデフォルト(成功)出力ストリーム(「STDOUT」)に書き込みます。
  • Write-Error:エラー情報を別のストリームに書き込みます( "STDERR")。
  • Write-Warning:警告と見なされるメッセージ(つまり、障害ではなく、ユーザーが注目すべきもの)を別のストリームに書き込みます。
  • Write-Verbose:「通常の」出力よりも冗長と思われる情報を別のストリームに書き込みます。
  • Write-Debug:コードのデバッグに関連すると思われる情報を別のストリームに書き込みます。

Write-Informationは、このアプローチの続きです。出力にログレベルを実装できます(DebugVerboseInformationWarningError)。通常の出力に使用できる成功出力ストリーム。

Write-HostWrite-Informationのラッパーになった理由について:この決定の実際の理由はわかりませんが、ほとんどの人がWrite-Hostを実際に理解していないためだと思いますつまり、何に使用できるのか、何に使用すべきでないのか。


私の知る限り、PowerShellでログを記録するための一般的に受け入れられている、または推奨されるアプローチはありません。たとえば、彼の答えで提案されている @ JeremyMontgomery のような単一のロギング関数を実装できます。

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'

またはログ機能のセット(ログレベルごとに1つ):

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'

別のオプションは、カスタムロガーオブジェクトを作成することです:

$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')

どちらの方法でも、ログコードを外部化できます。

  • 別のスクリプトファイルに入れて dot-sourcing そのファイル:

    . 'C:\path\to\logger.ps1'
    
  • module に入れて、そのモジュールをインポートします:

    Import-Module Logger
    
33
Ansgar Wiechers

PowerShellは自動化に関するものです。

時々、スクリプトを1日に複数回実行し、常に出力を表示したくない場合があります。

Write-Hostには、出力を隠す可能性はありません。何であれ、コンソールに書き込まれます。

Write-Informationを使用すると、スクリプトの-InformationActionパラメーターを指定できます。このパラメーターを使用すると、メッセージを表示するか(-InformationAction Continue)、表示しないか(-InformationAction SilentlyContinue)を指定できます。

編集:そして、ログに"Some Message" | out-file D:\foo.logを使用し、Write-HostWrite-Informationも使用しないでください

4
MicroScripter

これは、最近スクリプトに使用した、より特殊なログ機能の汎用バージョンです。

このシナリオは、スケジュールされたタスクとして何かをする必要がある場合、一般的なスクリプト、または「重いリフティング」を行うモジュール内の関数を作成し、特定のジョブの詳細を処理する呼び出しスクリプトを作成することです。 XML構成からの引数の取得、ロギング、通知など。

内部スクリプトはWrite-ErrorWrite-Warning、およびWrite-Verboseを使用します。呼び出し元のスクリプトはすべての出力ストリームをパイプラインにリダイレクトしますこの関数は、タイムスタンプ、レベル、およびメッセージとともにcsvファイルにメッセージを記録します。

この場合、PoSh v.4を対象としていたため、基本的にWrite-VerboseをWrite-Informationの代用として使用していますが、同じ考えです。 Write-VerboseまたはWrite-Informationの代わりにSome-Script.ps1でWrite-Hostを使用した場合(例を参照)、Add-LogEntry関数はメッセージをキャプチャしてログに記録しません。これを使用してより多くのストリームを適切にキャプチャする場合は、必要に応じてswitchステートメントにエントリを追加します。

この場合の-PassThrスイッチは、基本的に、コンソール(または別の変数への出力、またはパイプラインへの出力)に加えて、ログファイルへの書き込みの両方について述べたことに正確に対処する方法でした。この実装では、オブジェクトに「レベル」プロパティを追加しましたが、うまくいけばポイントを確認できます。この使用例では、ログエントリを変数に渡してエラーをチェックし、エラーが発生した場合にSMTP通知で使用できるようにしました。

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

使用例は次のようになります

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

-PassThruスイッチは、変数で出力をキャプチャしない場合、またはパイプを介して他の何かに渡す場合、基本的に情報オブジェクトをコンソールに書き込む必要があります。

0
webward