Write-Host против Write-Information в 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 в том, что,Write-Host Можно указать, в каком цвете сообщения отображаются в консоли с помощью-ForegroundColor а также-BackgroundColor.

С другой стороны, используяWrite-Informationвходное сообщение может использоваться везде, где мы хотим, через конвейер № 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-Information лучше, чемWrite-HostПожалуйста, оставьте ваши добрые ответы здесь.
Спасибо.

Ответы на вопрос(5)

что я ненавижу журналирование PowerShell и всеWrite-* команды ... Итак, я запускаю все свои скрипты с одной и той же функцией:

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
    $DataFi,le = $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
    }
}
 mklement023 июл. 2016 г., 04:59
Помимо изучения вашей личной неприязни, мы мало что узнали. Длинная функция, которую вы опубликовали - с редкими комментариями и без общего объяснения - не помогла.
 mklement024 июл. 2016 г., 16:55
Я прошу прощения за snark в моем оригинальном комментарии; тем не менее, я хотел дать указание на то, чего может не хватать вашему ответу, чтобы быть полезным длядругие (множественное число; хотя они, очевидно,мой указатели): положительноЗачем тебе не нравитсяWrite-* командлеты, и обеспечитьобъяснение за то, что ваша функция делает, чтобы объяснить, как она компенсирует недостатки командлетов. Длямы Перспектива в целом: это проявится с течением времени, что отражено в голосах (или их отсутствии).

которую я недавно использовал для моего сценария.

Сценарий для этого заключается в том, что когда мне нужно сделать что-то как запланированное задание, я обычно создаю общий сценарий или функцию в модуле, который выполняет «тяжелую работу», а затем вызывающий сценарий, который обрабатывает специфику для конкретной работы, например получение аргументов из конфигурации XML, ведение журнала, уведомления и т. д.

Внутренний скрипт используетWrite-Error, Write-Warning, а такжеWrite-Verboseвызывающий скрипт перенаправляет все выходные потоки вниз по конвейеру к этой функции, которая захватывает записи сообщений в файле csv с меткой времени, уровнем и сообщением.

В данном случае он был нацелен на PoSh v.4, поэтому я в основном использую Write-Verbose в качестве замены для Write-Information, но та же идея. Если бы я использовал Write-Host в Some-Script.ps1 (см. Пример) вместо Write-Verbose или Write-Information, функция Add-LogEntry не будет захватывать и регистрировать сообщение. Если вы хотите использовать это для правильного захвата большего количества потоков, добавьте записи в оператор switch, чтобы удовлетворить ваши потребности.

-Пройти Переключение в этом случае было в основном способом решения именно того, что вы упомянули о записи в файл журнала в дополнение к выводу на консоль (или в другую переменную, или по конвейеру). В этой реализации я добавил свойство «Уровень» к объекту, но, надеюсь, вы поймете смысл. Мой вариант использования для этого состоял в том, чтобы передать записи журнала в переменную, чтобы их можно было проверить на наличие ошибок и использовать в уведомлении 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 должен по существу записывать информационный объект в консоль, если вы не захватываете выходные данные в переменной или не передаете их по каналу чему-либо другому.

Ансгар полезный и всеобъемлющий ответ:

Write-Host стал (по сути) оберткой для
Write-Information -InformationAction Continue
 в PSv5, вероятно, потому что:

Этопозволяет подавлять или перенаправлятьWrite-Host Сообщения, что ранее было невозможно (в PowerShell 4 или ниже,Write-Host обошел потоки PowerShell и вывел их прямо на хост),

в то время каксохранение обратной совместимости в том, что сообщениявыводятся по умолчанию - в отличие отWrite-Information, чье поведение по умолчанию должно бытьбесшумный (потому что он уважает переменную предпочтения$InformationPreference, чье значение по умолчаниюSilentlyContinue).

В то время какWrite-Host поэтому (PSv5 +) немного ошибочно - это не обязательно писать вхозяин больше - это все еще имеетодно явное преимущество передWrite-Information (как вы заявляете):это может произвестицветной выход с-ForegroundColor а также-BackgroundColor.

Ответ Ансгара имеетобычная регистрация перспектива покрыта, но PowerShellStart-Transcript командлетможет служитьвстроенный альтернатива (см. ниже).

Что касается вашего желаниявывод сообщений на хоств то же время захватывая их в лог-файл:

PowerShell-хстенограммы сессий - с помощьюStart-Transcript а такжеStop-Transcript - может дать вам то, что вы хотите.

Как следует из названия,стенограммы захватывают все, что печатает на экран (без окраски), поэтомупо умолчаниювключает в себя результат успеха, тем не мение.
Применительно к вашему примеру:

$null = Start-Transcript "D:\foo.log"

$InformationPreference = "Continue"
Write-Information "Some Message"
Write-Information "Another Message"

$null = Stop-Transcript

Выше будет печатать сообщенияи то и другое экрана также файл стенограммы; обратите внимание, что, как ни странно,только вфайл будут ли они иметь префиксINFO:.
(В отличие отWrite-Warning, Write-Verbose а такжеWrite-Debug - если настроен на вывод - используйте префиксWARNING:, VERBOSE:, DEBUG: как на экране, так и в файле; так же,Write-Error производит "шумный" многострочный ввод как на экране, так и в файле.)

Обратите внимание, одинстранность (Мне неясно, является ли это ошибкой или дизайном, наблюдалось в PSv5.1):вывод изWrite-Information отображается в файле расшифровки (но не на экране), даже когда$InformationPreference установлен вSilentlyContinue (по умолчанию); единственный способ исключитьWrite-Information вывод (через переменную предпочтения или-InformationAction параметр) представляется значениемIgnore - что выводит вывод на печать категорически - или, что любопытно,Continue, в котором он печатает толькоприставка, как отмечает PetSerAl.

В двух словах,ты можешь использоватьStart-Transcript как удобный, встроенныйприближение средства ведения журнала, подробностью которого вы можете управлять извне через переменные предпочтения ($InformationPreference, $VerbosePreference...)со следующимважные различия из обычной лесозаготовки:

Как правило, что входит в файл расшифровкитакже вывод наприставка (что в целом можно считать плюсом).

Тем не мение,успех вывод (вывод данных) по умолчаниютакже отправлено в стенограмму - если вызахватить это илиподавлять это вообще - и вы не можете выборочно держать это вне стенограммы:

Если вы перехватываете или подавляете его, он не будет отображаться на хосте (консоль по умолчанию)[1].

Обратное, однако, возможно: вы можете отправлять вывод только в транскрипт (без вывода его в консоль) посредствомOut-Default -Transcript Спасибо,PetSerAl; например.,
'to transcript only' | Out-Default -Transcript

В общем-то,внешние перенаправления держать потоки вне стенограммыс двумяисключения, начиная с Windows PowerShell v5.1 / PowerShell Core v6.0.0-beta.5:

Write-Host выходной, даже если6> или же*> перенаправления используются.Ошибка вывода, даже если2> или же*> перенаправления используются.
Однако, используя$ErrorActionPreference = 'SilentlyContinue' / 'Ignore' делает держатьне-терминатор ошибки из стенограммы, но нетерминатор из них.

Файлы транскриптов не ориентированы на строки (есть блок строк заголовка с информацией о вызовах, и нет никакой гарантии, что вывод, создаваемый сценарием, ограничен строкой), поэтому нельзя ожидать, что они будут анализироваться построчно манера.

[1] PetSerAl упоминает следующий ограниченный и несколько громоздкий обходной путь (PSv5 +) для отправки вывода об успешном выполнении только на консоль, что в частности исключает отправку вывода по конвейеру или его захват:
'to console only' | Out-String -Stream | ForEach-Object { $Host.UI.WriteLine($_) }

 PatrickFranchise05 апр. 2017 г., 21:54
Я подумал, что сошел с ума, когда узнал, что если $ InformationPreference -eq 'Continue', вывод идет на консоль, но не на стенограмму. Спасибо, что сообщили мне, что это ожидаемое поведение.
 mklement005 апр. 2017 г., 22:05
@PatrickFranchise: Ну, этофактический поведение с PSv5.1 - не уверен, что этоожидаемый; честно говоря, я подозреваю ошибку.
Решение Вопроса

Write-* Командлеты позволяют структурировать канал вывода кода PowerShell, что позволяет легко отличать сообщения разной степени важности друг от друга.

Write-Host: отображать сообщения для интерактивного пользователя на консоли. В отличие от другихWrite-* Командлеты не подходят и не предназначены для целей автоматизации / перенаправления. Не зло, просто другое.Write-Output: записать «нормальный» вывод кода в поток вывода по умолчанию (успех) («STDOUT»).Write-Error: записать информацию об ошибке в отдельный поток ("STDERR").Write-Warning: записывать сообщения, которые вы рассматриваете как предупреждения (то есть вещи, которые не являются сбоями, но что-то, на что должен обратить внимание пользователь) в отдельный поток.Write-Verbose: запишите информацию, которую вы считаете более многословной, чем «нормальный» вывод, в отдельный поток.Write-Debug: запишите информацию, которую вы считаете важной для отладки вашего кода, в отдельный поток.

Write-Information это просто продолжение этого подхода. Это позволяет вам реализовать уровни журнала в вашем выводе (Debug, Verbose, Information, Warning, Error) и все еще иметь поток вывода успеха, доступный для регулярного вывода.

ПочемуWrite-Host стал оберткойWrite-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'

или набор функций регистрации (одна для каждого уровня регистрации):

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

В любом случае вы можете экспортировать код регистрации

положить его в отдельный файл сценария идот-источники этот файл:

. 'C:\path\to\logger.ps1'

положить его вмодуль и импортировать этот модуль:

Import-Module Logger
 mklement027 июл. 2016 г., 04:11
@WonTasia: чтобы войти в консоль и в файл, используйтеStart-Transcript / Stop-Transcript (но обратите внимание на предостережения, изложенные в моем ответе).
 wontasia25 июл. 2016 г., 08:25
Спасибо за подробный ответ.Write-* Командлеты хороши для регистрации сообщений, но отсутствие поддержки ведения журнала файлов очень неудобно, как я писал в вопросе,Write-Host необходимо написать несколько строк, иWrite-Information нужно много настроек. Есть ли предлагаемый подход для входа в консоль и файлы?

Иногда вы запускаете скрипт несколько раз в день и не хотите видеть результат все время.

Write-Host не имеет возможности скрыть вывод. Это записывается на консоли, несмотря ни на что.

СWrite-Information, вы можете указать-InformationAction Параметр в сценарии. С помощью этого параметра вы можете указать, хотите ли вы видеть сообщения (-InformationAction Continue) или нет (-InformationAction SilentlyContinue)

Редактировать: И, пожалуйста, используйте"Some Message" | out-file D:\foo.log для регистрации, и ниWrite-Host или жеWrite-Information

 wontasia25 июл. 2016 г., 05:47
Благодарю. Не могли бы вы сказать мне, почему с помощьюWrite-Information входить в файлы не предлагается?
 mklement026 июл. 2016 г., 04:55
С PSv5,Write-Host выходМожно быть подавленным / перенаправленным через выходной поток6 (так какWrite-Host теперь обертка дляWrite-Information -InformationAction Continue). Например.,Write-Host "Some Message" 6>$null будутне напечатать что-нибудь.
 mklement027 июл. 2016 г., 04:44
Также стоит отметить, что любой сценарий, который вы пишете, будет уважать только-Information Параметр, если вы определяете сценарий сparam(...) оператор объявления параметров, украшенный[CmdletBinding()] приписывать.
 mklement027 июл. 2016 г., 04:22
@WonTasia: нет ничего плохого в перенаправленииWrite-Host а такжеWrite-Information вывод в файл как таковой - по требованию, извне, используя6> (PSv5 +); напротив, если вы заранее знаете, что будете писатьтолько файлНет причин использовать эти командлеты. Если вы хотите написатьи то и другое консоль (используя поток6 в PSv5 +)а также в файл, используйте'Some Message' | Tee-Object D:\foo.log | Write-Host

Ваш ответ на вопрос