IF 语句内函数内 Tee-Object 命令的 PowerShell 控制台输出

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

考虑以下代码:

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

我希望在 ShowSave-Log 函数中看到来自 Tee-Object 命令的消息,但它从未显示在终端中。我猜这是因为它是从 'if' 语句调用的。如何将 Tee-Object 输出到终端屏幕?它被保存到日志文件中。 BTW Write-Host 命令正确地将消息输出到终端。 我正在使用 PowerShell ISE、Visual Studio 代码和 PowerShell 终端。 PowerShell 版本 5.1

如果你把它想成

可能更容易理解
$result = Is-Installed "Notepad++ (64-bit x64)"
if ($result) {Write-Host "TRUE"}

很明显,结果不会随时输出到控制台。


您可能还误解了 return 的工作原理

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

在功能上与

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

你最好让你的函数 return 简单的 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
}

并将用户的格式保留在脚本的顶层。


使用 DefaultDisplayPropertySet 属性 可能值得查看自定义类型。例如:

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
}

或者在没有自定义类型的情况下,这种令人厌恶的单行代码:

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

Approved Verbs for PowerShell 页面也值得一看。

对于 Powershell 如何处理 return 数据存在一个常见的误解。实际上,没有您习惯于使用其他编程语言的 single return 值或对象。相反,有对象的 输出流

有几种方法可以将数据添加到输出流,例如。 g.:

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

来自其他语言的 PS 新手感到困惑的是 return $data 没有定义函数的唯一“return 值”.这只是将 Write-Output $data 与函数提前退出相结合的一种便捷方式。在 return 语句之前写入输出流的任何数据也会影响函数的输出!

代码解析

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

... 将 InputObject 附加到 ShowSave-Log

的输出流
ShowSave-Log "`'$app_name`' is installed."

... 将消息附加到 Is-Installed

的输出流
return $true

... 将值 $true 附加到 Is-Installed

的输出流

现在 Is-Installed 的输出流中实际上有 两个 个对象,字符串消息和 $true 值!

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

让我拆分 if 语句来详细解释它的作用:

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

... 将 Is-Installed 的输出流重定向到临时变量。由于输出流已存储到一个变量中,它不会在函数调用链中进一步向上移动,因此它不会再显示在控制台中!这就是为什么您看不到来自 Tee-Object.

的消息的原因

在我们的例子中,输出流中有多个对象,因此变量将是一个数组,如 @('... is installed', $true)

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

... 对数组 $temp 进行隐式布尔转换。非空数组转换为 $true。所以这里有一个错误,因为函数 Is-Installed 总是 "returns" 一个非空数组。未安装软件时,$temp 看起来像 @('... not found ...', $false),它也会转换为 $true!

证明:

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

如何将 Tee-Object 输出到终端屏幕?

不要让它写入输出流,输出流应该只用于从函数“returned”的实际数据,而不用于日志消息。

一个简单的方法是将 Tee-Object 的输出重定向到 Write-Host,它写入信息流:

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

更明智的方法是重定向到详细流:

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

现在默认情况下,日志消息不会使终端混乱。相反,要查看详细的日志记录,调用者必须启用详细输出,例如。 G。通过设置 $VerbosePreference = 'Continue' 或使用 -Verbose 参数调用函数:

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