Запись хоста и запись информации в 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, пожалуйста, оставьте здесь свои добрые ответы.
Спасибо.


person wontasia    schedule 22.07.2016    source источник


Ответы (5)


Командлеты 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
    
person Ansgar Wiechers    schedule 22.07.2016
comment
Спасибо за подробный ответ. Write-* командлеты отлично подходят для регистрации сообщений, но отсутствие поддержки ведения журнала файлов очень неудобно, как я уже писал в вопросе, Write-Host нужно написать несколько строк, а Write-Information нужно много настроек. Есть ли какой-либо предлагаемый подход для входа как в консоль, так и в файлы? - person wontasia; 25.07.2016
comment
@WonTasia: чтобы войти как в консоль, так и в файл, используйте Start-Transcript / Stop-Transcript (но обратите внимание на оговорки, указанные в моем ответе). - person mklement0; 27.07.2016
comment
Согласно этому ответу Write-Error по умолчанию не всегда записывает в stderr - stackoverflow.com/a/15669365/2801913 - person Graeme; 07.01.2020

В дополнение к полезному и исчерпывающему ответу Ансгара:

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.


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

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

Стенограммы сеанса 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 производит "шумный" многострочный ввод как на экране, так и в файле.)

Обратите внимание на одну ошибку, которая затрагивает только Windows PowerShell (это была исправлено в PowerShell [Core]. Спасибо, JohnLBevan.): вывод 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; однако, начиная с PowerShell 7.0, это, кажется, записывает вывод дважды в расшифровку; также обратите внимание, что Out-Default обычно не предназначен для вызова из пользовательского кода - см. этот ответ.

  • Как правило, внешние перенаправления (применение > к вызову скрипта, который выполняет внутреннюю транскрипцию) не позволяют потокам из стенограммы, за двумя исключениями, начиная с PowerShell 7.0:

    • Write-Host output, even if 6> or *> redirections are used.
    • Вывод ошибок, даже если используются 2> или *> перенаправления.
      Однако использование $ErrorActionPreference = 'SilentlyContinue' / 'Ignore' позволяет исключить непрекращающиеся ошибки из стенограммы, но не завершающие.
  • Файлы транскриптов не ориентированы на строки (есть блок строк заголовков с информацией о вызове, и нет гарантии, что вывод, созданный сценарием, ограничен строкой), поэтому вы не можете рассчитывать на их анализ построчно. манера.


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

person mklement0    schedule 26.07.2016
comment
Я подумал, что сошел с ума, когда узнал, что если $ InformationPreference -eq 'Continue', вывод идет на консоль, но не транскрипт. Спасибо, что сообщили мне, что это ожидаемое поведение. - person PatrickFranchise; 05.04.2017
comment
@PatrickFranchise: Ну, это фактическое поведение с PSv5.1 - не уверен, ожидалось; честно говоря, подозреваю баг. - person mklement0; 05.04.2017
comment
В связи с приведенным выше комментарием это поведение теперь исправлено в ядре PS: github.com/PowerShell/PowerShell / issues / 6916 - person JohnLBevan; 02.04.2020
comment
Спасибо, @John - я соответствующим образом обновил ответ. - person mklement0; 02.04.2020

PowerShell - это автоматизация.

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

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

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

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

person MicroScripter    schedule 22.07.2016
comment
Спасибо. Не могли бы вы сказать мне, почему не рекомендуется использовать Write-Information для входа в файлы? - person wontasia; 25.07.2016
comment
Начиная с PSv5, Write-Host вывод может подавляться / перенаправляться через выходной поток 6 (потому что Write-Host теперь является оболочкой для Write-Information -InformationAction Continue). Например, Write-Host "Some Message" 6>$null не ничего печатает. - person mklement0; 26.07.2016
comment
@WonTasia: Нет ничего плохого в перенаправлении вывода Write-Host и Write-Information в файл как таковой - по запросу извне, используя 6> (PSv5 +); напротив, если вы заранее знаете, что будете писать в только файл, нет причин использовать эти командлеты. Если вы хотите записать в обе консоли (используя поток 6 в PSv5 +) и в файл, используйте 'Some Message' | Tee-Object D:\foo.log | Write-Host - person mklement0; 27.07.2016
comment
Также стоит отметить, что любой сценарий, который вы пишете, будет учитывать общий параметр -Information, только если вы определите сценарий с помощью оператора объявления параметра param(...), украшенного атрибутом [CmdletBinding()]. - person mklement0; 27.07.2016

Вот общая версия более специализированной функции ведения журнала, которую я недавно использовал для своего скрипта.

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

Внутренний сценарий использует Ошибка записи, Предупреждение записи и Подробная запись, вызывающий сценарий перенаправляет все выходные потоки по конвейеру на этот функция, которая захватывает записи сообщений в CSV-файл с отметкой времени, уровнем и сообщением.

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

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

person webward    schedule 28.03.2017

Я должен признать, что ненавижу ведение журнала 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
    $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
    }
}
person Jeremy Montgomery    schedule 23.07.2016
comment
Кроме того, что мы узнали о ваших личных антипатиях, мы многому не научились. Опубликованная вами длинная функция - с редкими комментариями и без общего объяснения - не помогла. - person mklement0; 23.07.2016
comment
Прошу прощения за язвительность в моем первоначальном комментарии; однако я действительно хотел указать, чего может не хватать в вашем ответе, чтобы быть полезным для других (во множественном числе; даже если они, очевидно, являются моими указателями): Проще говоря: сообщает нам почему вам не нравятся Write-* командлеты и дает объяснение того, что делает ваша функция, чтобы объяснить, как она компенсирует командлеты. недостатки. Что касается точки зрения мы в целом: она появится со временем, что отразится на голосах (или их отсутствии). - person mklement0; 24.07.2016