【问题标题】:PowerShell console output from Tee-Object command inside function inside IF statement来自 IF 语句内函数内的 Tee-Object 命令的 PowerShell 控制台输出
【发布时间】:2020-12-13 00:37:34
【问题描述】:

考虑以下代码:

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 版

【问题讨论】:

  • 在我的测试中显示得很好。
  • 您使用什么版本的 PowerShell 进行测试?
  • PSVersion 5.1.19041.610 PSEdition DesktopPSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}BuildVersion 10.0.19041.610CLRVersion 4.0.30319.42000

标签: powershell console


【解决方案1】:

对于 Powershell 函数如何返回数据存在一个常见的误解。实际上,没有像您从其他编程语言中习惯的那样single 返回值或对象。相反,有一个对象的输出流

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

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

让来自其他语言的 PS 新手感到困惑的是,return $data没有定义函数的唯一“返回值”。这只是将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 总是“返回”一个非空数组。未安装软件时,$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 输出到终端屏幕?

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

一个简单的方法是将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 #>}

【讨论】:

  • 感谢您的详细解释。我尝试了“明智的方式”解决方案,它对我有用。有趣的事实是,如果我用 Write-Output 替换 Write-Host,我不会得到像 Tee-Object 那样的输出。我怀疑背后的原因是一样的。我还测试了你的证明,它就像你演示的那样工作,但前提是你将函数输出分配给变量。在我的代码中,我没有这样做,'if' 总是返回正确的值。
【解决方案2】:

如果你认为它可能更容易理解

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

很明显,结果不会在任何时候输出到控制台。


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

    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

最好让函数返回简单的 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 页面也值得一看。

【讨论】:

    猜你喜欢
    • 2021-11-24
    • 1970-01-01
    • 1970-01-01
    • 2020-07-05
    • 1970-01-01
    • 2014-10-17
    • 2012-06-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多