Stefan Korte
Stefan Korte

Reputation: 31

Powershell runspaces won't execute

I'm at a bit of a loss with the script I am trying to pull.

In short: I want to scan my domain-computers for WinRM connectivity - and I can do that just fine. The problem is, that it takes up to 5 minutes to finish - thats why I want to multithread the task.

Working NON MULTITHREAD code:

# this is normaly a textfile with lots of machine hostnames
$computers = "PC100","PC106","PC124","PC115","PC21"

function checkMachine($computers){

  $ErrorActionPreference = "Stop"

  foreach ($item in $computers){
    #the function contest only performs a ping and returne $true or $false
    $connection = ConTest($item)
    if($connection){
      try{
        $winRM = test-wsman -ComputerName $item
        if($winRM){
          write-host "winRM"
          [void] $objListboxLeft.Items.Add($item)
        }
      }catch{
        write-host "NO winRM"
        [void] $objListboxCenter.Items.Add($item)
      }
    }else{
      write-host "offline"
      [void] $objListboxRight.Items.Add($item)
    }
  }
}

this is basically just a small portion of what my skript does/will do but it's the part that takes ages.

My failing runspace test - I basically fail to get ANY results at all. Nothing in textboxes, no output on my commandline and I basically have no idea what I am doing wrong.

Multithread code:

function MulticheckMachine($computers){
 
  $ErrorActionPreference = "Stop"

  $runspaceCollection = @()
  $runspacePool = [RunspaceFactory]::CreateRunspacePool(1,5)
  $runspacePool.open()

  $scriptBlock = {
    Param($item)

      $connection = ConTest($item)
      if($connection){
        try{
          test-wsman -ComputerName $item
          $winRM = test-wsman -ComputerName $item
          if($winRM){
            write-host "winRM"
            [void] $objListboxLeft.Items.Add($item)
          }
        }catch{
          write-host "NO winRM"
          [void] $objListboxCenter.Items.Add($item)
        }
      }else{
        write-host "offline"
        [void] $objListboxRight.Items.Add($item)
      }
  }

  Foreach($item in $computers){
    $powershell = [PowerShell]::Create().AddScript($scriptBlock).AddArgument($item)
    $powershell.runspacePool = $runspacePool
      
    
    [Collections.Arraylist]$runspaceCollection += New-Object -TypeName PSObject -Property @{
      Runspace = $powershell.BeginInvoke()
      PowerShell = $powershell  
    }
    $runspaceCollection
  }

    
  While($runspaceCollection){
   Foreach($runspace in $runspaceCollection.ToArray()){
    If($runspace.Runspace.IsCompleted){
     $runspace.PowerShell.EndInvoke($runspace.Runspace)
     $runspace.PowerShell.Dispose()
     $runspaceCollection.Remove($runspace)
    }
   }
  }
}

the runspace code comes from a mix of these guides:

http://blogs.technet.com/b/heyscriptingguy/archive/2013/09/29/weekend-scripter-max-out-powershell-in-a-little-bit-of-time-part-2.aspx

http://newsqlblog.com/2012/05/22/concurrency-in-powershell-multi-threading-with-runspaces/

I hope someone can help me out and tell me where/why I fail. Thanks!

Upvotes: 2

Views: 1801

Answers (1)

Stefan Korte
Stefan Korte

Reputation: 31

Well, thanks for the hints but the problem was far more basic.

I was trying to get my data at the wrong position. Also, I simplified my script a bit. I don't call functions in functions anymore.

Note1: I did not realize I can/need to work with return values within my scriptblock for the runspace.

Note2: I am now collecting my data and inserting it into my listboxes (or where-ever else I wanted to) at the end of my function within the while loop - where I basically build-back my runspaces.

Note3: All "GUI parts" I reference to are located in a different file and do exist!

I got the duration down to roughly 20 seconds (from almost 5 minutes before)

The number of threads I use is a bit random, it's one of the combinations that works fastest.

Code:

function multiCheckMachine($computers){
  $ErrorActionPreference = "Stop"

  $runspaceCollection = @()
  $runspacePool = [RunspaceFactory]::CreateRunspacePool(1,50)
  $runspacePool.open()

  $scriptBlock = {
    Param($item)
    $FQDNitem = "$item.domain.com"
    $address = nslookup $FQDNitem

    if($address -like "addresses*"){
      $address = $address[5] -replace ".* ",""
    }else{
      $address = $address[4] -replace ".* ",""
    }

    $con = ping -n 1 $address
    if($con[2] -like "*Bytes*"){
      $winRM = test-wsman -ComputerName $item
      if($winRM){
        return "$item.winRM"
      }else{
        return "$item.NOremote"  
      }

    }else{
      return "$item.offline"
    }
  }

  Foreach($item in $computers){
    $powershell = [PowerShell]::Create().AddScript($scriptBlock).AddArgument($item)
    $powershell.runspacePool = $runspacePool


    [Collections.Arraylist]$runspaceCollection += New-Object -TypeName PSObject -Property @{
      Runspace = $powershell.BeginInvoke()
      PowerShell = $powershell  
    }
  }

  While($runspaceCollection){
   Foreach($runspace in $runspaceCollection.ToArray()){
    If($runspace.Runspace.IsCompleted){

      if($runspace.PowerShell.EndInvoke($runspace.Runspace) -like "*winrm"){
        [void] $objListboxOnline.Items.Add($runspace.PowerShell.EndInvoke($runspace.Runspace).split(".")[0])

      }elseif($runspace.PowerShell.EndInvoke($runspace.Runspace) -like "*NOremote"){
        [void] $objListboxNoWinRM.Items.Add($runspace.PowerShell.EndInvoke($runspace.Runspace).split(".")[0])

      }elseif($runspace.PowerShell.EndInvoke($runspace.Runspace) -like "*offline"){
        [void] $objListboxOffline.Items.Add($runspace.PowerShell.EndInvoke($runspace.Runspace).split(".")[0])
      }   

     $runspace.PowerShell.Dispose()
     $runspaceCollection.Remove($runspace)
    }
   }
  }
}

Upvotes: 1

Related Questions