Forum Discussion

JeremyTBradshaw's avatar
JeremyTBradshaw
Steel Contributor
Aug 28, 2023

Tee-Object / -OutVariable into existing array (i.e., add into that array - is this doable?)

Hello,

 

I have some Exchange reporting scripts where I'd like to offer the option to export CSV automatically but always send the output to standard output stream as well.  I though about using Tee-Object like this:

 

 

foreach ($y in $yadayada) {
    [PSCustomObject]@{
        p1 = $y.v1
        p2 = $y.v2
    } | Tee-Object -Variable preExistingArray
}

 

 

I would later then like to do this:

 

 

if ($ExportCSVs) { $preExistingArray | Export-Csv ... }

 

 

 ...and by that time, nobody was waiting at the console for all their objects to collect before being output all at once.  That is the difference from my usual approach which is more like:  1-Collect everything into variable;2-output variable (all objects) to std output stream; 3-export to CSV.  Thought I could use Tee-Object to combine 1 and 2.  I don't think it's possible.

 

So, wondering if anyone else has something clever in this area?  Please do share.

  • LainRobertson's avatar
    LainRobertson
    Silver Contributor

    JeremyTBradshaw 

     

    Hey, Jeremy.

     

    The first issue where Tee-Object will let you down is that only one format will apply to both the file as well as the screen output (at least within Windows PowerShell - PowerShell has some options I won't get into here), meaning you cannot have the usual table (or list) format for the screen while having a CSV format for the file. Table (or list) format doesn't make for a useful file while CSV presentation isn't fun to read on-screen.

     

    Next, you're accruing arrays ($yadyada plus $preExistingArray, plus others if you keep growing this approach) which doesn't scale well in larger environments. It will be far more efficient (over large data sets, at least) to work with a single pipeline object approach.

     

    This approach lends itself to then doing what you cannot with Tee-Object, which is to use the default format for the screen while still being able to export to a file in CSV format. It also solves your other challenge (subject to not using post-processing commandlets like Sort-Object, Group-Object, etc.) around displaying objects "immediately".

     

    It's also worth pointing out that there's nothing inherently efficient with Tee-Object from a performance perspective. It's just saving a little bit of extra coding at best.

     

    Here's a basic example (since I'm unsure how your current scripts operate) that leverages the single pipeline object approach that sends the object both to the pipeline as well as the CSV file, which avoids the performance hits from large data sets multiplied by additional arrays.

     

    Example

    $csvFilename = "D:\Data\Temp\Forum\forum.csv";
    Remove-Item -Path $csvFilename -ErrorAction:SilentlyContinue;
    
    # Work with the current object on the pipeline and let it be released once all processing is done.
    Get-EXOMailbox -Anr "test.mailbox" -Properties ExchangeGuid, PrimarySmtpAddress |
        ForEach-Object {
            $mailbox = $_;
    
            $mailbox |
                Get-EXOMailboxStatistics -Properties MailboxTypeDetail, TotalItemSize |
                    ForEach-Object {
                        $output = [PSCustomObject] @{
                            id = $mailbox.ExchangeGuid;
                            mail = $mailbox.PrimarySmtpAddress;
                            type = $_.MailboxTypeDetail;
                            size = $_.TotalItemSize.Value.ToBytes();
                        }
    
                        $output | Export-Csv -NoTypeInformation -Path $csvFilename;
                        $output;
                    }
        }

     

    Screen output

     

    CSV file output

     

    Cheers,

    Lain

    • JeremyTBradshaw's avatar
      JeremyTBradshaw
      Steel Contributor
      Appreciate the thorough response. I think I will hang the Tee-Object hat here! It's one of those ones I've never had the luxury and almost seemed like I finally got it. But I was finding similar things earlier, not seeing a lot of gain with Tee anyway.
  • sgnewman's avatar
    sgnewman
    Copper Contributor

    JeremyTBradshaw 

    This is how I solved this:

    using namespace System.Collections.Generic;
    [List[string]]$msg = [List[string]]::new();
    
    $logFile = "c:\logs\log-$((get-date).ToString('yyyyMMdd-hhmmss')).log"
    1..20 | %{
        "Message $_" | Tee-Object -FilePath $logFile -Append | Select-Object @{n='void';e={[void]$Warnings.Add($_);}} | Out-Null
    }
    
    $msg 

     Basically I just use\abuse the select-object's expression to append to a collection...I THINK this fits what you're aiming to do?

     

    Scott

    • JeremyTBradshaw's avatar
      JeremyTBradshaw
      Steel Contributor
      Thanks for the code sample and checking with me. I ended up doing different things - https://github.com/JeremyTBradshaw/PowerShell/blob/main/Get-MailboxFolderPermissionReport.ps1

      Essentially there are 2 modes to pick between - Summary or Detailed. In either mode, there's an optional CSV export which, regardless of the mode, will output CSVs for both Summary and Detailed output. So if exporting CSV, you get 2 CSVs no matter what, while screen gets just the chosen mode's output.

      Definitely over engineered to the Tee's (Tee-Object pun intended), but this is sometimes more fun than other mundane things in a work day.

Resources