# encoding: ascii
# api: powershell
# title: Invoke-async
# description: quick way to do processes a ton of jobs, for example ping 100 boxes at once
# version: 0.1
# type: function
# author: Justin
# license: CC0
# x-poshcode-id: 4542
# x-archived: 2016-01-03T20:00:46
# x-published: 2016-10-22T17:42:00
#
#
param(
#The data group to process, such as server names.
[parameter(Mandatory=$true,ValueFromPipeLine=$true)]
[object[]]$Set,
#The parameter name that the set belongs to, such as Computername.
[parameter(Mandatory=$true)]
[string] $SetParam,
#The Cmdlet for Function you'd like to process with.
[parameter(Mandatory=$true, ParameterSetName='cmdlet')]
[string]$Cmdlet,
#The ScriptBlock you'd like to process with
[parameter(Mandatory=$true, ParameterSetName='ScriptBlock')]
[scriptblock]$ScriptBlock,
#any aditional parameters to be forwarded to the cmdlet/function/scriptblock
[hashtable]$Params,
#number of jobs to spin up, default being 10.
[int]$ThreadCount=10,
#return performance data
[switch]$Measure
)
Begin
{

    $Threads = @()
    $Length = $JobsLeft = $Set.Length

    $Count = 0
    if($Length -lt $ThreadCount){$ThreadCount=$Length}
    $timer = @(1..$ThreadCount  | ForEach-Object{$null})
    $Jobs = @(1..$ThreadCount  | ForEach-Object{$null})
    
    If($PSCmdlet.ParameterSetName -eq 'cmdlet')
    {
        $CmdType = (Get-Command $Cmdlet).CommandType
        if($CmdType -eq 'Alias')
        {
            $CmdType = (Get-Command (Get-Command $Cmdlet).ResolvedCommandName).CommandType
        }
        
        If($CmdType -eq 'Function')
        {
            $ScriptBlock = (Get-Item Function:\$Cmdlet).ScriptBlock
            1..$ThreadCount | ForEach-Object{ $Threads += [powershell]::Create().AddScript($ScriptBlock)}
        }
        ElseIf($CmdType -eq "Cmdlet")
        {
            1..$ThreadCount  | ForEach-Object{ $Threads += [powershell]::Create().AddCommand($Cmdlet)}
        }
    }
    Else
    {
        1..$ThreadCount | ForEach-Object{ $Threads += [powershell]::Create().AddScript($ScriptBlock)}
    }

    If($Params){$Threads | ForEach-Object{$_.AddParameters($Params) | Out-Null}}

}
Process
{
    while($JobsLeft)
    {
        for($idx = 0; $idx -lt ($ThreadCount-1) ; $idx++)
        {
            $SetParamObj = $Threads[$idx].Commands.Commands[0].Parameters| Where-Object {$_.Name -eq $SetPa
ram}
             
            If($Jobs[$idx].IsCompleted) #job ran ok, clear it out
            {  
                $result = $null
                if($threads[$idx].InvocationStateInfo.State -eq "Failed")
                {
                    $result  = $Threads[$idx].InvocationStateInfo.Reason
                    Write-Error "Set Item: $($SetParamObj.Value) Exception: $result"
                }
                else
                { 
                    $result = $Threads[$idx].EndInvoke($Jobs[$idx])
                }
                $ts = (New-TimeSpan -Start $timer[$idx] -End (Get-Date))
                if($Measure)
                {
                    new-object psobject -Property @{
                        TimeSpan = $ts
                        Output = $result
                        SetItem = $SetParamObj.Value}
                }
                else
                {
                    $result
                }
                $Jobs[$idx] = $null
                $JobsLeft-- #one less left
                write-verbose "Completed: $($SetParamObj.Value) in $ts"
                write-progress -Activity "Processing Set" -Status "$JobsLeft jobs left" -PercentComplete ((
$length-$jobsleft)/$length*100)
            }
            If(($Count -lt $Length) -and ($Jobs[$idx] -eq $null)) #add job if there is more to process
            {
                write-verbose "starting: $($Set[$Count])"
                $timer[$idx] = get-date
                $Threads[$idx].Commands.Commands[0].Parameters.Remove($SetParamObj) | Out-Null #check for s
uccess?
                $Threads[$idx].AddParameter($SetParam,$Set[$Count]) | Out-Null
                $Jobs[$idx] = $Threads[$idx].BeginInvoke()
                $Count++
            }
        }

    }
}
End
{
    $Threads | ForEach-Object{$_.runspace.close();$_.Dispose()}
}
