简体   繁体   English

PowerShell 控制台 output 来自 IF 语句内 function 内的 Tee-Object 命令

[英]PowerShell console output from Tee-Object command inside function inside IF statement

Consider following code:考虑以下代码:

Function ShowSave-Log {
  Param ([Parameter(Mandatory=$true)][String] $text)
  $PSDefaultParameterValues=@{'Out-File:Encoding' = 'utf8'}
  $date=[string](Get-Date).ToString("yyyy/MM/dd HH:mm:ss")
  Tee-Object -InputObject "$date $text" -FilePath $LOG_FILE -Append
  #Write-Host "$date $text"
}

Function Is-Installed {
  Param ([parameter(Mandatory=$true)][String] $app_name, [String] $app_version)
  $apps = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" | 
  Select-Object DisplayName, DisplayVersion
  $apps += Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" | Select-Object DisplayName, DisplayVersion
  $apps = $apps.Where{$_.DisplayName -like "$app_name"}
  if ($apps.Count -eq 0) {
    ShowSave-Log "`'$app_name`' not found in the list of installed applications."
    return $false
  } else {
    ShowSave-Log "`'$app_name`' is installed."
    return $true
  }
}

$LOG_FILE="$Env:TEMP\LOG.log"
if (Is-Installed "Notepad++ (64-bit x64)") {Write-Host "TRUE"}

I'd expect to see message from Tee-Object command in ShowSave-Log function, however it is never shown in terminal.我希望在 ShowSave-Log function 中看到来自 Tee-Object 命令的消息,但它从未在终端中显示。 I am guessing it's because it is called from 'if' statement.我猜这是因为它是从“if”语句中调用的。 How can I get Tee-Object output to terminal screen?我怎样才能让 Tee-Object output 到终端屏幕? It is saved to log file.它被保存到日志文件中。 BTW Write-Host command correctly outputs message to terminal. BTW Write-Host 命令正确地将消息输出到终端。 I am using PowerShell ISE, Visual Studio code and PowerShell terminal.我正在使用 PowerShell ISE、Visual Studio 代码和 PowerShell 终端。 PowerShell version 5.1 PowerShell 5.1版

There is a common misconception about how Powershell functions return data.关于 Powershell 函数如何返回数据存在一个常见的误解。 Actually there isn't a single return value or object as you are used to from other programming languages.实际上,没有一个返回值或 object 就像您在其他编程语言中习惯的那样。 Instead, there is an output stream of objects.相反,有一个output stream对象。

There are several ways to add data to the output stream, eg:有几种方法可以向 output stream 添加数据,例如:

  • Write-Output $data
  • $data
  • return $data

Confusing to PS newcomers coming from other languages is the fact that return $data does not define the sole "return value" of a function .令来自其他语言的 PS 新手感到困惑的是, return $data没有定义 function 的唯一“返回值” It is just a convenient way to combine Write-Output $data with an early exit from the function.这只是将Write-Output $data与function 的提前退出结合起来的一种便捷方式。 Whatever data that was written to the output stream befor the return statement also contributes to the output of the function!return语句之前写入 output stream 的任何数据也有助于函数的 output!

Analysis of the code代码分析

Tee-Object -InputObject "$date $text" -FilePath $LOG_FILE -Append

... appends the InputObject to the output stream of ShowSave-Log ... 将 InputObject 附加到ShowSave-Log的 output stream

 ShowSave-Log "`'$app_name`' is installed."

... appends the message to the output stream of Is-Installed ... 将消息附加到Is-Installed的 output stream

 return $true

... appends the value $true to the output stream of Is-Installed ... 将值$true附加到Is-Installed的 output stream

Now we actually have two objects in the output stream of Is-Installed , the string message and the $true value!现在我们实际上在Is-Installed的 output stream 中有两个对象,字符串 message 和$true值!

 if (Is-Installed "Notepad++ (64-bit x64)") {Write-Host "TRUE"}

Let me split up the if-statement to explain in detail what it does:让我拆分 if 语句来详细解释它的作用:

$temp = Is-Installed "Notepad++ (64-bit x64)"

... redirects the output stream of Is-Installed to temporary variable. ...将Is-Installed的 output stream 重定向到临时变量。 As the output stream has been stored into a variable, it won't go further up in the function call chain, so it won't show up in the console anymore! As the output stream has been stored into a variable, it won't go further up in the function call chain, so it won't show up in the console anymore! That's why you don't see the message from Tee-Object .这就是为什么您看不到来自Tee-Object的消息的原因。

In our case there is more than one object in the output stream, so the variable will be an array like @('... is installed', $true)在我们的例子中,output stream 中存在不止一个 object,因此变量将是一个类似@('... is installed', $true)的数组

if ($temp) {Write-Host "TRUE"}

... does an implicit boolean conversion of the array $temp . ... 对数组$temp进行隐式 boolean 转换。 A non-empty array converts to $true .非空数组转换为$true So there is a bug here, because the function Is-Installed always "returns" a non-empty array.所以这里有一个错误,因为 function Is-Installed总是“返回”一个非空数组。 When the software is not installed, $temp would look like @('... not found...', $false) , which also converts to $true !未安装软件时, $temp看起来像@('... not found...', $false) ,它也转换为$true

Proof:证明:

$temp = Is-Installed "nothing"
$temp.GetType().Name    # Prints 'Object[]'
$temp[0]                # Prints '2020.12.13 12:39:37 'nothing' not found ...'
$temp[1]                # Prints 'False'
if( $temp ) {'Yes'}     # Prints 'Yes' !!!    

How can I get Tee-Object output to terminal screen?我怎样才能让 Tee-Object output 到终端屏幕?

Don't let it write to the output stream, which should be used only for actual data to be "returned" from a function, not for log messages.不要让它写入 output stream,它应该只用于从 function “返回”的实际数据,而不是用于日志消息。

A simple way to do that is to redirect the output of Tee-Object to Write-Host , which writes to the information stream:一个简单的方法是将Tee-Object的 output 重定向到Write-Host ,写入信息 stream :

Tee-Object -InputObject "$date $text" -FilePath $LOG_FILE -Append | Write-Host

A more sensible way would be to redirect to the verbose stream:更明智的方法是重定向到详细的 stream:

Tee-Object -InputObject "$date $text" -FilePath $LOG_FILE -Append | Write-Verbose

Now the log message doesn't clutter the terminal by default.现在,默认情况下,日志消息不会使终端混乱。 Instead, to see detailed logging the caller has to enable verbose output, eg by setting $VerbosePreference = 'Continue' or calling the function with -Verbose parameter:相反,要查看详细的日志记录,调用者必须启用详细 output,例如通过设置$VerbosePreference = 'Continue'或使用-Verbose参数调用 function:

if( Is-Installed 'foo' -Verbose ){<# do something #>}

It might be easier to understand if you think of it as如果你认为它可能更容易理解

$result = Is-Installed "Notepad++ (64-bit x64)"
if ($result) {Write-Host "TRUE"}

It's pretty clear that way that the result isn't output to the console at any time.很明显,结果不是 output 到控制台的任何时候。


You may also be misunderstanding how return works您可能还误解了退货的运作方式

    ShowSave-Log "`'$app_name`' not found in the list of installed applications."
    return $false

is functionally the same as在功能上与

    ShowSave-Log "`'$app_name`' not found in the list of installed applications."
    $false
    return

You'd be better of having your functions return simple PowerShell objects rather than human readable text and truth values.你最好让你的函数返回简单的 PowerShell 对象而不是人类可读的文本和真值。

function Get-InstalledApps {
    param (
        [parameter(Mandatory=$true)][string] $app_name,
        [string] $app_version
    )
    $installPaths = @(
        'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
        'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
    )
    Get-ItemProperty -Path $installPaths | Where-Object DisplayName -like $app_name
}

And leave the formatting for the user to the top level of your script.并将用户的格式留给脚本的顶层。


It could be worth looking at custom types with the DefaultDisplayPropertySet property.使用DefaultDisplayPropertySet属性查看自定义类型可能是值得的。 For example:例如:

Update-TypeData -TypeName 'InstalledApp' -DefaultDisplayPropertySet 'DisplayName', 'DisplayVersion'

function Get-InstalledApps {
    param (
        [parameter(Mandatory=$true)][string] $app_name,
        [string] $app_version
    )
    $installPaths = @(
        'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
        'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
    )
    Get-ItemProperty -Path $installPaths | Where-Object DisplayName -like $app_name | Add-Member -TypeName 'InstalledApp' -PassThru
}

Or without a custom type, this abomination of a one liner:或者没有自定义类型,这种可憎的单线:

Get-ItemProperty -Path $installPaths | Where-Object DisplayName -like $app_name | Add-Member -MemberType MemberSet -Name PSStandardMembers -Value ([System.Management.Automation.PSMemberInfo[]](New-Object System.Management.Automation.PSPropertySet DefaultDisplayPropertySet, ([string[]]('DisplayName', 'DisplayVersion')))) -PassThru

Also worth taking a look at is the Approved Verbs for PowerShell page.另外值得一看的是PowerShell 页面的已批准动词

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM