PowerShell函数的输出只在脚本完成后显示。

huangapple go评论66阅读模式
英文:

Output from Powershell function shows only after script finishes

问题

经过多个小时尝试多种方法后,我最终放弃了。我的问题虽然微不足道,但让我很头疼。

我有这个函数:

function SearchSharedMailboxes {
param (
    [parameter(Mandatory=$true)] $objectList,
    [parameter(Mandatory=$true)] [string]$objectName
)
    
    $matchingList = [System.Collections.ArrayList]::new()

    if ($objectList.Count -le 0) {
        
    $decision = Read-Host "未找到匹配项,是否重新搜索 Y/N?"

        if ($decision -eq "Y") {

            GetSharedMailboxes

        } else {

            Exit-PSSession

        }

    } else {

        Write-Verbose "找到的匹配项:"
        
        $i=0
        $objectList | Foreach-Object {

             [PSCustomObject]@{
                ID = $i
                UserPrincipal = $_.UserPrincipalName
                RecipientTypeDetails = $_.RecipientTypeDetails
                PrimarySMTPAddress = $_.PrimarySMTPAddress
                
            }
            $i++

        }

    }
    
}

我期望在控制台中看到如下输出:

ID UserPrincipal                       RecipientTypeDetails PrimarySMTPAddress
-- -------------                       -------------------- ------------------
 0 Name1                               SharedMailbox        addres1@domain.com
 1 Name2                               SharedMailbox        addres2@domain.com
 2 Name3                               SharedMailbox        addres3@domain.com

问题是,输出显示正常,但如果在最后的大括号之前调用另一个函数,输出将不再显示。所以为了演示:

     $i=0
        $objectList | Foreach-Object {

             [PSCustomObject]@{
                ID = $i
                UserPrincipal = $_.UserPrincipalName
                RecipientTypeDetails = $_.RecipientTypeDetails
                PrimarySMTPAddress = $_.PrimarySMTPAddress
                
            }
            $i++

        }

    }

    ObjectAssigning
    
}

在上面的代码中,像上面这样调用 "ObjectAssigning" 函数时,输出不再显示直到脚本结束。因此,当 "ObjectAssigning" 方法完成并且整个脚本结束时,它才会产生输出,但我需要在脚本运行时显示输出,而不是在脚本完成后。我尝试将自定义的 PSObject 分配给变量并调用该变量,我还尝试了像这样的方法:

$variableWithCustomPSObjAssigned | Select-Object -Property *

但它产生了完全相同的结果 - 输出在脚本完成后才显示。我还尝试让 "SearchSharedMailboxes" 函数完成,然后从其他地方调用 "ObjectAssigning",但结果相同。尝试使用旧的方法也没有解决问题,就像这样:

   $objectList | Foreach-Object {

            $item = New-Object -TypeName PSObject
            $item | Add-Member -MemberType NoteProperty -Name ID -Value $i
            $item | Add-Member -MemberType NoteProperty -Name UserPrincipal -Value $_.UserPrincipalName
            $item | Add-Member -MemberType NoteProperty -Name RecipientTypeDetails -Value $_.RecipientTypeDetails
            $item | Add-Member -MemberType NoteProperty -Name PrimarySMTPAddress -Value $_.PrimarySMTPAddress
            $i++
        }

对于这个问题,我会感激任何帮助。

英文:

after many hours of trying numerous approaches I finally gave up. My problem is trivial but it gives me really hard time.

I have this function:

function SearchSharedMailboxes {
param (
    [parameter(Mandatory=$true)] $objectList,
    [parameter(Mandatory=$true)] [string]$objectName
)
    
    $matchingList = [System.Collections.ArrayList]::new()

    if ($objectList.Count -le 0) {
        
    $decision = Read-Host "No match found, search again Y/N?"

        if ($decision -eq "Y") {

            GetSharedMailboxes

        } else {

            Exit-PSSession

        }

    } else {

        Write-Verbose "Matches found:"
        
        $i=0
        $objectList | Foreach-Object {

             [PSCustomObject]@{
                ID = $i
                UserPrincipal = $_.UserPrincipalName
                RecipientTypeDetails = $_.RecipientTypeDetails
                PrimarySMTPAddress = $_.PrimarySMTPAddress
                
            }
            $i++

        }

    }
    
}

I expect to see output in the console like this:

ID UserPrincipal                       RecipientTypeDetails PrimarySMTPAddress
-- -------------                       -------------------- ------------------
 0 Name1                               SharedMailbox        addres1@domain.com
 1 Name2                               SharedMailbox        addres2@domain.com
 2 Name3                               SharedMailbox        addres3@domain.com

The thing is that the output shows fine but if I call another function before the final bracket the output shows no more. So to demonstrate:

     $i=0
        $objectList | Foreach-Object {

             [PSCustomObject]@{
                ID = $i
                UserPrincipal = $_.UserPrincipalName
                RecipientTypeDetails = $_.RecipientTypeDetails
                PrimarySMTPAddress = $_.PrimarySMTPAddress
                
            }
            $i++

        }

    }

    ObjectAssigning
    
}

With "ObjectAssigning" function call like above, the output shows no more UNTIL the script ends. So when the "ObjectAssigning" method finishes and therefore whole script ends, it produces output like before. However, I need the output to show up while the script is running, not after it finishes. I tried assigning my custom PSObject to variable and calling the variable, I also tried something like

$variableWithCustomPSObjAssigned | Select-Object -Property *

but it gives exactly the same results - output shows after the script finishes. I also tried to let "SearchSharedMailboxes" function to finish and then calling "ObjectAssigning" from elsewhere but with the same results. Trying to use older approach like this also did not resolve the problem:

   $objectList | Foreach-Object {

            $item = New-Object -TypeName PSObject
            $item | Add-Member -MemberType NoteProperty -Name ID -Value $i
            $item | Add-Member -MemberType NoteProperty -Name UserPrincipal -Value $_.UserPrincipalName
            $item | Add-Member -MemberType NoteProperty -Name RecipientTypeDetails -Value $_.RecipientTypeDetails
            $item | Add-Member -MemberType NoteProperty -Name PrimarySMTPAddress -Value $_.PrimarySMTPAddress
            $i++
        }

I would appreciate any help regarding this matter.

答案1

得分: 0

似乎问题是又一个变种的臭名昭著的 300 毫秒延迟,可能是隐式应用的(就像在您的情况下),Format-Table 调用为了确定合适的列宽而采用的:
在此延迟已经过去之前调用的长时间运行的阻塞命令(在您的情况下是 ObjectAssigning)可能会无限期地延迟表格输出。

解决方法:

  • 如果允许您的函数仅生成显示输出,即不再输出数据,您可以通过将其传递给 Out-Host 强制进行同步显示输出(您也可以将其传递给 Format-Table,但由于您的输出对象已经暗示了它的使用,而且与 Out-Host 不同,它仍然会生成成功流输出,尽管是无用的形式:Format-* cmdlet 输出到管道的是格式化指令):
# ...

$objectList | Foreach-Object {

     [PSCustomObject]@{
        ID = $i
        UserPrincipal = $_.UserPrincipalName
        RecipientTypeDetails = $_.RecipientTypeDetails
        PrimarySMTPAddress = $_.PrimarySMTPAddress
    }
    $i++

} | Out-Host

# ...
  • 如果您确实需要数据输出,不幸的是,解决方案并不简单:

    • 一个相对简单但不太理想的解决方案是添加一个开关参数,例如 -DisplayOnly,只有在需要同步显示输出时才传递,然后仅在那时使用 Out-Host,这是一个简单的概念验证:
# 支持 -DisplayOnly 的示例函数
function Get-Foo {
  param(
    [switch] $DisplayOnly
  )

  # 定义要执行的管道作为脚本块,以供以后按需调用。
  $pipelineToExecute = {
    1..5 | % {
      [pscustomobject] @{
        Bar = "Bar$_"
      }
    }
  }

  # 执行管道:
  if ($DisplayOnly) {
    # 同步,但仅用于显示输出。
    & $pipelineToExecute | Out-Host
  }
  else {
    # 正常输出到管道,但显示上通过下面的 `pause` 命令延迟。
    & $pipelineToExecute
  }

  # 模拟长时间运行的命令。
  # 没有 -DisplayOnly,表格在此命令返回后才呈现。
  pause
}

# 使用 -DisplayOnly 强制同步显示输出调用函数。
# 省略 -DisplayOnly 以捕获数据输出。
Get-Foo -DisplayOnly
  • 一个更正式的解决方案需要更多的工作,不幸的是:

    • 将预定义的格式化数据与输出对象的 .NET 类型关联,并定义具有固定列宽的表视图。

    • 这需要编写一个格式化文件(*._Format.ps1xml),必须首先加载到会话中,这是一个比较复杂的工作。

    • 作为将特定 .NET 类型与格式化数据关联的较简单的替代方法,您可以将 PSTypeName 属性添加到您的 [pscustomobject] 输出对象中(例如,[pscustomobject] @{ PSTypeName = 'My.Type'; ID = $i; ... })。

[1] 但是,如果需要使用非默认格式,可以将 Format-* 调用与 Out-Host 结合使用(例如,... | Format-List | Out-Host)。

如果您需要更多帮助,请随时提问。

英文:

<!-- language-all: sh -->

It sounds like the problem is yet another variation of the infamous 300-millisecond delay that (possibly implicitly applied, as in your case) Format-Table calls employ in order to determine suitable column widths:
A long-running, blocking command that is invoked before this delay has elapsed (ObjectAssigning, in your case) can indefinitely delay the tabular output.

Workarounds:

If it is acceptable to have your function produce to-display output only - i.e. to no longer output data - you can force synchronous to-display output by piping to Out-Host (you may also pipe to Format-Table, but its use is implied by your output objects, and - unlike Out-Host - it would still produce success-stream output, albeit in useless form: what Format-* cmdlets output to the pipeline are formatting instructions):<sup>[1]</sup>

# ...

        $objectList | Foreach-Object {

             [PSCustomObject]@{
                ID = $i
                UserPrincipal = $_.UserPrincipalName
                RecipientTypeDetails = $_.RecipientTypeDetails
                PrimarySMTPAddress = $_.PrimarySMTPAddress
            }
            $i++

        } | Out-Host

# ...

If you do need data output, the solutions are nontrivial, unfortunately:

  • A comparatively simple, but suboptimal solution would be to add a switch parameter, say -DisplayOnly, to be passed only when synchronous to-display output is needed, and use Out-Host only then; here's a simple proof-of-concept:

    # Sample function that supports -DisplayOnly
    function Get-Foo {
      param(
        [switch] $DisplayOnly
      )
    
      # Define the pipeline to execute as a script block, to be invoked
      # on demand later.
      $pipelineToExecute = {
        1..5 | % {
          [pscustomobject] @{
            Bar = &quot;Bar$_&quot;
          }
        }
      }
    
      # Execute the pipeline:
      if ($DisplayOnly) {
        # Synchronous, but to-display output only.
        &amp; $pipelineToExecute | Out-Host
      }
      else {
        # Normal output to the pipeline, but display-wise
        # delayed by the `pause` command below.
        &amp; $pipelineToExecute
      }
    
      # Simulate a long-running command.
      # Without -DisplayOnly, the table doesn&#39;t render until 
      # *after* this command returns.
      pause
    
    }
    
    # Invoke the function with -DisplayOnly to force synchronous
    # to-display output.
    # Omit -DisplayOnly to *capture the output as data*.
    Get-Foo -DisplayOnly
    
  • A proper solution requires much more work, unfortunately:

    • Associate predefined formatting data with the .NET type of your output objects and define a table view with fixed column widths.

    • This requires the nontrivial effort of authoring a formatting file (*._Format.ps1xml), which must be loaded into the session first.

    • As a simpler alternative to defining a specific .NET type associated with your formatting data, you can add a PSTypeName property to your [pscustomobject] output objects (e.g, [pscustomobject] @{ PSTypeName = &#39;My.Type&#39;; ID = $i; ... })


<sup>[1] You can, however, combine a Format-* call with Out-Host (e.g. ... | Format-List | Out-Host) if you want to use non-default formatting.</sup>

huangapple
  • 本文由 发表于 2023年7月13日 22:20:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76680437.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定