1

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.

Maduser
  • 13
  • 3
  • Just pipe the results to Format-Table : {....} | Format-Table – jdweng Jul 13 '23 at 14:23
  • What is `GetSharedMailboxes`, and how does `$objectlist` get populated? – Mathias R. Jessen Jul 13 '23 at 14:48
  • I tried format table but I do not know how to add custom column "ID" - i just need something to give me index for every row. GetSharedMailboxes is another function that gets mailboxes and passes them to SearchSharedMailboxes function. But on this level everything works fine - as I wrote i get the output but only after script finishes. – Maduser Jul 13 '23 at 14:57

1 Answers1

0

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):[1]

# ...

        $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 = "Bar$_"
          }
        }
      }
    
      # Execute the pipeline:
      if ($DisplayOnly) {
        # Synchronous, but to-display output only.
        & $pipelineToExecute | Out-Host
      }
      else {
        # Normal output to the pipeline, but display-wise
        # delayed by the `pause` command below.
        & $pipelineToExecute
      }
    
      # Simulate a long-running command.
      # Without -DisplayOnly, the table doesn'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 = 'My.Type'; ID = $i; ... })


[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.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • I only need to produce display-like output. The trick with Out-Host cmdlet successfully resolved my issue. I really appreciate your help and the time you spent for writing this extensive reply. Wish you all the best :) Thank you! – Maduser Jul 13 '23 at 20:45
  • Glad to hear it, @Maduser; my pleasure. All the best to you as well. – mklement0 Jul 13 '23 at 20:48