0

I am experimenting with Powershell runspaces and have noticed a difference in how output is written to the console depending on where I create my custom object. If I create the custom object directly in my script block, the output is written to the console in a table format. However, the table appears to be held open while the runspace pool still has open threads, i.e. it creates a table but I can see the results from finished jobs being appended dynamically to the table. This is the desired behavior. I'll refer to this as behavior 1.

The discrepancy occurs when I add a custom module to the runspace pool and then call a function contained in that module, which then creates a custom object. This object is printed to the screen in a list format for each returned object. This is not the desired behavior. I'll call this behavior 2

I have tried piping the output from behavior 2 to Format-Table but this just creates a new table for each returned object. I can achieve the desired effect somewhat by using Write-Host to print a line of the object values but I don't think this is appropriate considering it seems there is a built in behavior that can achieve my desired result if I can understand it.

My thoughts on the matter are that it has something to do with the asynchronous behavior of the runspace. I'm new to powershell but perhaps when the custom object comes directly from the script block there is a hidden method or type declaration telling powershell to hold the table open and wait for result? This would be overridden when using the second technique because its coming from my custom function?

I would like to understand why this is occurring and how I can achieve behavior 1 while being able to use the custom module, which will eventually be very large. I'm open to a different method technique as well, so long as its possible to essentially see the table of outputs grow as jobs finish. The code used is below.

$ISS = [InitialSessionState]::CreateDefault()
[void]$ISS.ImportPSModule(".\Modules\Test-Item.psm1")
$Pool = [RunspaceFactory]::CreateRunspacePool(1, 5, $ISS, $Host)
$Pool.Open()
$Runspaces = @()

# Script block to run code in
$ScriptBlock = {
    Param ( [string]$Server, [int]$Count )
    Test-Server -Server $Server -Count $Count

    # Uncomment the three lines below and comment out the two
    # lines above to test behavior 1.
    #[int] $SleepTime = Get-Random -Maximum 4 -Minimum 1
    #Start-Sleep -Seconds $SleepTime
    #[pscustomobject]@{Server=$Server; Count=$Count;}
}


# Create runspaces and assign to runspace pool
1..10 | ForEach-Object {
    $ParamList = @{ Server = "Server A" Count = $_ }
    $Runspace = [PowerShell]::Create()
    [void]$Runspace.AddScript($ScriptBlock)
    [void]$Runspace.AddParameters($ParamList)
    $Runspace.RunspacePool = $Pool
    $Runspaces += [PSCustomObject]@{
        Id = $_
        Pipe = $Runspace
        Handle = $Runspace.BeginInvoke()
        Object = $Object
    }
}

# Check for things to be finished
while ($Runspaces.Handle -ne $null)
{
    $Completed = $Runspaces | Where-Object { $_.Handle.IsCompleted -eq $true }

    foreach ($Runspace in $Completed)
    {
        $Runspace.Pipe.EndInvoke($Runspace.Handle)
        $Runspace.Handle = $null
    }

    Start-Sleep -Milliseconds 100
}

$Pool.Close()
$Pool.Dispose()

The custom module I'm using is as follows.

function Test-Server {
    Param ([string]$Server, [int]$Count )
    [int] $SleepTime = Get-Random -Maximum 4 -Minimum 1
    Start-Sleep -Seconds $SleepTime

    [pscustomobject]@{Server = $Server;Item = $Count}
}
Collin Craige
  • 109
  • 1
  • 8
  • 1
    Can you actually reproduce the issue with code in question? Given errors in your code, I presume you do not test it yourself. – user4003407 Jul 28 '18 at 03:19
  • The error is due to me simplyfying the code for SO. I left an extra parameter in there. I'll edit the post. I can reproduce both behaviors on my machine. – Collin Craige Jul 28 '18 at 03:32
  • 1
    But if you actually verify that your simplified code allows to reproduce the issue, why not just copy-paste working simplified code into question? – user4003407 Jul 28 '18 at 03:45
  • I misunderstood your initial comment. I did not check what is posted, but just did and it works as desired. I did not check it because all I changed was removing extra values in the module custom object. I added the $SleepTime variable and a custom message (I am here) to that object. However, I misspelled $SleepTime and was too distracted by the dramatic change in output to notice. But why the output change? Why not an error or a table with an empty value for $SleepTime like the list format? Can you explain as an answer? – Collin Craige Jul 28 '18 at 04:30
  • Some added wierdness. If I add more than 4 variables the output goes back to being a list format. Also, the output change appears to be more of a me not saving when I make changes. However, I still don't see why an error wouldn't be thrown. IS an error being thrown but just not making it back to my main power she thread? – Collin Craige Jul 28 '18 at 04:46
  • Unless you provide the code, which allows to reproduce described behavior, I do not see how I can help you. And, BTW, `(Begin/End)Invoke` does not return anything besides successful output. If you need something else, like errors, warnings, etc., then you need to inspect [`Streams`](https://learn.microsoft.com/dotnet/api/system.management.automation.powershell.streams) property. – user4003407 Jul 28 '18 at 06:09

2 Answers2

0

What you have mentioned sounds completely normal to me. That is how powershell is designed because it shares the burden of display. If the user has not specified how to display, PowerShell decides how to.

I couldn't reproduce your issue with the code provided but I think this will solve your problem.

$FinalTable = foreach ($Runspace in $Completed)
{
    $Runspace.Pipe.EndInvoke($Runspace.Handle)
    $Runspace.Handle = $null
}

$FinalResult will now have the table format you expect.

Sid
  • 2,586
  • 1
  • 11
  • 22
0

It appears that my primary issue, aside from errors in my code, was a lack of understanding related to powershell's default object handling. Powershell displays the output of objects as a table when there are less than four key-value pairs and as a list when there are more.

The custom object returned in my test module had more than for key-value pairs while the custom object I returned directly only had two. This resulted in what I thought was odd behavior. I compounded the issue by removing some key-value pairs in my posted code to shorten it and then didn't test it (sorry).

This stackoverflow post has a lengthy answer explaining the behavior some and providing examples for changing the default output.

Collin Craige
  • 109
  • 1
  • 8