RunspacePoolManager.psm1

function Start-RunspacePool {
<#
    .EXTERNALHELP RunspacePoolManager.psm1-Help.xml
#>


    [CmdletBinding(ConfirmImpact = 'Medium',
                   SupportsShouldProcess = $false)]
    [alias("Start-RSP")]
    [OutputType([System.Management.Automation.Runspaces.RunspacePool])]
    param
    (
        [Parameter(Position = 0)]
        [ValidateNotNullOrEmpty()]
        [int]$MaxJobs = $env:NUMBER_OF_PROCESSORS,
        [Parameter(Position = 1)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Runspaces.InitialSessionState]$InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault(),
        [Parameter(Position = 2)]
        [ValidateScript({
                if ($_ -match '(([0-1][0-9])|([2][0-3])):([0-5][0-9]):([0-5][0-9])') {
                    return $true
                }
                else {
                    Write-Warning -Message "The CleanupInterval must be HH:MM:SS."
                }
            })]
        [System.TimeSpan]$CleanupInterval = "00:15:00"
    )
    
    begin {
        $functionName = $($MyInvocation.MyCommand).Name
        Write-Verbose -Message "$functionName`: Entering the begin block."
    }
    process {
        Write-Verbose -Message "$functionName`: Entering the process block."
        try {
            Write-Verbose -Message "$functionName`: Starting runspace pool with $MaxJobs max jobs."
            $return = [runspacefactory]::CreateRunspacePool(1, $MaxJobs, $initialSessionState, $host)
            $return.CleanupInterval = $CleanupInterval
            $powerShell = [powershell]::Create()
            $powerShell.RunspacePool = $return
            $return.Open()
        }
        catch {
            $exception = "$($_.Exception.GetType().FullName)"
            $msg = "Unhandled error: $_"
            Write-Warning -Message $exception
            Write-Warning -Message $msg
        }
    }
    end {
        Write-Verbose -Message "$functionName`: Entering the end block."
        if (-not ([string]::IsNullOrEmpty($return))) {
            Write-Output $return
        }
    }
}

function Start-RunspacePoolJob {
<#
    .EXTERNALHELP RunspacePoolManager.psm1-Help.xml
#>


    [CmdletBinding(ConfirmImpact = 'Medium',
                   SupportsShouldProcess = $false)]
    [alias("Start-RSPJob")]
    param
    (
        [Parameter(Mandatory = $true,
                   Position = 0)]
        [ValidateNotNullOrEmpty()]
        [scriptblock]$ScriptBlock,
        [Parameter(Mandatory = $true,
                   Position = 1)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Runspaces.RunspacePool]$RunspacePool,
        [Parameter(Mandatory = $false,
                   Position = 0)]
        [System.Collections.Hashtable]$HashTable
    )
    
    begin {
        $functionName = $($MyInvocation.MyCommand).Name
        Write-Verbose -Message "$functionName`: Entering the begin block."
        if ($RunspacePool.IsDisposed) {
            Write-Error -Message "The runspace pool has been closed." -Category InvalidOperation
            break
        }
    }
    process {
        Write-Verbose -Message "$functionName`: Entering the process block."
        try {
            Write-Verbose -Message "$functionName`: Processing script block for using variables."
            #https://github.com/proxb/PoshRSJob/blob/master/PoshRSJob/Public/Start-RSJob.ps1
            $usingVariables = @(GetUsingVariables $ScriptBlock)
            if ($usingVariables.count -gt 0) {
                Write-Verbose -Message "$functionName`: Using variables were found. Processing script using variables."
                $usingVar = $usingVariables | Group-Object SubExpression | ForEach-Object {
                    $_.Group | Select-Object -First 1
                }
                #Write-Debug "CommandOrigin: $($MyInvocation.CommandOrigin)"
                $usingVariableValues = @(foreach ($var in $usingVar) {
                        try {
                            if ($MyInvocation.CommandOrigin -eq 'Runspace') {
                                $value = Get-Variable -Name $Var.SubExpression.VariablePath.UserPath
                            }
                            else {
                                $value = ($PSCmdlet.SessionState.PSVariable.Get($var.SubExpression.VariablePath.UserPath))
                                if ([string]::IsNullOrEmpty($Value)) {
                                    throw 'No value!'
                                }
                            }
                            [pscustomobject]@{
                                Name = $var.SubExpression.Extent.Text
                                Value = $value.Value
                                NewName = ('$__using_{0}' -f $var.SubExpression.VariablePath.UserPath)
                                NewVarName = ('__using_{0}' -f $var.SubExpression.VariablePath.UserPath)
                            }
                        }
                        catch {
                            throw "The value of the using variable '$($var.SubExpression.Extent.Text)' cannot be retrieved because it has not been set in the local session."
                        }
                    })
                #endregion Get Variable Values
                Write-Verbose -Message ("$functionName`: Found {0} `$Using: variables!" -f $usingVariableValues.count)
            }
            if (($UsingVariableValues.Count -gt 0) -or ($HashTable)) {
                Write-Verbose -Message "$functionName`: Converting script with using variables."
                $convertScriptParams = @{
                    ScriptBlock         = $ScriptBlock
                    HasParam            = ($SBParamVars.Count -ne 0)
                    UsingVariables      = $UsingVariables
                    UsingVariableValues = $UsingVariableValues
                    HashTable           = $HashTable
                }
                $NewScriptBlock = ConvertScript @convertScriptParams
            }
            else {
                $NewScriptBlock = $ScriptBlock
            }
            Write-Verbose -Message "$functionName`: running:"
            Write-Verbose -Message "$functionName`: $($NewScriptBlock | Out-String)"
            #https://github.com/proxb/PoshRSJob/blob/master/PoshRSJob/Public/Start-RSJob.ps1
            Write-Verbose -Message "$functionName`: Build the PowerShell runspace and assign it to the pool."
            $powerShell = [powershell]::Create()
            $powerShell.RunspacePool = $RunspacePool
            $powerShell.AddScript($NewScriptBlock) | Out-Null
            Write-Verbose "$functionName`: Checking for Using: variables"
            if ($UsingVariableValues.count -gt 0) {
                for ($i = 0; $i -lt $UsingVariableValues.count; $i++) {
                    Write-Verbose "$functionName`: Adding Param: $($UsingVariableValues[$i].Name) Value: $($UsingVariableValues[$i].Value)"
                    [void]$powerShell.AddParameter($UsingVariableValues[$i].NewVarName, $UsingVariableValues[$i].Value)
                }
            }
            if ($HashTable) {
                Write-Verbose "$functionName`: Adding HashTable param to script."
                [void]$powerShell.AddParameter('HashTable', $HashTable)
            }
            $return = $powerShell.BeginInvoke()
        }
        catch {
            $continue = $false
            $exception = "$($_.Exception.GetType().FullName)"
            $msg = "Unhandled error: $_"
            Write-Warning -Message $exception
            Write-Warning -Message $msg
        }
    }
    end {
        Write-Verbose -Message "$functionName`: Entering the end block."
        if ($return) {
            Write-Output $return
        }
    }
}

function Stop-RunspacePool {
<#
    .EXTERNALHELP RunspacePoolManager.psm1-Help.xml
#>


    [CmdletBinding(ConfirmImpact = 'High',
                   SupportsShouldProcess = $true)]
    [alias("Stop-RSP")]
    param
    (
        [Parameter(Mandatory = $true,
                   Position = 0)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Runspaces.RunspacePool]$RunspacePool,
        [switch]$Force
    )
    
    begin {
        $functionName = $($MyInvocation.MyCommand).Name
        Write-Verbose -Message "$functionName`: Entering the begin block."
    }
    process {
        Write-Verbose -Message "$functionName`: Entering the process block."
        try {
            $msg = 'Running Stop-RunspacePool will terminate all jobs immediately and cancel all pending jobs.'
            $prompt = 'Do you want to continue?'
            if (($Force) -or ($PSCmdlet.ShouldProcess($msg, $prompt, $msg))) {
                Write-Verbose -Message "$functionName`: Stopping runspace pool with the ID: $($RunspacePool.InstanceId)."
                $RunspacePool.Close()
                $RunspacePool.Dispose()
            }
        }
        catch {
            $exception = "$($_.Exception.GetType().FullName)"
            $msg = "Unhandled error: $_"
            Write-Warning -Message $exception
            Write-Warning -Message $msg
        }
    }
    end {
        Write-Verbose -Message "$functionName`: Entering the end block."
    }
}

function New-SynchronizedHashTable {
<#
    .EXTERNALHELP RunspacePoolManager.psm1-Help.xml
#>


    [CmdletBinding(ConfirmImpact = 'Low',
                   SupportsShouldProcess = $false)]
    [OutputType([System.Collections.Hashtable])]
    param ()
    
    begin {
        $functionName = $($MyInvocation.MyCommand).Name
        Write-Verbose -Message "$functionName`: Entering the begin block."
    }
    process {
        Write-Verbose -Message "$functionName`: Entering the process block."
        try {
            Write-Verbose -Message "$functionName`: Creating synchronized hash table."
            $return = [hashtable]::Synchronized(@{
                })
        }
        catch {
            $exception = "$($_.Exception.GetType().FullName)"
            $msg = "Unhandled error: $_"
            Write-Warning -Message $exception
            Write-Warning -Message $msg
        }
    }
    end {
        Write-Verbose -Message "$functionName`: Entering the end block."
        Write-Output $return
    }
}

function New-HashTableKey {
<#
    .EXTERNALHELP RunspacePoolManager.psm1-Help.xml
#>


    [CmdletBinding(ConfirmImpact = 'Low',
                   SupportsShouldProcess = $false)]
    [alias("Add-HashTableKey")]
    [alias("New-HTKey")]
    [alias("Add-HTKey")]
    param
    (
        [Parameter(Mandatory = $true,
                   Position = 0)]
        [System.Collections.Hashtable]$HashTable,
        [Parameter(Mandatory = $true,
                   Position = 1)]
        [string]$Key,
        [Parameter(Mandatory = $false,
                   Position = 2)]
        $Value,
        [switch]$Force
    )
    
    begin {
        $functionName = $($MyInvocation.MyCommand).Name
        Write-Verbose -Message "$functionName`: Entering the begin block."
        if ($HashTable.IsReadOnly) {
            Write-Error -Message "Hash table is read only."
            break
        }
        if (Get-HashTableKey -HashTable $HashTable -Key $Key) {
            if ($Force) {
                Set-HashTableKey -HashTable $HashTable -Key $Key -Value $Value
                break
            }
            else {
                Write-Warning -Message "A key with the name '$Key' already exists in this hash table. Use -Force or Set-HashTableKey."
                break
            }
        }
    }
    process {
        Write-Verbose -Message "$functionName`: Entering the process block."
        try {
            Write-Verbose -Message "$functionName`: Adding key '$($Key.tostring())' and value '$($Value.tostring())'."
            $HashTable.Add($Key, $Value)
        }
        catch {
            $exception = "$($_.Exception.GetType().FullName)"
            $msg = "Unhandled error: $_"
            Write-Warning -Message $exception
            Write-Warning -Message $msg
        }
    }
    end {
        Write-Verbose -Message "$functionName`: Entering the end block."
        Write-Output $return
    }
}

function Get-HashTableKey {
<#
    .EXTERNALHELP RunspacePoolManager.psm1-Help.xml
#>


    [CmdletBinding(ConfirmImpact = 'Low',
                   SupportsShouldProcess = $false)]
    [alias("Get-HTKey")]
    param
    (
        [Parameter(Mandatory = $true,
                   Position = 0)]
        [System.Collections.Hashtable]$HashTable,
        [Parameter(Mandatory = $true,
                   Position = 1)]
        [string]$Key
    )
    
    begin {
        $functionName = $($MyInvocation.MyCommand).Name
        Write-Verbose -Message "$functionName`: Entering the begin block."
    }
    process {
        Write-Verbose -Message "$functionName`: Entering the process block."
        try {
            Write-Verbose -Message "$functionName`: Getting key '$Key'."
            $return = $HashTable["$Key"]
        }
        catch {
            $exception = "$($_.Exception.GetType().FullName)"
            $msg = "Unhandled error: $_"
            Write-Warning -Message $exception
            Write-Warning -Message $msg
        }
    }
    end {
        Write-Verbose -Message "$functionName`: Entering the end block."
        Write-Output $return
    }
}

function Remove-HashTableKey {
<#
    .EXTERNALHELP RunspacePoolManager.psm1-Help.xml
#>

    
    [CmdletBinding(ConfirmImpact = 'Low',
                   SupportsShouldProcess = $false)]
    [alias("Remove-HTKey")]
    param
    (
        [Parameter(Mandatory = $true,
                   Position = 0)]
        [System.Collections.Hashtable]$HashTable,
        [Parameter(Mandatory = $true,
                   Position = 1)]
        [string]$Key,
        [switch]$Force
    )
    
    begin {
        $functionName = $($MyInvocation.MyCommand).Name
        Write-Verbose -Message "$functionName`: Entering the begin block."
        if ((-not (Get-HashTableKey -HashTable $HashTable -Key $Key)) -and (-not ($Force))) {
            Write-Warning -Message "'$Key' does not exist in the hash table."
            break
        }
    }
    process {
        Write-Verbose -Message "$functionName`: Entering the process block."
        try {
            Write-Verbose -Message "$functionName`: Removing key '$Key'."
            $HashTable.Remove($Key)
        }
        catch {
            $exception = "$($_.Exception.GetType().FullName)"
            $msg = "Unhandled error: $_"
            Write-Warning -Message $exception
            Write-Warning -Message $msg
        }
    }
    end {
        Write-Verbose -Message "$functionName`: Entering the end block."
    }
}

function Set-HashTableKey {
<#
    .EXTERNALHELP RunspacePoolManager.psm1-Help.xml
#>


    [CmdletBinding(ConfirmImpact = 'Low',
                   SupportsShouldProcess = $false)]
    [alias("Set-HTKey")]
    param
    (
        [Parameter(Mandatory = $true,
                   Position = 0)]
        [System.Collections.Hashtable]$HashTable,
        [Parameter(Mandatory = $true,
                   Position = 1)]
        [string]$Key,
        [Parameter(Mandatory = $false,
                   Position = 2)]
        $Value,
        [switch]$Force
    )
    
    begin {
        $functionName = $($MyInvocation.MyCommand).Name
        Write-Verbose -Message "$functionName`: Entering the begin block."
        if ($HashTable.IsReadOnly) {
            Write-Error -Message "Hash table is read only."
            break
        }
        if (-not (Get-HashTableKey -HashTable $HashTable -Key $Key)) {
            if ($Force) {
                New-HashTableKey -HashTable $HashTable -Key $Key -Value $Value
                break
            }
            else {
                Write-Warning -Message "A key with the name '$Key' was not found in this hash table. Use -Force or New-HashTableKey"
                break
            }
        }
    }
    process {
        Write-Verbose -Message "$functionName`: Entering the process block."
        try {
            Write-Verbose -Message "$functionName`: Setting key '$($Key.tostring())' too value '$($Value.tostring())'."
            $HashTable["$Key"] = $Value
        }
        catch {
            $exception = "$($_.Exception.GetType().FullName)"
            $msg = "Unhandled error: $_"
            Write-Warning -Message $exception
            Write-Warning -Message $msg
        }
    }
    end {
        Write-Verbose -Message "$functionName`: Entering the end block."
    }
}