1

I have this code working well synchronously with powershell.Invoke() however with powershell.BeginInvoke() I am not able to capture the output. To use the below code you'll need to populate the $servers variable and it should run otherwise.

I tried to capture the output by adding each thread to the $threads variable while using EndInvoke() and I am able to see the thread handle and the iscompleted value, however I can't figure out where the value I am returning with the return portion of each function is stored.

The first block is the output I see, showing false for one async being finished until it finishes and then all thread handles show true.

Thanks!

8804 is True
16420 is True
13352 is True
11184 is True
3872 is True
8288 is True
17296 is False
20816 is True
11628 is True
17688 is True
12856 is True
19400 is True

8804 is True
16420 is True
13352 is True
11184 is True
3872 is True
8288 is True
17296 is True
20816 is True
11628 is True
17688 is True
12856 is True
19400 is True


Thread count: 12
Time elapsed: 3


cls;

$stopwatch =  [system.diagnostics.stopwatch]::StartNew();

#region Runspace Pool

[runspacefactory]::CreateRunspacePool() | Out-Null;
$SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault();
$RunspacePool = [runspacefactory]::CreateRunspacePool(

    1, #Min Runspaces

    16 #Max Runspaces

);
$RunspacePool.Open();

#endregion Runspace pool

$threads = New-Object System.Collections.ArrayList;
$servers = @("goodServer1", "goodServer2", "badServer1", "goodServer3");

foreach ($server in $servers)
{
    $PowerShell = [powershell]::Create();
    $PowerShell.RunspacePool = $RunspacePool;

    [void]$PowerShell.AddScript({

        Param ($server, $portNumber)
        
        [pscustomobject]@{

            server = $server
            portNumber = $portNumber

        } | Out-Null
    
        Function testPort ($server, $portNumber)
        {
            $testPort = New-Object System.Net.Sockets.TCPClient # -ArgumentList $server, 3389;
            $testPort.SendTimeout = 3;
            try
            {
                $testPort.Connect($server, $portNumber);
            }
            catch
            {
                #do nothing;
            }
            $result = $testPort.Connected;
            $testPort.Close();          
            $dateTime = ([DateTime]::Now.ToString());
            
            return "$server|testPort|$result|$dateTime"; # server | function | result | DateTime
        }


        testPort -server $server -portNumber $portNumber;
    }) # end of add script

    $portNumber = "3389";
    $PowerShell.AddParameter('server', $server).AddParameter('portNumber', $portNumber) | Out-Null;
    $returnVal = $PowerShell.BeginInvoke();
    $temp = "" | Select PowerShell,returnVal;
    $temp.PowerShell = $PowerShell;
    $temp.returnVal = $returnVal;
    $threads.Add($Temp) | Out-Null;
    
    
    $PowerShell = [powershell]::Create();
    $PowerShell.RunspacePool = $RunspacePool;
    [void]$PowerShell.AddScript({

        Param ($server, $shareName, $timeOutInMs)
        
        [pscustomobject]@{

            server = $server
            shareName = $shareName
            timeOutInMs = $timeOutInMs

        } | Out-Null
    

        Function testShare ($server, $shareName, $timeOutInMs)
        {
        $cSharp = 
@'
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace cSharp7
{
    public class cSharpClass
    {
        public bool verifyDirectoryExists(string uri, int timeoutInMs)
        {
            var task = new Task<bool>(() =>
            {
                var dir = new DirectoryInfo(uri);
                return dir.Exists;
            });
            task.Start();
            return task.Wait(timeoutInMs) && task.Result;
        }
        
        public bool verifyFileExists(string uri, int timeoutInMs)
        {
            var task = new Task<bool>(() =>
            {
                var fi = new FileInfo(uri);
                return fi.Exists;
            });
            task.Start();
            return task.Wait(timeoutInMs) && task.Result;
        }
    }
}
'@

            $assemblies = ("System", "System.Collections", "System.ComponentModel", "System.Data", "System.Drawing", "System.Linq", "System.Threading.Tasks", "System.Windows.Forms", "System.Management.Automation", "System.Security", "System.Threading", "System.Collections.Concurrent", "System.Security.Principal", "System.Management", "System.IO", "System.Collections");
            Add-Type -TypeDefinition $cSharp -ReferencedAssemblies $assemblies -Language CSharp;
            $directoryExists = New-Object CSharp7.cSharpClass;
            $path = "\\" + $server + "\" + $shareName;
            try
            {
                $result = $directoryExists.verifyDirectoryExists($path, $timeOutInMs); # has a 2 minute timeout period, needs an asynchronous thread with a timeout period
                #Write-Host $result;
            }
            catch
            {
                # do nothing
            }
            
            $dateTime = ([DateTime]::Now.ToString());
            return "$server|testShare|$result|$dateTime"; # server | function | result | DateTime
        }

        testShare -server $server -shareName $shareName -timeOutInMs $timeOutInMs;
    }) # end of add script

    $shareName = "c$";
    $timeOutInMs = "3000";
    $PowerShell.AddParameter('server', $server).AddParameter('shareName', $shareName).AddParameter('timeOutInMs', $timeOutInMs) | Out-Null;
    $returnVal = $PowerShell.BeginInvoke();
    $temp = "" | Select PowerShell,returnVal;
    $temp.PowerShell = $PowerShell;
    $temp.returnVal = $returnVal;
    $threads.Add($Temp) | Out-Null;
    
    
    $PowerShell = [powershell]::Create();
    $PowerShell.RunspacePool = $RunspacePool;
    [void]$PowerShell.AddScript({

        Param ($server, $pingCount)
        
        [pscustomobject]@{

            server = $server
            pingCount = $pingCount

        } | Out-Null
    

        Function testPing ($server, $pingCount)
        { 
            try
            {
                $result = Test-Connection $server -Count $pingCount -Quiet;
            }
            catch
            {
                # do nothing
            }
            
            $dateTime = ([DateTime]::Now.ToString());
            
            return "$server|testPing|$result|$dateTime"; # server | function | result | DateTime
        } 
        
        testPing -server $server -pingCount $pingCount;
    }) # end of add script

    $pingCount = "1";
    $PowerShell.AddParameter('server', $server).AddParameter('pingCount', $pingCount) | Out-Null;
    $returnVal = $PowerShell.BeginInvoke();
    $temp = "" | Select PowerShell,returnVal;
    $temp.PowerShell = $PowerShell;
    $temp.returnVal = $returnVal;
    $threads.Add($Temp) | Out-Null;
}


$completed = $false;
while ($completed -eq $false)
{
    $completed = $true;
    
    foreach ($thread in $threads)
    {
        $endInvoke = $thread.PowerShell.EndInvoke($thread.returnVal);
        $endInvoke;
        $threadHandle = $thread.returnVal.AsyncWaitHandle.Handle;
        $threadIsCompleted = $thread.returnVal.IsCompleted;
        #Write-Host "$threadHandle is $threadIsCompleted";
        if ($threadIsCompleted -eq $false)
        {
            $completed = $false;
        }
    }
    Write-Host "";
    sleep -Milliseconds 500;
}

foreach ($thread in $threads)
{
    $thread.PowerShell.Dispose();
}

$stopwatch.Stop();

Write-Host "";
Write-Host "Thread count:" $threads.Count;
Write-Host "Time elapsed:" $stopwatch.Elapsed.Seconds;
Bbb
  • 517
  • 6
  • 27
  • What is your expected output? How should the returned object look like? It's really hard to tell looking at your code, for the C# function why not use [`Test-Path`](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/test-path?view=powershell-7.1)? – Santiago Squarzon Apr 30 '21 at 19:10

1 Answers1

2

Here is how you capture the return value data. You define the custom object $temp with 2 properties names, in thise case Powershell and returnVal. Then you add them to an array list. After the async BeginInvoke is completed you can call EndInvoke against the asyncResult by doing this $endInvoke = $thread.PowerShell.EndInvoke($thread.returnVal); and parse it however you want.

This has to be the most complicated script I've ever written with a runspace pool, runspaces, asynchronous returns, functions being passed and even some c# mixed in. Hopefully others can draw from parts or all of this.

$returnVal = $PowerShell.BeginInvoke();
$temp = "" | Select PowerShell,returnVal;
$temp.PowerShell = $PowerShell;
$temp.returnVal = $returnVal;
$threads.Add($Temp) | Out-Null;

$completed = $false;
while ($completed -eq $false)
{
    $completed = $true;
    
    foreach ($thread in $threads)
    {
        $endInvoke = $thread.PowerShell.EndInvoke($thread.returnVal);
        $endInvoke;
        $threadHandle = $thread.returnVal.AsyncWaitHandle.Handle;
        $threadIsCompleted = $thread.returnVal.IsCompleted;
        #Write-Host "$threadHandle is $threadIsCompleted";
        if ($threadIsCompleted -eq $false)
        {
            $completed = $false;
        }
    }
    Write-Host "";
    sleep -Milliseconds 500;
}

foreach ($thread in $threads)
{
    $thread.PowerShell.Dispose();
}
Bbb
  • 517
  • 6
  • 27