├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── CONTRIBUTING.md ├── Images ├── GetRSJob-ReceiveRSJob.gif ├── NanoPoshRSJob.png ├── PoshRSJob.gif ├── RSJobStreamingExample.gif └── RSjobExample1.gif ├── LICENSE ├── PoshRSJob ├── PoshRSJob.psd1 ├── PoshRSJob.psm1 ├── Private │ ├── ConvertScript.ps1 │ ├── ConvertScriptBlockV2.ps1 │ ├── FindFunction.ps1 │ ├── GetFunctionByFile.ps1 │ ├── GetFunctionDefinitionByFunction.ps1 │ ├── GetParamVariable.ps1 │ ├── GetUsingVariables.ps1 │ ├── GetUsingVariablesV2.ps1 │ ├── Increment.ps1 │ ├── RegisterScriptScopeFunction.ps1 │ ├── SetIsReceived.ps1 │ └── WriteStream.ps1 ├── Public │ ├── Get-RSJob.ps1 │ ├── Receive-RSJob.ps1 │ ├── Remove-RSJob.ps1 │ ├── Start-RSJob.ps1 │ ├── Stop-RSJob.ps1 │ └── Wait-RSJob.ps1 ├── Scripts │ └── TabExpansion.ps1 ├── TypeData │ ├── PoshRSJob.Format.ps1xml │ └── PoshRSJob.Types.ps1xml └── en-US │ └── about_PoshRSJob.help.txt ├── README.md ├── ReleaseNotes.md ├── Tests ├── PoshRSJob.Tests.ps1 └── appveyor.pester.ps1 └── appveyor.yml /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Do you want to request a *feature* or report a *bug*?** 2 | 3 | **What is the current behavior?** 4 | 5 | **If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem** 6 | 7 | **What is the expected behavior?** 8 | 9 | **Which versions of Powershell and which OS are affected by this issue? Did this work in previous versions of our scripts?** 10 | 11 | **Please provide a code example showing the issue, if applicable:** 12 | ```PowerShell 13 | #Code goes here 14 | ``` 15 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # . 2 | 3 | Changes proposed in this pull request: 4 | - 5 | - 6 | - 7 | 8 | How to test this code: 9 | - 10 | - 11 | - 12 | 13 | Has been tested on (remove any that don't apply): 14 | - Powershell 3 and above 15 | - Windows 7 and above 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the PoshRSJob 2 | 3 | First of all, welcome! We're excited that you'd like to contribute. How would you like to help? 4 | 5 | * [I'd like to report a bug or request an enhancement](#how-to-report-bugs-or-request-enhancements) 6 | * [How to Write or Update Documentation](#how-to-write-or-update-documentation) 7 | 8 | Everyone here is expected to abide by the [Contributor Covenant Code of Conduct](#the-contributor-covenant-code-of-conduct). 9 | 10 | Wanna do something else, or have a question not answered here? Email to boeprox@gmail.com 11 | 12 | 13 | ## How to Report Bugs or Request Enhancements 14 | Check out the [Github issues list]. Search for what you're interested in - there may already be an issue for it. Make sure to search through closed issues, too, because we often decline things that aren't a good fit for these tools. 15 | 16 | If you can't find a similar issue, go ahead and open your own. Include as much detail as you can - what you're seeing now, and what you'd like to see. 17 | 18 | When requesting new checks, keep in mind that we want to focus on: 19 | 20 | * Actionable warnings - SQL Server folks are usually overwhelmed with data, and we only want to report on things they can actually do something about 21 | * Performance issues or reliability risks - if it's just a setting we don't agree with, let's set that aside 22 | * Things that end users or managers will notice - if we're going to have someone change a setting on their system, we want it to be worth their time 23 | 24 | Now head on over to the [Github issues list] and get started. 25 | 26 | 27 | ## How to Write or Update Documentation 28 | (stub) 29 | 30 | 31 | ## The Contributor Covenant Code of Conduct 32 | 33 | ### Our Pledge 34 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 35 | 36 | 37 | ### Our Standards 38 | Examples of behavior that contributes to creating a positive environment 39 | include: 40 | 41 | * Using welcoming and inclusive language 42 | * Being respectful of differing viewpoints and experiences 43 | * Gracefully accepting constructive criticism 44 | * Focusing on what is best for the community 45 | * Showing empathy towards other community members 46 | 47 | Examples of unacceptable behavior by participants include: 48 | 49 | * The use of sexualized language or imagery and unwelcome sexual attention or 50 | advances 51 | * Trolling, insulting/derogatory comments, and personal or political attacks 52 | * Public or private harassment 53 | * Publishing others' private information, such as a physical or electronic 54 | address, without explicit permission 55 | * Other conduct which could reasonably be considered inappropriate in a 56 | professional setting 57 | 58 | ### Our Responsibilities 59 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 60 | 61 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 62 | 63 | ### Scope 64 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 65 | 66 | ### Enforcement 67 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at boeprox@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 68 | 69 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 70 | 71 | ### Attribution 72 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 73 | 74 | [homepage]: http://contributor-covenant.org 75 | [version]: http://contributor-covenant.org/version/1/4/ 76 | [Github issues list]:https://github.com/proxb/PoshRSJob/issues 77 | -------------------------------------------------------------------------------- /Images/GetRSJob-ReceiveRSJob.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proxb/PoshRSJob/83ce4ae11c171442f5d141bae1fc413757b15ef0/Images/GetRSJob-ReceiveRSJob.gif -------------------------------------------------------------------------------- /Images/NanoPoshRSJob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proxb/PoshRSJob/83ce4ae11c171442f5d141bae1fc413757b15ef0/Images/NanoPoshRSJob.png -------------------------------------------------------------------------------- /Images/PoshRSJob.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proxb/PoshRSJob/83ce4ae11c171442f5d141bae1fc413757b15ef0/Images/PoshRSJob.gif -------------------------------------------------------------------------------- /Images/RSJobStreamingExample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proxb/PoshRSJob/83ce4ae11c171442f5d141bae1fc413757b15ef0/Images/RSJobStreamingExample.gif -------------------------------------------------------------------------------- /Images/RSjobExample1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proxb/PoshRSJob/83ce4ae11c171442f5d141bae1fc413757b15ef0/Images/RSjobExample1.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Boe Prox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PoshRSJob/PoshRSJob.psd1: -------------------------------------------------------------------------------- 1 |  2 | # 3 | # PoshRSJob 4 | # Version 1.7.4.4 5 | # 6 | # Boe Prox (c) 2014 7 | # http://learn-powershell.net 8 | # 9 | ################################# 10 | 11 | @{ 12 | 13 | # Script module or binary module file associated with this manifest 14 | ModuleToProcess = 'PoshRSJob.psm1' 15 | 16 | # Version number of this module. 17 | ModuleVersion = '1.7.4.4' 18 | 19 | # ID used to uniquely identify this module 20 | GUID = '9b17fb0f-e939-4a5c-b194-3f2247452972' 21 | 22 | # Author of this module 23 | Author = 'Boe Prox' 24 | 25 | # Company or vendor of this module 26 | CompanyName = 'NA' 27 | 28 | # Copyright statement for this module 29 | Copyright = '(c) 2014 Boe Prox. All rights reserved.' 30 | 31 | # Description of the functionality provided by this module 32 | Description = 'Module designed to use PowerShell runspaces to create jobs that allow throttling and quicker execution of commands' 33 | 34 | # Minimum version of the Windows PowerShell engine required by this module 35 | #PowerShellVersion = '' 36 | 37 | # Name of the Windows PowerShell host required by this module 38 | #PowerShellHostName = '' 39 | 40 | # Minimum version of the Windows PowerShell host required by this module 41 | #PowerShellHostVersion = '' 42 | 43 | # Minimum version of the .NET Framework required by this module 44 | #DotNetFrameworkVersion = '' 45 | 46 | # Minimum version of the common language runtime (CLR) required by this module 47 | #CLRVersion = '' 48 | 49 | # Processor architecture (None, X86, Amd64, IA64) required by this module 50 | #ProcessorArchitecture = '' 51 | 52 | # Modules that must be imported into the global environment prior to importing this module 53 | #RequiredModules = @() 54 | 55 | # Assemblies that must be loaded prior to importing this module 56 | #RequiredAssemblies = @() 57 | 58 | # Script files (.ps1) that are run in the caller's environment prior to importing this module 59 | ScriptsToProcess = @('Scripts\TabExpansion.ps1') 60 | 61 | # Type files (.ps1xml) to be loaded when importing this module 62 | #TypesToProcess = 'TypeData\PoshRSJob.Types.ps1xml' 63 | 64 | # Format files (.ps1xml) to be loaded when importing this module 65 | #FormatsToProcess = 'TypeData\PoshRSJob.Format.ps1xml' 66 | 67 | # Modules to import as nested modules of the module specified in ModuleToProcess 68 | #NestedModules = @() 69 | 70 | # Functions to export from this module 71 | FunctionsToExport = 'Get-RSJob','Receive-RSJob','Remove-RSJob', 72 | 'Start-RSJob','Stop-RSJob','Wait-RSJob' 73 | 74 | # Cmdlets to export from this module 75 | #CmdletsToExport = '*' 76 | 77 | # Variables to export from this module 78 | VariablesToExport = 'PoshRS_Jobs','PoshRS_JobCleanup','PoshRS_JobID','PoshRS_RunspacePools','PoshRS_RunspacePoolCleanup' 79 | 80 | # Aliases to export from this module 81 | AliasesToExport = 'gsj','rmsj','rsj','spsj','ssj','wsj' 82 | 83 | # List of all modules packaged with this module 84 | #ModuleList = @() 85 | 86 | # List of all files packaged with this module 87 | FileList = 'PoshRSJob.psd1', 'PoshRSJob.psm1', 'en-US\about_PoshRSJob.help.txt', 'Private\ConvertScript.ps1', 'Private\ConvertScriptBlockV2.ps1', 88 | 'Private\FindFunction.ps1', 'Private\GetFunctionByFile.ps1', 'Private\GetFunctionDefinitionByFunction.ps1', 'Private\GetParamVariable.ps1', 'Private\GetUsingVariables.ps1', 'Private\GetUsingVariablesV2.ps1', 89 | 'Private\Increment.ps1', 'Private\RegisterScriptScopeFunction.ps1', 'Public\Get-RSJob.ps1', 'Public\Receive-RSJob.ps1', 90 | 'Public\Remove-RSJob.ps1', 'Public\Start-RSJob.ps1', 'Public\Stop-RSJob.ps1', 'Public\Wait-RSJob.ps1', 'TypeData\PoshRSJob.Format.ps1xml', 'TypeData\PoshRSJob.Types.ps1xml', 91 | 'Private\SetIsReceived.ps1' 92 | 93 | # Private data to pass to the module specified in ModuleToProcess 94 | PrivateData = @{ 95 | PSData = @{ 96 | # The primary categorization of this module (from the TechNet Gallery tech tree). 97 | Category = "Multithreading" 98 | 99 | # Keyword tags to help users find this module via navigations and search. 100 | Tags = @('PoshRSJob', 'Runspace','RunspacePool', 'Linux', 'PowerShellCore', 'RSJob') 101 | 102 | # The web address of an icon which can be used in galleries to represent this module 103 | #IconUri = '' 104 | 105 | # The web address of this module's project or support homepage. 106 | ProjectUri = "https://github.com/proxb/PoshRSJob" 107 | 108 | # The web address of this module's license. Points to a page that's embeddable and linkable. 109 | LicenseUri = "https://opensource.org/licenses/MIT" 110 | 111 | # Release notes for this particular version of the module 112 | # ReleaseNotes = False 113 | 114 | # If true, the LicenseUrl points to an end-user license (not just a source license) which requires the user agreement before use. 115 | RequireLicenseAcceptance = "False" 116 | 117 | # Indicates this is a pre-release/testing version of the module. 118 | IsPrerelease = 'False' 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /PoshRSJob/PoshRSJob.psm1: -------------------------------------------------------------------------------- 1 | $ScriptPath = Split-Path $MyInvocation.MyCommand.Path 2 | $PSModule = $ExecutionContext.SessionState.Module 3 | $PSModuleRoot = $PSModule.ModuleBase 4 | If ($PSVersionTable['PSEdition'] -and $PSVersionTable.PSEdition -eq 'Core') { 5 | #PowerShell V4 and below will throw a parser error even if I never use the classes keyword 6 | @' 7 | class V2UsingVariable { 8 | [string]$Name 9 | [string]$NewName 10 | [object]$Value 11 | [string]$NewVarName 12 | } 13 | 14 | class RSRunspacePool{ 15 | [System.Management.Automation.Runspaces.RunspacePool]$RunspacePool 16 | [System.Management.Automation.Runspaces.RunspacePoolState]$State 17 | [int]$AvailableJobs 18 | [int]$MaxJobs 19 | [DateTime]$LastActivity = [DateTime]::MinValue 20 | [String]$RunspacePoolID 21 | [bool]$CanDispose = $False 22 | } 23 | class RSJob { 24 | [string]$Name 25 | [int]$ID 26 | [System.Management.Automation.PSInvocationState]$State 27 | [object]$InputObject 28 | [string]$InstanceID 29 | [object]$Handle 30 | [object]$Runspace 31 | [System.Management.Automation.PowerShell]$InnerJob 32 | [System.Threading.ManualResetEvent]$Finished 33 | [string]$Command 34 | [System.Management.Automation.PSDataCollection[System.Management.Automation.ErrorRecord]]$Error 35 | [System.Management.Automation.PSDataCollection[System.Management.Automation.VerboseRecord]]$Verbose 36 | [System.Management.Automation.PSDataCollection[System.Management.Automation.DebugRecord]]$Debug 37 | [System.Management.Automation.PSDataCollection[System.Management.Automation.WarningRecord]]$Warning 38 | [System.Management.Automation.PSDataCollection[System.Management.Automation.ProgressRecord]]$Progress 39 | [bool]$HasMoreData = $True 40 | [bool]$HasErrors 41 | [object]$Output 42 | [string]$RunspacePoolID 43 | [bool]$Completed = $False 44 | [string]$Batch 45 | hidden [bool] $IsReceived = $False 46 | 47 | } 48 | '@ | Invoke-Expression 49 | } 50 | Else { 51 | Add-Type @" 52 | using System; 53 | using System.Collections.Generic; 54 | using System.Text; 55 | using System.Management.Automation; 56 | 57 | public class V2UsingVariable 58 | { 59 | public string Name; 60 | public string NewName; 61 | public object Value; 62 | public string NewVarName; 63 | } 64 | 65 | public class RSRunspacePool 66 | { 67 | public System.Management.Automation.Runspaces.RunspacePool RunspacePool; 68 | public System.Management.Automation.Runspaces.RunspacePoolState State; 69 | public int AvailableJobs; 70 | public int MaxJobs; 71 | public DateTime LastActivity = DateTime.MinValue; 72 | public string RunspacePoolID; 73 | public bool CanDispose = false; 74 | } 75 | public class RSJob 76 | { 77 | public string Name; 78 | public int ID; 79 | public System.Management.Automation.PSInvocationState State; 80 | public object InputObject; 81 | public string InstanceID; 82 | public object Handle; 83 | public object Runspace; 84 | public System.Management.Automation.PowerShell InnerJob; 85 | public System.Threading.ManualResetEvent Finished; 86 | public string Command; 87 | public System.Management.Automation.PSDataCollection Error; 88 | public System.Management.Automation.PSDataCollection Verbose; 89 | public System.Management.Automation.PSDataCollection Debug; 90 | public System.Management.Automation.PSDataCollection Warning; 91 | public System.Management.Automation.PSDataCollection Progress; 92 | public bool HasMoreData = true; 93 | public bool HasErrors; 94 | public object Output; 95 | public string RunspacePoolID; 96 | public bool Completed = false; 97 | public string Batch; 98 | #pragma warning disable 414 99 | private bool IsReceived = false; 100 | #pragma warning restore 414 101 | } 102 | "@ 103 | } 104 | 105 | #region RSJob Variables 106 | Write-Verbose "Creating RS collections" 107 | New-Variable PoshRS_Jobs -Value ([System.Collections.ArrayList]::Synchronized([System.Collections.ArrayList]@())) -Option ReadOnly -Scope Global -Force 108 | New-Variable PoshRS_jobCleanup -Value ([hashtable]::Synchronized(@{})) -Option ReadOnly -Scope Global -Force 109 | New-Variable PoshRS_JobID -Value ([int64]0) -Option ReadOnly -Scope Global -Force 110 | New-Variable PoshRS_RunspacePools -Value ([System.Collections.ArrayList]::Synchronized([System.Collections.ArrayList]@())) -Option ReadOnly -Scope Global -Force 111 | New-Variable PoshRS_RunspacePoolCleanup -Value ([hashtable]::Synchronized(@{})) -Option ReadOnly -Scope Global -Force 112 | #endregion RSJob Variables 113 | 114 | #region Cleanup Routine 115 | Write-Verbose "Creating routine to monitor RS jobs" 116 | $PoshRS_jobCleanup.Flag=$True 117 | $PoshRS_jobCleanup.Host = $Host 118 | $PSModulePath = $env:PSModulePath 119 | $PoshRS_jobCleanup.Runspace =[runspacefactory]::CreateRunspace() 120 | $PoshRS_jobCleanup.Runspace.Open() 121 | $PoshRS_jobCleanup.Runspace.SessionStateProxy.SetVariable("PoshRS_jobCleanup",$PoshRS_jobCleanup) 122 | $PoshRS_jobCleanup.Runspace.SessionStateProxy.SetVariable("PoshRS_Jobs",$PoshRS_Jobs) 123 | $PoshRS_jobCleanup.PowerShell = [PowerShell]::Create().AddScript({ 124 | #Routine to handle completed runspaces 125 | #$PoshRS_jobCleanup.Host.UI.WriteVerboseLine("Begin Do Loop") 126 | Do { 127 | [System.Threading.Monitor]::Enter($PoshRS_Jobs.syncroot) 128 | try { 129 | Foreach($job in $PoshRS_Jobs) { 130 | If ($job.Handle.isCompleted -AND (-NOT $Job.Completed)) { 131 | #$PoshRS_jobCleanup.Host.UI.WriteVerboseLine("$($Job.Id) completed") 132 | $Data = $null 133 | $CaughtErrors = $null 134 | Try { 135 | $Data = $job.InnerJob.EndInvoke($job.Handle) 136 | } Catch { 137 | $CaughtErrors = $Error 138 | #$PoshRS_jobCleanup.Host.UI.WriteVerboseLine("$($Job.Id) Caught terminating Error in job: $_") 139 | } 140 | #$PoshRS_jobCleanup.Host.UI.WriteVerboseLine("$($Job.Id) Checking for errors") 141 | If ($job.InnerJob.Streams.Error -OR $CaughtErrors) { 142 | #$PoshRS_jobCleanup.Host.UI.WriteVerboseLine("$($Job.Id) Errors Found!") 143 | $ErrorList = New-Object System.Management.Automation.PSDataCollection[System.Management.Automation.ErrorRecord] 144 | If ($job.InnerJob.Streams.Error) { 145 | ForEach ($Err in $job.InnerJob.Streams.Error) { 146 | #$PoshRS_jobCleanup.Host.UI.WriteVerboseLine("`t$($Job.Id) Adding Error") 147 | [void]$ErrorList.Add($Err) 148 | } 149 | } 150 | If ($CaughtErrors) { 151 | ForEach ($Err in $CaughtErrors) { 152 | #$PoshRS_jobCleanup.Host.UI.WriteVerboseLine("`t$($Job.Id) Adding Error") 153 | [void]$ErrorList.Add($Err) 154 | } 155 | } 156 | $job.Error = $ErrorList 157 | } 158 | #$PoshRS_jobCleanup.Host.UI.WriteVerboseLine("$($Job.Id) Disposing job") 159 | $job.InnerJob.dispose() 160 | #Return type from Invoke() is a generic collection; need to verify the first index is not NULL 161 | If ($Data -and ($Data.Count -gt 0) -AND (-NOT ($Data.Count -eq 1 -AND $Null -eq $Data[0]))) { 162 | $job.output = $Data 163 | $job.HasMoreData = $True 164 | } 165 | $Error.Clear() 166 | $job.Completed = $True 167 | } 168 | } 169 | } 170 | finally { 171 | [System.Threading.Monitor]::Exit($PoshRS_Jobs.syncroot) 172 | } 173 | Start-Sleep -Milliseconds 100 174 | } while ($PoshRS_jobCleanup.Flag) 175 | }) 176 | $PoshRS_jobCleanup.PowerShell.Runspace = $PoshRS_jobCleanup.Runspace 177 | $PoshRS_jobCleanup.Handle = $PoshRS_jobCleanup.PowerShell.BeginInvoke() 178 | 179 | Write-Verbose "Creating routine to monitor Runspace Pools" 180 | $PoshRS_RunspacePoolCleanup.Flag=$True 181 | $PoshRS_RunspacePoolCleanup.Host=$Host 182 | #2 minute timeout for unused runspace pools 183 | $PoshRS_RunspacePoolCleanup.Timeout = [timespan]::FromMinutes(2).Ticks 184 | $InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() 185 | 186 | #Create Type Collection so the object will work properly 187 | $Types = Get-ChildItem "$($PSScriptRoot)\TypeData" -Filter *Types* | Select-Object -ExpandProperty Fullname 188 | ForEach ($Type in $Types) { 189 | $TypeConfigEntry = New-Object System.Management.Automation.Runspaces.SessionStateTypeEntry -ArgumentList $Type 190 | $InitialSessionState.Types.Add($TypeConfigEntry) 191 | } 192 | $PoshRS_RunspacePoolCleanup.Runspace =[runspacefactory]::CreateRunspace($InitialSessionState) 193 | 194 | $PoshRS_RunspacePoolCleanup.Runspace.Open() 195 | $PoshRS_RunspacePoolCleanup.Runspace.SessionStateProxy.SetVariable("PoshRS_RunspacePoolCleanup",$PoshRS_RunspacePoolCleanup) 196 | $PoshRS_RunspacePoolCleanup.Runspace.SessionStateProxy.SetVariable("PoshRS_RunspacePools",$PoshRS_RunspacePools) 197 | $PoshRS_RunspacePoolCleanup.Runspace.SessionStateProxy.SetVariable("ParentHost",$Host) 198 | $PoshRS_RunspacePoolCleanup.PowerShell = [PowerShell]::Create().AddScript({ 199 | #Routine to handle completed runspaces 200 | $DisposePoshRS_RunspacePools=$False 201 | Do { 202 | #$ParentHost.ui.WriteVerboseLine("Beginning Do Statement") 203 | $DisposePoshRS_RunspacePools=$False 204 | If ($PoshRS_RunspacePools.Count -gt 0) { 205 | #$ParentHost.ui.WriteVerboseLine("$($PoshRS_RunspacePools | Out-String)") 206 | [System.Threading.Monitor]::Enter($PoshRS_RunspacePools.syncroot) 207 | try { 208 | Foreach($RunspacePool in $PoshRS_RunspacePools) { 209 | #$ParentHost.ui.WriteVerboseLine("RunspacePool <$($RunspacePool.RunspacePoolID)> | MaxJobs: $($RunspacePool.MaxJobs) | AvailJobs: $($RunspacePool.AvailableJobs)") 210 | If (($RunspacePool.AvailableJobs -eq $RunspacePool.MaxJobs) -AND $PoshRS_RunspacePools.LastActivity.Ticks -ne 0) { 211 | If ((Get-Date).Ticks - $RunspacePool.LastActivity.Ticks -gt $PoshRS_RunspacePoolCleanup.Timeout) { 212 | #Dispose of runspace pool 213 | $RunspacePool.RunspacePool.Dispose() 214 | $RunspacePool.CanDispose = $True 215 | $DisposePoshRS_RunspacePools=$True 216 | } 217 | } Else { 218 | $RunspacePool.LastActivity = (Get-Date) 219 | } 220 | } 221 | #Remove runspace pools 222 | If ($DisposePoshRS_RunspacePools) { 223 | $TempCollection = $PoshRS_RunspacePools.Clone() 224 | $TempCollection | Where-Object { 225 | $_.CanDispose 226 | } | ForEach-Object { 227 | #$ParentHost.ui.WriteVerboseLine("Removing runspacepool <$($_.RunspacePoolID)>") 228 | [void]$PoshRS_RunspacePools.Remove($_) 229 | } 230 | #Not setting this to silentlycontinue seems to cause another runspace to be created if an error occurs 231 | Remove-Variable TempCollection -ErrorAction SilentlyContinue 232 | #Perform garbage collection 233 | [gc]::Collect() 234 | } 235 | } 236 | finally { 237 | [System.Threading.Monitor]::Exit($PoshRS_RunspacePools.syncroot) 238 | } 239 | } 240 | #$ParentHost.ui.WriteVerboseLine("Sleeping") 241 | If ($DisposePoshRS_RunspacePools) { 242 | #Perform garbage collection 243 | [gc]::Collect() 244 | } 245 | Start-Sleep -Milliseconds 5000 246 | } while ($PoshRS_RunspacePoolCleanup.Flag) 247 | }) 248 | $PoshRS_RunspacePoolCleanup.PowerShell.Runspace = $PoshRS_RunspacePoolCleanup.Runspace 249 | $PoshRS_RunspacePoolCleanup.Handle = $PoshRS_RunspacePoolCleanup.PowerShell.BeginInvoke() 250 | #endregion Cleanup Routine 251 | 252 | #region Load Public Functions 253 | Try { 254 | Get-ChildItem "$ScriptPath\Public" -Filter *.ps1 | Select-Object -ExpandProperty FullName | ForEach-Object { 255 | $Function = Split-Path $_ -Leaf 256 | . $_ 257 | } 258 | } Catch { 259 | Write-Warning ("{0}: {1}" -f $Function,$_.Exception.Message) 260 | Continue 261 | } 262 | #endregion Load Public Functions 263 | 264 | #region Load Private Functions 265 | Try { 266 | Get-ChildItem "$ScriptPath\Private" -Filter *.ps1 | Select-Object -ExpandProperty FullName | ForEach-Object { 267 | $Function = Split-Path $_ -Leaf 268 | . $_ 269 | } 270 | } Catch { 271 | Write-Warning ("{0}: {1}" -f $Function,$_.Exception.Message) 272 | Continue 273 | } 274 | #endregion Load Private Functions 275 | 276 | #region Format and Type Data 277 | Try { 278 | Update-FormatData "$ScriptPath\TypeData\PoshRSJob.Format.ps1xml" -ErrorAction Stop 279 | } 280 | Catch {} 281 | Try { 282 | Update-TypeData "$ScriptPath\TypeData\PoshRSJob.Types.ps1xml" -ErrorAction Stop 283 | } 284 | Catch {} 285 | #endregion Format and Type Data 286 | 287 | #region Aliases 288 | New-Alias -Name ssj -Value Start-RSJob -Force 289 | New-Alias -Name gsj -Value Get-RSJob -Force 290 | New-Alias -Name rsj -Value Receive-RSJob -Force 291 | New-Alias -Name rmsj -Value Remove-RSJob -Force 292 | New-Alias -Name spsj -Value Stop-RSJob -Force 293 | New-Alias -Name wsj -Value Wait-RSJob -Force 294 | #endregion Aliases 295 | 296 | #region Handle Module Removal 297 | $PoshRS_OnRemoveScript = { 298 | $PoshRS_jobCleanup.Flag=$False 299 | $PoshRS_RunspacePoolCleanup.Flag=$False 300 | #Let sit for a second to make sure it has had time to stop 301 | Start-Sleep -Seconds 1 302 | $PoshRS_jobCleanup.PowerShell.EndInvoke($PoshRS_jobCleanup.Handle) 303 | $PoshRS_jobCleanup.PowerShell.Dispose() 304 | $PoshRS_RunspacePoolCleanup.PowerShell.EndInvoke($PoshRS_RunspacePoolCleanup.Handle) 305 | $PoshRS_RunspacePoolCleanup.PowerShell.Dispose() 306 | Remove-Variable PoshRS_JobId -Scope Global -Force 307 | Remove-Variable PoshRS_Jobs -Scope Global -Force 308 | Remove-Variable PoshRS_jobCleanup -Scope Global -Force 309 | Remove-Variable PoshRS_RunspacePoolCleanup -Scope Global -Force 310 | Remove-Variable PoshRS_RunspacePools -Scope Global -Force 311 | } 312 | $ExecutionContext.SessionState.Module.OnRemove += $PoshRS_OnRemoveScript 313 | Register-EngineEvent -SourceIdentifier ([System.Management.Automation.PsEngineEvent]::Exiting) -Action $PoshRS_OnRemoveScript 314 | #endregion Handle Module Removal 315 | 316 | #region Export Module Members 317 | $ExportModule = @{ 318 | Alias = @('gsj','rmsj','rsj','spsj','ssj','wsj') 319 | Function = @('Get-RSJob','Receive-RSJob','Remove-RSJob','Start-RSJob','Stop-RSJob','Wait-RSJob') 320 | Variable = @('PoshRS_JobId','PoshRS_Jobs','PoshRS_jobCleanup','PoshRS_RunspacePoolCleanup','PoshRS_RunspacePools') 321 | } 322 | Export-ModuleMember @ExportModule 323 | #endregion Export Module Members 324 | 325 | $env:PSModulePath = $PSModulePath 326 | -------------------------------------------------------------------------------- /PoshRSJob/Private/ConvertScript.ps1: -------------------------------------------------------------------------------- 1 | Function ConvertScript { 2 | Param ( 3 | [scriptblock]$ScriptBlock, 4 | [bool]$HasParam, 5 | $UsingVariables, 6 | $UsingVariableValues, 7 | [bool]$InsertPSItem = $false 8 | ) 9 | # $HasParam unused 10 | $List = New-Object 'System.Collections.Generic.List`1[System.Management.Automation.Language.VariableExpressionAst]' 11 | $Params = New-Object System.Collections.ArrayList 12 | If ($InsertPSItem) { 13 | [void]$Params.Add('$_') 14 | } 15 | If ($UsingVariables) { 16 | ForEach ($Ast in $UsingVariables) { 17 | [void]$list.Add($Ast.SubExpression) 18 | } 19 | } 20 | if ($UsingVariableValues) { 21 | [void]$Params.AddRange(@($UsingVariableValues.NewName)) 22 | } 23 | $NewParams = $Params -join ', ' 24 | $Tuple=[Tuple]::Create($list,$NewParams) 25 | $bindingFlags = [Reflection.BindingFlags]"Default,NonPublic,Instance" 26 | 27 | $GetWithInputHandlingForInvokeCommandImpl = ($ScriptBlock.ast.gettype().GetMethod('GetWithInputHandlingForInvokeCommandImpl',$bindingFlags)) 28 | $StringScriptBlock = $GetWithInputHandlingForInvokeCommandImpl.Invoke($ScriptBlock.ast,@($Tuple)) 29 | If ([scriptblock]::Create($StringScriptBlock).ast.endblock[0].statements.extent.text.startswith('$input |')) { 30 | $StringScriptBlock = $StringScriptBlock -replace '\$Input \|' 31 | } 32 | If (-NOT $ScriptBlock.Ast.ParamBlock) { 33 | $StringScriptBlock = "Param($($NewParams))`n$($StringScriptBlock)" 34 | [scriptblock]::Create($StringScriptBlock) 35 | } Else { 36 | [scriptblock]::Create($StringScriptBlock) 37 | } 38 | } -------------------------------------------------------------------------------- /PoshRSJob/Private/ConvertScriptBlockV2.ps1: -------------------------------------------------------------------------------- 1 | Function ConvertScriptBlockV2 { 2 | Param ( 3 | [scriptblock]$ScriptBlock, 4 | [bool]$HasParam, 5 | $UsingVariables, 6 | $UsingVariableValues, 7 | [bool]$InsertPSItem = $false 8 | ) 9 | # $UsingVariables unused 10 | $errors = [System.Management.Automation.PSParseError[]] @() 11 | $Tokens = [Management.Automation.PsParser]::Tokenize($ScriptBlock.tostring(), [ref] $errors) 12 | $StringBuilder = New-Object System.Text.StringBuilder 13 | $UsingHash = @{} 14 | $UsingVariableValues | ForEach-Object { 15 | $UsingHash["Using:$($_.Name)"] = $_.NewVarName 16 | } 17 | $Params = New-Object System.Collections.ArrayList 18 | If ($InsertPSItem) { 19 | [void]$Params.Add('$_') 20 | } 21 | If ($UsingVariableValues) { 22 | [void]$Params.AddRange(@($UsingVariableValues | Select-Object -ExpandProperty NewName)) 23 | } 24 | $NewParams = $Params -join ', ' 25 | If (-Not $HasParam) { 26 | [void]$StringBuilder.Append("Param($($NewParams))") 27 | } 28 | For ($i=0;$i -lt $Tokens.count; $i++){ 29 | #Write-Verbose "Type: $($Tokens[$i].Type)" 30 | #Write-Verbose "Previous Line: $($Previous.StartLine) -- Current Line: $($Tokens[$i].StartLine)" 31 | If ($Previous.StartLine -eq $Tokens[$i].StartLine) { 32 | $Space = " " * [int]($Tokens[$i].StartColumn - $Previous.EndColumn) 33 | [void]$StringBuilder.Append($Space) 34 | } 35 | Switch ($Tokens[$i].Type) { 36 | 'NewLine' {[void]$StringBuilder.Append("`n")} 37 | 'Variable' { 38 | If ($UsingHash[$Tokens[$i].Content]) { 39 | [void]$StringBuilder.Append(("`${0}" -f $UsingHash[$Tokens[$i].Content])) 40 | } Else { 41 | [void]$StringBuilder.Append(("`${0}" -f $Tokens[$i].Content)) 42 | } 43 | } 44 | 'String' { 45 | $qchar = $ScriptBlock.ToString().Split("`n")[($Tokens[$i].StartLine-1)].Substring($Tokens[$i].StartColumn-1,1) 46 | [void]$StringBuilder.Append(("{0}{1}{0}" -f $qchar,$Tokens[$i].Content)) 47 | } 48 | 'GroupStart' { 49 | $Script:GroupStart++ 50 | If ($Script:AddUsing -AND $Script:GroupStart -eq 1) { 51 | $Script:AddUsing = $False 52 | [void]$StringBuilder.Append($Tokens[$i].Content) 53 | If ($HasParam) { 54 | [void]$StringBuilder.Append("$($NewParams),") 55 | } 56 | } Else { 57 | [void]$StringBuilder.Append($Tokens[$i].Content) 58 | } 59 | } 60 | 'GroupEnd' { 61 | $Script:GroupStart-- 62 | If ($Script:GroupStart -eq 0) { 63 | $Script:Param = $False 64 | [void]$StringBuilder.Append($Tokens[$i].Content) 65 | } Else { 66 | [void]$StringBuilder.Append($Tokens[$i].Content) 67 | } 68 | } 69 | 'KeyWord' { 70 | If ($Tokens[$i].Content -eq 'Param') { 71 | $Script:Param = $True 72 | $Script:AddUsing = $True 73 | $Script:GroupStart=0 74 | [void]$StringBuilder.Append($Tokens[$i].Content) 75 | } Else { 76 | [void]$StringBuilder.Append($Tokens[$i].Content) 77 | } 78 | } 79 | 'Type' { 80 | [void]$StringBuilder.Append('[{0}]' -f $Tokens[$i].Content) 81 | } 82 | Default { 83 | [void]$StringBuilder.Append($Tokens[$i].Content) 84 | } 85 | } 86 | $Previous = $Tokens[$i] 87 | } 88 | #$StringBuilder.ToString() 89 | [scriptblock]::Create($StringBuilder.ToString()) 90 | } 91 | -------------------------------------------------------------------------------- /PoshRSJob/Private/FindFunction.ps1: -------------------------------------------------------------------------------- 1 | #Helper function 2 | Function FindFunction { 3 | [CmdletBinding()] 4 | param ( 5 | [string]$ScriptBlock 6 | ) 7 | #Just in case we have some oddness going on 8 | $ScriptBlock = $ScriptBlock -replace '`','``' 9 | # Tokenize the script 10 | $tokens = [Management.Automation.PSParser]::Tokenize($ScriptBlock, [ref]$null) 11 | 12 | # First Pass - Grab all tokens between the first param block. 13 | $functionsearch = $false 14 | $IsName=$False 15 | $Counter = 0 16 | $SpaceCount = 0 17 | for ($i = 0; $i -lt $tokens.Count; $i++) { 18 | if (!$functionsearch) { 19 | if ($tokens[$i].Content -eq "function" -AND $tokens[$i].Type -eq 'Keyword') { 20 | $functionsearch = $true 21 | $IsName=$False 22 | $Definition = New-Object System.Text.StringBuilder 23 | $i++ 24 | } 25 | } 26 | if ($functionsearch) { 27 | If ($i -gt 1 -AND ($tokens[$i].StartLine -eq $tokens[$i-1].EndLine)) { 28 | $SpaceCount = $tokens[$i].StartColumn - $tokens[$i-1].EndColumn 29 | $space = ' '*"$($SpaceCount)" 30 | If ($SpaceCount -gt 0) { 31 | If ($SpaceCount -notmatch '^[5|9]$') { 32 | Write-Verbose "Adding Space: $($SpaceCount)" 33 | [void]$Definition.Append($Space) 34 | } ElseIf ($SpaceCount -match '^[5|9]$') { 35 | Write-Verbose "Adding NewLine" 36 | [void]$Definition.Append("`n") 37 | } 38 | } 39 | } 40 | Write-Verbose $tokens[$i].Content 41 | Switch ($tokens[$i].Type) { 42 | 'NewLine' { 43 | Write-Verbose 'Adding NewLine' 44 | [void]$Definition.Append("`n") 45 | } 46 | 'CommandArgument' { 47 | If (-NOT $IsName) { 48 | $Name = $tokens[$i].Content 49 | $IsName = $True 50 | $ExpectingStart=$True 51 | } Else { 52 | [void]$Definition.Append($tokens[$i].Content) 53 | } 54 | } 55 | 'GroupStart' { 56 | If ($tokens[$i].Content -eq '{') { 57 | $Counter++ 58 | If ($ExpectingStart) { 59 | $ExpectingStart = $False 60 | } 61 | } 62 | [void]$Definition.Append($tokens[$i].Content) 63 | } 64 | 'GroupEnd' { 65 | If ($tokens[$i].Content -eq '}') { 66 | $Counter-- 67 | If ($ExpectingStart) { 68 | $ExpectingStart = $False 69 | } 70 | } 71 | [void]$Definition.Append($tokens[$i].Content) 72 | } 73 | 'Variable' { 74 | [void]$Definition.Append("`$$($tokens[$i].Content)") 75 | } 76 | 'Type' { 77 | Switch ($PSVersionTable.PSVersion.Major) { 78 | '2' { 79 | [void]$Definition.Append("[$($tokens[$i].Content)]") 80 | } 81 | Default { 82 | [void]$Definition.Append($($tokens[$i].Content)) 83 | } 84 | } 85 | } 86 | Default { 87 | [void]$Definition.Append($tokens[$i].Content) 88 | } 89 | } 90 | if ($Counter -eq 0 -AND -NOT $ExpectingStart) { 91 | $functionsearch = $false 92 | #Create the object and display it 93 | New-Object PSObject -Property @{ 94 | Name = $Name 95 | Body = $Definition.ToString() 96 | } 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /PoshRSJob/Private/GetFunctionByFile.ps1: -------------------------------------------------------------------------------- 1 | Function GetFunctionByFile { 2 | [CmdletBinding()] 3 | param ( 4 | [string[]]$FilePath 5 | ) 6 | 7 | $psMajorVersion = $PSVersionTable.PSVersion.Major 8 | $functionsInFile = @() 9 | ForEach ($thisFilePath in $FilePath) { 10 | Write-Verbose "Working on file : $thisFilePath" 11 | 12 | if (-not (Test-Path $thisFilePath)) { 13 | Write-Warning "Cannot find file : $thisFilePath" 14 | continue 15 | } 16 | 17 | try { 18 | Switch ($psMajorVersion) { 19 | '2' { 20 | $scriptBlockInFile = [ScriptBlock]::Create($(Get-Content $thisFilePath) -join [Environment]::NewLine) 21 | $functionsInFile += @(FindFunction -ScriptBlock $scriptBlockInFile) 22 | } 23 | Default { 24 | $AST = [System.Management.Automation.Language.Parser]::ParseFile($thisFilePath, [ref]$null, [ref]$null) 25 | $functionsInFile += $AST.FindAll( {$args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst]} , $true) 26 | } 27 | } 28 | Write-Verbose "Functions found in file : $($functionsInFile.Name -join '; ')" 29 | } 30 | catch { 31 | Write-Warning "$thisFilePath : $($_.Exception.Message)" 32 | } 33 | } 34 | $functionsInFile 35 | } 36 | -------------------------------------------------------------------------------- /PoshRSJob/Private/GetFunctionDefinitionByFunction.ps1: -------------------------------------------------------------------------------- 1 | Function GetFunctionDefinitionByFunction { 2 | [CmdletBinding()] 3 | param ( 4 | [parameter(ValueFromPipeline = $True)] 5 | $FunctionItem 6 | ) 7 | 8 | if ($FunctionItem -is [PSCustomObject]) { 9 | # In case of Powershell v2 10 | $function.Body.Trim().Trim("{}") 11 | } 12 | else { 13 | # In case of Powershell v3+ 14 | $function.Body.Extent.Text.Trim("{}") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PoshRSJob/Private/GetParamVariable.ps1: -------------------------------------------------------------------------------- 1 | Function GetParamVariable { 2 | [CmdletBinding()] 3 | param ( 4 | [scriptblock]$ScriptBlock 5 | ) 6 | # Tokenize the script 7 | [array] $tokens = [Management.Automation.PSParser]::Tokenize($ScriptBlock, [ref]$null) | Where-Object { 8 | $_.Type -ne 'NewLine' -and $_.Type -ne 'Comment' 9 | } 10 | # old code was buggy - it can grab internal param block for code like { $a = 1; invoke-command { param($b) } -argumentlist $a } 11 | # New code know that scriptblock param() can only be the first token or right after [attribute] tokens, any other - ignored. 12 | # It also get right variable names when param($a = $b + $c, $d) and other difficult cases (see tests) 13 | $state = 0 14 | $bracket = 0 15 | $awaitVariable = $false 16 | foreach ($token in $tokens) 17 | { 18 | # using state machine method 19 | switch ($state) { 20 | 0 { # search for sttribute start or param 21 | if ($token.Type -eq 'Keyword' -and $token.Content -eq 'param') { 22 | $state = 3 # collect variables start 23 | $awaitVariable = $true # catch variable name after param( 24 | } 25 | elseif ($token.Type -eq 'Operator' -and $token.Content -eq '[') { #attribute start 26 | $state = 1 # check for attribute token 27 | $bracket++ 28 | } 29 | else { # no param found, break 30 | $state = -1 31 | } 32 | } 33 | 1 { # Attribute token check. may be excessive? 34 | if ($token.Type -eq 'Attribute') { 35 | $state = 2 # wait for close attribute block 36 | } 37 | } 38 | 2 { # await attribte end 39 | if ($token.Type -eq 'Operator') { 40 | if ($token.Content -eq '[') { 41 | $bracket++ 42 | } 43 | elseif ($token.Content -eq ']') { 44 | $bracket-- 45 | if ($bracket -eq 0) { 46 | # catched attribute close bracket 47 | $state = 0 # back to param() search 48 | } 49 | } 50 | } 51 | } 52 | 3 { # inside params 53 | if ($token.Type -eq 'GroupStart' -and $token.Content -eq '(') { 54 | $bracket++ 55 | } 56 | elseif ($token.Type -eq 'GroupEnd' -and $token.Content -eq ')') { 57 | $bracket-- 58 | if ($bracket -eq 0) { 59 | # param() closed, exiting 60 | $state = -1 61 | } 62 | } 63 | elseif ($token.Type -eq 'Operator' -and $token.Content -eq '[') { 64 | $bracket += 2 #count square brackets 65 | } 66 | elseif ($token.Type -eq 'Operator' -and $token.Content -eq ']') { 67 | $bracket -= 2 #count square brackets 68 | } 69 | elseif ($token.Type -eq 'GroupStart' -and ($token.Content -eq '{' -or $token.Content -eq '@{')) { 70 | $bracket += 2 #count curly brackets 71 | } 72 | elseif ($token.Type -eq 'GroupEnd' -and $token.Content -eq '}') { 73 | $bracket -= 2 #count curly brackets 74 | } 75 | elseif ($token.Type -eq 'Operator' -and $token.Content -eq ',' -and ($bracket -eq 1)) { 76 | $awaitVariable = $true # await variable name after comma without extra brackets 77 | } 78 | elseif ($token.Type -eq 'Variable' -and ($bracket -eq 1) -and $awaitVariable) 79 | { 80 | $awaitVariable = $false 81 | $token.Content 82 | } 83 | } 84 | } 85 | if ($state -eq -1) { break } 86 | } 87 | } -------------------------------------------------------------------------------- /PoshRSJob/Private/GetUsingVariables.ps1: -------------------------------------------------------------------------------- 1 | Function GetUsingVariables { 2 | Param ([scriptblock]$ScriptBlock) 3 | $ScriptBlock.ast.FindAll( {$args[0] -is [System.Management.Automation.Language.UsingExpressionAst]}, $True) 4 | } -------------------------------------------------------------------------------- /PoshRSJob/Private/GetUsingVariablesV2.ps1: -------------------------------------------------------------------------------- 1 | Function GetUsingVariablesV2 { 2 | Param ([scriptblock]$ScriptBlock) 3 | $errors = [System.Management.Automation.PSParseError[]] @() 4 | $Results = [Management.Automation.PsParser]::Tokenize($ScriptBlock.tostring(), [ref] $errors) 5 | $Results | Where-Object { 6 | $_.Content -match '^Using:' -AND $_.Type -eq 'Variable' 7 | } 8 | } -------------------------------------------------------------------------------- /PoshRSJob/Private/Increment.ps1: -------------------------------------------------------------------------------- 1 | Function Increment { 2 | Write-Verbose "Incrementing job ID" 3 | Set-Variable -Name PoshRS_JobId -Value ($PoshRS_JobId + 1) -Force -Scope Global 4 | Write-Output $PoshRS_JobId 5 | } -------------------------------------------------------------------------------- /PoshRSJob/Private/RegisterScriptScopeFunction.ps1: -------------------------------------------------------------------------------- 1 | Function RegisterScriptScopeFunction { 2 | [cmdletbinding()] 3 | Param ( 4 | [parameter()] 5 | [string[]]$Name 6 | ) 7 | $FoundName = @() 8 | Write-Verbose "Getting callstacks" 9 | $PSCallStack = Get-PSCallStack 10 | Write-Verbose "PSCallStacks: `n$($PSCallStack|Out-String)" 11 | If ($PSCallStack.count -gt 1) { 12 | foreach ($CallStack in ($PSCallStack | Select-Object -Skip 2)) { 13 | Switch ($PSVersionTable.PSVersion.Major) { 14 | '2' { 15 | $ScriptBlock = Get-Content $CallStack.ScriptName 16 | $Functions = @(FindFunction -ScriptBlock ($ScriptBlock | Out-String)) 17 | } 18 | Default { 19 | $Flags = [System.Reflection.BindingFlags]'nonpublic,instance,static' 20 | $FunctionContext = $CallStack.GetType().GetProperty('FunctionContext',$Flags).GetValue($CallStack,$Null) 21 | $ScriptBlock = $FunctionContext.GetType().GetField('_scriptBlock',$Flags).GetValue($FunctionContext) 22 | $Functions = @(($ScriptBlock.ast.FindAll({$args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst]},$False))) 23 | } 24 | } 25 | Write-Verbose ("Found {0} functions" -f $Functions.count) 26 | Write-Verbose "Functions found in callstack $Callstack`n$($Functions | Select-Object -ExpandProperty Name|Out-String)" 27 | if ($Functions) { 28 | $Functions | ForEach-Object { 29 | If ($PSBoundParameters.ContainsKey('Name')) { 30 | If ($Name -contains $_.Name -and $FoundName -notcontains $_.Name) { 31 | Write-Verbose "Loading $($_.Name)" 32 | $FoundName += $_.Name 33 | .([scriptblock]::Create("Function Script:$($_.Name) $($_.Body)")) 34 | } 35 | } Else { 36 | if ($FoundName -notcontains $_.Name) { 37 | Write-Verbose "Loading $($_.Name)" 38 | $FoundName += $_.Name 39 | .([scriptblock]::Create("Function Script:$($_.Name) $($_.Body)")) 40 | } 41 | } 42 | } 43 | 44 | # Stop searching callstacks once we found what we want 45 | if ($Name.Count -eq $FoundName.Count) { 46 | break 47 | } 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /PoshRSJob/Private/SetIsReceived.ps1: -------------------------------------------------------------------------------- 1 | Function SetIsReceived { 2 | Param ( 3 | [parameter(ValueFromPipeline=$True)] 4 | [rsjob]$RSJob, 5 | [switch]$SetTrue 6 | ) 7 | Begin{ 8 | $Flags = 'nonpublic','instance','static' 9 | } 10 | Process { 11 | If ($PSVersionTable['PSEdition'] -and $PSVersionTable.PSEdition -eq 'Core') { 12 | $RSJob.IsReceived = $SetTrue.ToBool() 13 | } 14 | Else { 15 | $Field = $RSJob.gettype().GetField('IsReceived',$Flags) 16 | $Field.SetValue($RSJob,$SetTrue.ToBool()) 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /PoshRSJob/Private/WriteStream.ps1: -------------------------------------------------------------------------------- 1 | Function WriteStream { 2 | [CmdletBinding()] 3 | Param ( 4 | [Parameter(ValueFromPipeline=$true)] 5 | [Object]$IndividualJob 6 | ) 7 | Begin { 8 | $Streams = "Verbose","Warning","Error","Output","Debug" 9 | } 10 | 11 | Process { 12 | ForEach ($Stream in $Streams) 13 | { 14 | If (($IndividualJob.$Stream)) 15 | { 16 | Switch ($Stream) { 17 | "Verbose" { $IndividualJob | Select-Object -ExpandProperty Verbose| Where-Object { $_ } | ForEach-Object { $host.ui.WriteVerboseLine($_)} } 18 | "Debug" { $IndividualJob | Select-Object -ExpandProperty Debug| Where-Object { $_ } | ForEach-Object { $host.ui.WriteDebugLine($_)} } 19 | "Warning" { $IndividualJob | Select-Object -ExpandProperty Warning| Where-Object { $_ } | ForEach-Object { $host.ui.WriteWarningLine($_) } } 20 | "Error" { $IndividualJob | Select-Object -ExpandProperty Error | ForEach-Object {$host.ui.WriteErrorLine($_)} } 21 | "Output" { $IndividualJob | Where-Object { $_ } | Select-Object -ExpandProperty Output } 22 | } 23 | } 24 | 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /PoshRSJob/Public/Get-RSJob.ps1: -------------------------------------------------------------------------------- 1 | Function Get-RSJob { 2 | <# 3 | .SYNOPSIS 4 | Gets runspace jobs that are currently available in the session. 5 | 6 | .DESCRIPTION 7 | Get-RSJob will display all jobs that are currently available to include completed and currently running jobs. 8 | If no parameters are given, all jobs are displayed to view. 9 | 10 | .PARAMETER Job 11 | Represents the RSJob object being sent to query for. 12 | 13 | .PARAMETER Name 14 | The name of the jobs to query for. 15 | 16 | .PARAMETER ID 17 | The ID of the jobs to query for. 18 | 19 | .PARAMETER InstanceID 20 | The GUID of the jobs to query for. 21 | 22 | .PARAMETER Batch 23 | Name of the set of jobs to query for. 24 | 25 | .PARAMETER State 26 | The State of the job that you want to display. Accepted values are: 27 | 28 | NotStarted 29 | Running 30 | Completed 31 | Failed 32 | Stopping 33 | Stopped 34 | Disconnected 35 | 36 | .PARAMETER HasMoreData 37 | Displays jobs that have data being outputted. You can specify -HasMoreData:$False to display jobs 38 | that have no data to output. 39 | 40 | .NOTES 41 | Name: Get-RSJob 42 | Author: Boe Prox/Max Kozlov 43 | 44 | .EXAMPLE 45 | Get-RSJob -State Completed 46 | 47 | Description 48 | ----------- 49 | Displays a list of jobs which have completed. 50 | 51 | .EXAMPLE 52 | Get-RSJob -ID 1,5,78 53 | 54 | Id Name State HasMoreData HasErrors Command 55 | -- ---- ----- ----------- --------- ------- 56 | 1 Test_1 Completed True False ... 57 | 5 Test_5 Completed True False ... 58 | 78 Test_78 Completed True False ... 59 | 60 | Description 61 | ----------- 62 | Displays list of jobs with IDs 1,5,78. 63 | #> 64 | [OutputType('RSJob')] 65 | [cmdletbinding( 66 | DefaultParameterSetName='All' 67 | )] 68 | Param ( 69 | [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True, 70 | ParameterSetName='Job', Position=0)] 71 | [Alias('InputObject')] 72 | [RSJob[]]$Job, 73 | [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True, 74 | ParameterSetName='Name', Position=0)] 75 | [string[]]$Name, 76 | [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True, 77 | ParameterSetName='Id', Position=0)] 78 | [int[]]$Id, 79 | [parameter(ValueFromPipelineByPropertyName=$True, 80 | ParameterSetName='InstanceID')] 81 | [string[]]$InstanceID, 82 | [parameter(ValueFromPipelineByPropertyName=$True, 83 | ParameterSetName='Batch')] 84 | [string[]]$Batch, 85 | 86 | [parameter(ParameterSetName='Batch')] 87 | [parameter(ParameterSetName='Name')] 88 | [parameter(ParameterSetName='Id')] 89 | [parameter(ParameterSetName='InstanceID')] 90 | [parameter(ParameterSetName='All')] 91 | [ValidateSet('NotStarted','Running','Completed','Failed','Stopping','Stopped','Disconnected')] 92 | [string[]]$State, 93 | [parameter(ParameterSetName='Batch')] 94 | [parameter(ParameterSetName='Name')] 95 | [parameter(ParameterSetName='Id')] 96 | [parameter(ParameterSetName='InstanceID')] 97 | [parameter(ParameterSetName='All')] 98 | [Switch]$HasMoreData 99 | ) 100 | Begin { 101 | If ($PSBoundParameters['Debug']) { 102 | $DebugPreference = 'Continue' 103 | } 104 | Write-Debug "ParameterSet: $($PSCmdlet.parametersetname)" 105 | $Hash = @{} 106 | $ResultJobs = New-Object System.Collections.ArrayList 107 | } 108 | Process { 109 | Write-Debug "ParameterSet: $($PSCmdlet.ParameterSetName)" 110 | $Property = $PSCmdlet.ParameterSetName 111 | 112 | if ($PSCmdlet.ParameterSetName -eq 'Job') { 113 | Write-Verbose "Adding Job $($PSBoundParameters[$Property].Id)" 114 | foreach ($v in $PSBoundParameters[$Property]) { 115 | $Hash.Add($v.ID,1) 116 | } 117 | } 118 | elseif ($PSCmdlet.ParameterSetName -ne 'All') { 119 | Write-Verbose "Adding $($PSBoundParameters[$Property])" 120 | foreach ($v in $PSBoundParameters[$Property]) { 121 | $Hash.Add($v,1) 122 | } 123 | } 124 | } 125 | End { 126 | #Job objects searched by ID 127 | if ($Property -eq 'Job') { $Property = 'ID' } 128 | $States = if ($PSBoundParameters.ContainsKey('State')) { '^' + ($State -join '$|^') + '$' } else { '.' } 129 | 130 | # IF faster than any scriptblocks 131 | if ($PSCmdlet.ParameterSetName -eq 'All') { 132 | Write-Verbose 'All Jobs' 133 | $ResultJobs = $PoshRS_Jobs 134 | } 135 | else { 136 | Write-Verbose "Filtered Jobs by $Property" 137 | foreach ($job in $PoshRS_Jobs) { 138 | if ($Hash.ContainsKey($job.$Property)) 139 | { 140 | [void]$ResultJobs.Add($job) 141 | } 142 | } 143 | } 144 | foreach ($job in $ResultJobs) { 145 | if (($job.State -match $States) -and 146 | (-not $PSBoundParameters.ContainsKey('HasMoreData') -or $job.HasMoreData -eq $HasMoreData) 147 | ) 148 | { 149 | $job 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /PoshRSJob/Public/Receive-RSJob.ps1: -------------------------------------------------------------------------------- 1 | Function Receive-RSJob { 2 | <# 3 | .SYNOPSIS 4 | Gets the results of the Windows PowerShell runspace jobs in the current session. 5 | 6 | .DESCRIPTION 7 | Gets the results of the Windows PowerShell runspace jobs in the current session. You can use 8 | Get-RSJob and pipe the results into this function to get the results as well. 9 | 10 | .PARAMETER Job 11 | Represents the RSJob object to receive available data from. 12 | 13 | .PARAMETER Name 14 | The name of the jobs to receive available data from. 15 | 16 | .PARAMETER ID 17 | The ID of the jobs to receive available data from. 18 | 19 | .PARAMETER InstanceID 20 | The GUID of the jobs to receive available data from. 21 | 22 | .PARAMETER Batch 23 | Name of the set of jobs to receive available data from. 24 | 25 | .NOTES 26 | Name: Receive-RSJob 27 | Author: Boe Prox/Max Kozlov 28 | 29 | .EXAMPLE 30 | Get-RSJob -State Completed | Receive-RSJob 31 | 32 | Description 33 | ----------- 34 | Retrieves any available data that is outputted from completed RSJobs. 35 | 36 | .EXAMPLE 37 | Receive-RSJob -ID 1,5,78 38 | 39 | Description 40 | ----------- 41 | Receives data from RSJob with IDs 1,5,78. 42 | 43 | .EXAMPLE 44 | Receive-RSJob -InputObject (Get-RSJob) 45 | 46 | Description 47 | ----------- 48 | Receives data from all RSJobs. 49 | #> 50 | [cmdletbinding( 51 | DefaultParameterSetName='Job' 52 | )] 53 | Param ( 54 | [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True, 55 | ParameterSetName='Job', Position=0)] 56 | [Alias('InputObject')] 57 | [RSJob[]]$Job, 58 | [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True, 59 | ParameterSetName='Name', Position=0)] 60 | [string[]]$Name, 61 | [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True, 62 | ParameterSetName='Id', Position=0)] 63 | [int[]]$Id, 64 | [parameter(ValueFromPipelineByPropertyName=$True, 65 | ParameterSetName='InstanceID')] 66 | [string[]]$InstanceID, 67 | [parameter(ValueFromPipelineByPropertyName=$True, 68 | ParameterSetName='Batch')] 69 | [string[]]$Batch 70 | ) 71 | Begin { 72 | If ($PSBoundParameters['Debug']) { 73 | $DebugPreference = 'Continue' 74 | } 75 | $List = New-Object System.Collections.ArrayList 76 | } 77 | Process { 78 | Write-Debug "ParameterSet: $($PSCmdlet.ParameterSetName)" 79 | $Property = $PSCmdlet.ParameterSetName 80 | if ($PSBoundParameters[$Property]) { 81 | Write-Verbose "Adding $($PSBoundParameters[$Property])" 82 | [void]$List.AddRange($PSBoundParameters[$Property]) 83 | } 84 | } 85 | End { 86 | if (-not $List.Count) { return } # No jobs selected to search 87 | $PSBoundParameters[$Property] = $List 88 | [array]$ToReceive = Get-RSJob @PSBoundParameters 89 | 90 | if ($ToReceive.Count) { 91 | $ToReceive | ForEach-Object{ 92 | $_ | WriteStream 93 | if (@("Completed", "Failed", "Stopped") -contains $_.State) { 94 | $_ | SetIsReceived -SetTrue 95 | } 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /PoshRSJob/Public/Remove-RSJob.ps1: -------------------------------------------------------------------------------- 1 | Function Remove-RSJob { 2 | <# 3 | .SYNOPSIS 4 | Deletes a Windows PowerShell runspace job. 5 | 6 | .DESCRIPTION 7 | Deletes a Windows PowerShell background job that has been started using Start-RSJob 8 | 9 | .PARAMETER Job 10 | The job object to remove. 11 | 12 | .PARAMETER Name 13 | The name of the jobs to remove.. 14 | 15 | .PARAMETER ID 16 | The ID of the jobs to remove. 17 | 18 | .PARAMETER InstanceID 19 | The GUID of the jobs to remove. 20 | 21 | .PARAMETER Batch 22 | Name of the set of jobs to remove. 23 | 24 | .PARAMETER Force 25 | Force a running job to stop prior to being removed. 26 | 27 | .NOTES 28 | Name: Remove-RSJob 29 | Author: Boe Prox/Max Kozlov 30 | 31 | .EXAMPLE 32 | Get-RSJob -State Completed | Remove-RSJob 33 | 34 | Description 35 | ----------- 36 | Deletes all jobs with a State of Completed. 37 | 38 | .EXAMPLE 39 | Remove-RSJob -ID 1,5,78 40 | 41 | Description 42 | ----------- 43 | Removes jobs with IDs 1,5,78. 44 | #> 45 | [cmdletbinding( 46 | DefaultParameterSetName='Job', 47 | SupportsShouldProcess = $True 48 | )] 49 | Param ( 50 | [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True, 51 | ParameterSetName='Job', Position=0)] 52 | [Alias('InputObject')] 53 | [RSJob[]]$Job, 54 | [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True, 55 | ParameterSetName='Name', Position=0)] 56 | [string[]]$Name, 57 | [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True, 58 | ParameterSetName='Id', Position=0)] 59 | [int[]]$Id, 60 | [parameter(ValueFromPipelineByPropertyName=$True, 61 | ParameterSetName='InstanceID')] 62 | [string[]]$InstanceID, 63 | [parameter(ValueFromPipelineByPropertyName=$True, 64 | ParameterSetName='Batch')] 65 | [string[]]$Batch, 66 | 67 | [parameter()] 68 | [switch]$Force 69 | ) 70 | Begin { 71 | If ($PSBoundParameters['Debug']) { 72 | $DebugPreference = 'Continue' 73 | } 74 | $List = New-Object System.Collections.ArrayList 75 | } 76 | Process { 77 | Write-Debug "ParameterSet: $($PSCmdlet.ParameterSetName)" 78 | $Property = $PSCmdlet.ParameterSetName 79 | if ($PSBoundParameters[$Property]) { 80 | Write-Verbose "Adding $($PSBoundParameters[$Property])" 81 | [void]$List.AddRange($PSBoundParameters[$Property]) 82 | } 83 | } 84 | End { 85 | if (-not $List.Count) { return } # No jobs selected to search 86 | $PSBoundParameters[$Property] = $List 87 | [void]$PSBoundParameters.Remove('Force') 88 | [array]$ToRemove = Get-RSJob @PSBoundParameters 89 | if ($ToRemove.Count) { 90 | [System.Threading.Monitor]::Enter($PoshRS_Jobs.syncroot) 91 | try { 92 | $ToRemove | ForEach-Object { 93 | If ($PSCmdlet.ShouldProcess("Name: $($_.Name), associated with JobID $($_.Id)",'Remove')) { 94 | If ($_.State -notmatch 'Completed|Failed|Stopped') { 95 | If ($Force) { 96 | [void] $_.InnerJob.Stop() 97 | $PoshRS_Jobs.Remove($_) 98 | } Else { 99 | Write-Error "Unable to remove job $($_.InstanceID)" 100 | } 101 | } Else { 102 | [void]$PoshRS_Jobs.Remove($_) 103 | } 104 | } 105 | } 106 | } 107 | finally { 108 | [System.Threading.Monitor]::Exit($PoshRS_Jobs.syncroot) 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /PoshRSJob/Public/Start-RSJob.ps1: -------------------------------------------------------------------------------- 1 | Function Start-RSJob { 2 | <# 3 | .SYNOPSIS 4 | Starts a background job using runspaces. 5 | 6 | .DESCRIPTION 7 | This will run a command in the background, leaving your console available to perform other tasks. This uses 8 | runspaces in runspacepools which allows for throttling of running jobs. As the jobs are finished, they will automatically 9 | dispose of each runspace and allow other runspace jobs in queue to begin based on the throttling of the runspacepool. 10 | 11 | This is available on PowerShell V3 and above. By doing this, you can use the $Using: variable to take variables 12 | in the local scope and apply those directly into the scriptblock of the background runspace job. 13 | 14 | .PARAMETER ScriptBlock 15 | The scriptblock that holds all of the commands which will be run in the background runspace. You must specify 16 | at least one Parameter in the Param() to account for the item that is being piped into Start-Job. 17 | 18 | .PARAMETER FilePath 19 | This is the path to a file containing code that will be run in the background runspace job. 20 | 21 | .PARAMETER InputObject 22 | The object being piped into Start-RSJob or applied via the parameter. 23 | 24 | .PARAMETER Name 25 | The name of a background runspace job 26 | 27 | .PARAMETER Batch 28 | Name of the batch of RSJobs that will be run 29 | 30 | .PARAMETER ArgumentList 31 | List of values that will be applied at the end of the argument list in the Param() statement. 32 | 33 | .PARAMETER Throttle 34 | Number of concurrent running runspace jobs which are allowed at a time. 35 | 36 | .PARAMETER ModulesToImport 37 | A collection of modules that will be imported into the background runspace job. 38 | 39 | .PARAMETER PSSnapinsToImport 40 | A collection of PSSnapins that will be imported into the background runspace job. 41 | 42 | .PARAMETER FunctionsToImport 43 | A collection of functions that will be imported for use with a background runspace job. 44 | 45 | .PARAMETER FunctionFilesToImport 46 | A collection of files containing custom functions that will be imported into the background runspace job. 47 | 48 | .PARAMETER VariablesToImport 49 | A collection of variables that will be imported for use with a background runspace job. 50 | If used, $using:variable not expanded ! 51 | 52 | .NOTES 53 | Name: Start-RSJob 54 | Author: Boe Prox/Max Kozlov 55 | 56 | .EXAMPLE 57 | Get-ChildItem -Directory | Start-RSjob -Name {$_.Name} -ScriptBlock { 58 | Param($Directory) 59 | Write-Verbose $_ 60 | $Sum = (Get-ChildItem $Directory.FullName -Recurse -Force -ErrorAction SilentlyContinue | 61 | Measure-Object -Property Length -Sum).Sum 62 | [pscustomobject]@{ 63 | Name = $Directory.Name 64 | SizeMB = ([math]::round(($Sum/1MB),2)) 65 | } 66 | } 67 | 68 | Id Name State HasMoreData HasErrors Command 69 | -- ---- ----- ----------- --------- ------- 70 | 13 Contacts Running False False ... 71 | 14 Desktop Running False False ... 72 | 15 Documents Running False False ... 73 | 16 Downloads Running False False ... 74 | 17 Favorites Running False False ... 75 | 18 Links Running False False ... 76 | 19 Music Running False False ... 77 | 20 OneDrive Running False False ... 78 | 21 Pictures Running False False ... 79 | 22 Saved Games Running False False ... 80 | 23 Searches Running False False ... 81 | 24 Videos Running False False ... 82 | 83 | Get-RSJob | Receive-RSJob 84 | 85 | Name SizeMB 86 | ---- ------ 87 | Contacts 0 88 | Desktop 7.24 89 | Documents 83.99 90 | Downloads 10259.6 91 | Favorites 0 92 | Links 0 93 | Music 16691.89 94 | OneDrive 1485.24 95 | Pictures 1734.91 96 | Saved Games 0 97 | Searches 0 98 | Videos 17.19 99 | 100 | Description 101 | ----------- 102 | Starts a background runspace job that looks at the total size of each folder. Using Get-RSJob | Recieve-RSJob shows 103 | the results when the State is Completed. 104 | 105 | .EXAMPLE 106 | $Test = 'test' 107 | $Something = 1..10 108 | 1..5|start-rsjob -Name {$_} -ScriptBlock { 109 | Param($Object) [pscustomobject]@{ 110 | Result=($Object*2) 111 | Test=$Using:Test 112 | Something=$Using:Something 113 | } 114 | } 115 | 116 | Id Name State HasMoreData HasErrors Command 117 | -- ---- ----- ----------- --------- ------- 118 | 76 1 Completed True False ... 119 | 77 2 Running False False ... 120 | 78 3 Running False False ... 121 | 79 4 Completed False False ... 122 | 80 5 Completed False False ... 123 | 124 | Get-RSjob | Receive-RSJob 125 | 126 | Result Test Something 127 | ------ ---- --------- 128 | 2 test {1, 2, 3, 4...} 129 | 4 test {1, 2, 3, 4...} 130 | 6 test {1, 2, 3, 4...} 131 | 8 test {1, 2, 3, 4...} 132 | 10 test {1, 2, 3, 4...} 133 | 134 | Description 135 | ----------- 136 | Shows an example of the $Using: variable being used in the scriptblock. 137 | 138 | .EXAMPLE 139 | $Test = 42 140 | $AnotherTest = 7 141 | $String = 'SomeString' 142 | $ProcName = 'powershell_ise' 143 | $ScriptBlock = { 144 | Param($y,$z) 145 | [pscustomobject] @{ 146 | Test = $y 147 | Proc = (Get-Process -Name $Using:ProcName) 148 | String = $Using:String 149 | AnotherTest = ($z+$_) 150 | PipedObject = $_ 151 | } 152 | } 153 | 154 | 1..5|Start-RSJob $ScriptBlock -ArgumentList $test, $anothertest 155 | 156 | Description 157 | ----------- 158 | Shows an example of the $Using: variable being used in the scriptblock as well as $_ and multiple -ArgumentList parameters. 159 | 160 | #> 161 | [OutputType('RSJob')] 162 | [cmdletbinding( 163 | DefaultParameterSetName = 'ScriptBlock' 164 | )] 165 | Param ( 166 | [parameter(Mandatory = $True, Position = 0, ParameterSetName = 'ScriptBlock')] 167 | [ScriptBlock]$ScriptBlock, 168 | [parameter(Position = 0, ParameterSetName = 'ScriptPath')] 169 | [string]$FilePath, 170 | [parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] 171 | [object]$InputObject, 172 | [parameter()] 173 | [object]$Name, 174 | [parameter()] 175 | [string]$Batch = $([guid]::NewGuid().ToString()), 176 | [parameter()] 177 | [array]$ArgumentList = @(), 178 | [parameter()] 179 | [int]$Throttle = 5, 180 | [parameter()] 181 | [Alias('ModulesToLoad')] 182 | [string[]]$ModulesToImport, 183 | [parameter()] 184 | [Alias('PSSnapinsToLoad')] 185 | [string[]]$PSSnapinsToImport, 186 | [parameter()] 187 | [Alias('FunctionsToLoad')] 188 | [string[]]$FunctionsToImport, 189 | [parameter()] 190 | [Alias('FunctionFilesToLoad')] 191 | [string[]]$FunctionFilesToImport, 192 | [parameter()] 193 | [Alias('VariablesToLoad')] 194 | [string[]]$VariablesToImport 195 | ) 196 | Begin { 197 | 198 | If ($PSBoundParameters['Debug']) { 199 | $DebugPreference = 'Continue' 200 | } 201 | 202 | Write-Debug "[BEGIN]" 203 | 204 | If ($PSBoundParameters.ContainsKey('Verbose')) { 205 | Write-Verbose "Displaying PSBoundParameters" 206 | $PSBoundParameters.GetEnumerator() | ForEach-Object { 207 | Write-Verbose $_ 208 | } 209 | } 210 | 211 | If ($PSBoundParameters.ContainsKey('Name')) { 212 | If ($Name -isnot [scriptblock]) { 213 | $JobName = [scriptblock]::Create("Write-Output `"$Name`"") 214 | } 215 | Else { 216 | $JobName = [scriptblock]::Create( ($Name -replace '\$_', '$Item')) 217 | } 218 | } 219 | Else { 220 | Write-Verbose "Creating default Job Name" 221 | $JobName = [scriptblock]::Create('Write-Output Job$($Id)') 222 | } 223 | 224 | $InitialSessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() 225 | 226 | If ($PSBoundParameters['ModulesToImport']) { 227 | [void]$InitialSessionState.ImportPSModule($ModulesToImport) 228 | } 229 | 230 | If ($PSBoundParameters['PSSnapinsToImport']) { 231 | ForEach ($PSSnapin in $PSSnapinsToImport) { 232 | [void]$InitialSessionState.ImportPSSnapIn($PSSnapin, [ref]$Null) 233 | } 234 | } 235 | 236 | If ($PSBoundParameters['FunctionsToImport']) { 237 | Write-Verbose "Loading custom functions: $($FunctionsToImport -join '; ')" 238 | ForEach ($Function in $FunctionsToImport) { 239 | Try { 240 | RegisterScriptScopeFunction -Name $Function 241 | $Definition = Get-Content Function:\$Function -ErrorAction Stop 242 | Write-Debug "Definition: $($Definition)" 243 | $SessionStateFunction = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function, $Definition 244 | $InitialSessionState.Commands.Add($SessionStateFunction) 245 | } 246 | Catch { 247 | Write-Warning "$($Function): $($_.Exception.Message)" 248 | } 249 | 250 | #Check for an alias and add it as well 251 | If ($Alias = Get-Alias | Where-Object { $_.Definition -eq $Function }) { 252 | $AliasEntry = New-Object System.Management.Automation.Runspaces.SessionStateAliasEntry -ArgumentList $Alias.Name, $Alias.Definition 253 | $InitialSessionState.Commands.Add($AliasEntry) 254 | } 255 | } 256 | } 257 | 258 | If ($PSBoundParameters['FunctionFilesToImport']) { 259 | Write-Verbose "Loading custom function files : $($FunctionFilesToImport -join '; ')" 260 | $functionsInFiles = GetFunctionByFile -FilePath $FunctionFilesToImport 261 | 262 | if ($null -eq $functionsInFiles) { 263 | Write-Warning "Cannot find any functions in given files" 264 | } 265 | else { 266 | ForEach ($function in $functionsInFiles) { 267 | $functionName = $function.Name 268 | Write-Verbose "Loading custom function : $functionName" 269 | 270 | try { 271 | $functionDefinition = GetFunctionDefinitionByFunction -FunctionItem $function 272 | $SessionStateFunction = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $functionName, $functionDefinition 273 | $InitialSessionState.Commands.Add($SessionStateFunction) 274 | } 275 | catch { 276 | Write-Warning "$($functionName): $($_.Exception.Message)" 277 | } 278 | 279 | #Check for an alias and add it as well 280 | if ($Alias = Get-Alias | Where-Object { $_.Definition -eq $Function }) { 281 | $AliasEntry = New-Object System.Management.Automation.Runspaces.SessionStateAliasEntry -ArgumentList $Alias.Name, $Alias.Definition 282 | $InitialSessionState.Commands.Add($AliasEntry) 283 | } 284 | } 285 | } 286 | } 287 | 288 | If ($PSBoundParameters['VariablesToImport']) { 289 | Write-Verbose "Loading variables: $($VariablesToImport -join '; ')" 290 | $UserVariables = New-Object System.Collections.ArrayList 291 | $vartable = $null 292 | foreach ($varname in $VariablesToImport) { 293 | If ($MyInvocation.CommandOrigin -eq 'Runspace') { 294 | $vars = @(Get-Variable $varname -ErrorAction Continue | Where-Object { $_.Options -notmatch 'Constant' }) 295 | } 296 | else { 297 | # matching support uses powershell internals 298 | if ([System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters($varname)) { 299 | if (-not $vartable) { 300 | $Flags = 'static', 'nonpublic', 'instance' 301 | $internal_p = $PSCmdlet.SessionState.GetType().GetProperty('Internal',$Flags) 302 | $internal = $internal_p.GetValue($PSCmdlet.SessionState, $null) 303 | $vartable_m = $internal.GetType().GetMethod('GetVariableTable',$Flags) 304 | $vartable = $vartable_m.Invoke($internal, $null).GetEnumerator() | Select-Object -ExpandProperty Key 305 | } 306 | $vars = @($vartable | Where-Object { $_ -like $varname } | ForEach-Object { 307 | $PSCmdlet.SessionState.PSVariable.Get($_) 308 | }) 309 | } 310 | else { 311 | $vars = @($PSCmdlet.SessionState.PSVariable.Get($varname)) 312 | } 313 | } 314 | [void]$UserVariables.AddRange($vars) 315 | } 316 | if ($UserVariables.Count -gt 0) { 317 | Write-Verbose "Loaded variables: $(($UserVariables | Select-Object -ExpandProperty Name) -join '; ')" 318 | foreach($var in $UserVariables) 319 | { 320 | $v = New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $var.Name, $var.Value, $var.Description 321 | $InitialSessionState.Variables.Add($v) 322 | } 323 | } 324 | } 325 | 326 | If ($PSBoundParameters['FilePath']) { 327 | $ScriptBlock = [scriptblock]::Create((Get-Content $FilePath | Out-String)) 328 | } 329 | 330 | $List = New-Object System.Collections.ArrayList 331 | $ForeachDetected = $false 332 | $ForeachValue = $null 333 | # in v2 $_ variable always defined, so $ForeachDetected always be true on this state 334 | If ($PSCmdlet.SessionState.PSVariable.Get('_')) { 335 | Write-Debug 'it may be ForEach loop' 336 | $ForeachDetected = $true 337 | # on v2 $_ always exists in begin block, on v3+ only in process for foreach loop like $data | Foreach-Object { "here it is: $_" } 338 | Try { 339 | $ForeachValue = $PSCmdlet.SessionState.PSVariable.Get('_') | Select-Object -ExpandProperty Value 340 | if ($ForeachValue -eq $null) { 341 | $ForeachDetected = $false # since psv2 traps on above code, we always do not support $null in $_ 342 | } 343 | } 344 | Catch { 345 | #useless message: always exists on v2 when $_ absent and never on v3+ 346 | # Write-Warning "Start-RSJob : Error adding pipeline variable $($_.Exception.Message)" 347 | $ForeachDetected = $false # on psv2 we doesn't support "$null | %{ Start-RSJob }" pattern 348 | } 349 | } 350 | Write-Debug "ListCount: $($List.Count)" 351 | } 352 | 353 | Process { 354 | Write-Debug "[PROCESS]" 355 | If ($PSBoundParameters.ContainsKey('InputObject')) { 356 | [void]$List.AddRange(@($InputObject)) 357 | # If we here - it is not foreach loop 358 | $ForeachDetected = $false 359 | } 360 | } 361 | 362 | End { 363 | Write-Debug "[END]" 364 | $SBParamVars = @(GetParamVariable -ScriptBlock $ScriptBlock) 365 | $SBParamCount = $SBParamVars.Count 366 | $ArgumentCount = $ArgumentList.Count 367 | # We add $_ into list only if there is no InputObject 368 | # so in case: $data | Foreach-Object { $_.Value | Start-RSJob } 369 | # rsjob get as input $_.Value but not $_ 370 | # but for: $data | Foreach-Object { Start-RSJob } 371 | # rsjob can get $_ as input 372 | ### for long param() lists it can lead to insert null as first param 373 | ###if ($List.Count -eq 0 -and -not $ForeachDetected) { 374 | ### #make empty call ( Start-RSJob ) like ( $null | Start-RSJob ) call to support $null as InputObject in $null | Foreach-Object { Start-RSJob } case 375 | ### $ForeachDetected = $true 376 | ###} 377 | if ($ForeachDetected) { 378 | Write-Debug 'it is ForEach loop' 379 | [void]$List.Add($ForeachValue) 380 | } 381 | # NewParam variant 382 | if ($List.Count) { 383 | $ArgumentCount++ 384 | } 385 | # we add $_ into param() block when (ArgumentList+InputObject).Count > scriptBlock.Param().Count #or ForeachDetected 386 | #$InsertPSItemParam = ($ArgumentCount -gt $SBParamCount -and $List.Count) 387 | 388 | # Current version behaviour variant 389 | $ArgumentCount = $ArgumentList.Count 390 | # Without 'Ignore' fix 391 | #$InsertPSItemParam = ($SBParamCount -ne 1 -or (($SBParamCount -ne $ArgumentCount) -xor $List.Count)) 392 | # With 'Ignore' fix 393 | $InsertPSItemParam = (($SBParamCount -ne 1 -or $SBParamCount -eq $ArgumentCount) -and $List.Count) 394 | # 395 | 396 | Write-Debug ("ArgumentCount: $ArgumentCount | List.Count: $($List.Count) | SBParamCount: $SBParamCount | InsertPSItemParam: $InsertPSItemParam") 397 | #region Convert ScriptBlock for $Using: 398 | $PreviousErrorAction = $ErrorActionPreference 399 | $ErrorActionPreference = 'Stop' 400 | Write-Verbose "PowerShell Version: $($PSVersionTable.PSVersion.Major)" 401 | $UsingVariables = $UsingVariableValues = @() 402 | if (-Not $PSBoundParameters['VariablesToImport']) { 403 | Switch ($PSVersionTable.PSVersion.Major) { 404 | 2 { 405 | Write-Verbose "Using PSParser with PowerShell V2" 406 | $UsingVariables = @(GetUsingVariablesV2 -ScriptBlock $ScriptBlock) 407 | Write-Verbose "Using Count: $($UsingVariables.count)" 408 | Write-Verbose "$($UsingVariables|Out-String)" 409 | Write-Verbose "CommandOrigin: $($MyInvocation.CommandOrigin)" 410 | If ($UsingVariables.count -gt 0) { 411 | $UsingVariableValues = @($UsingVariables | ForEach-Object { 412 | $Name = $_.Content -replace 'Using:' 413 | Try { 414 | If ($MyInvocation.CommandOrigin -eq 'Runspace') { 415 | $Value = (Get-Variable -Name $Name).Value 416 | } 417 | Else { 418 | $Value = $PSCmdlet.SessionState.PSVariable.Get($Name).Value 419 | If ([string]::IsNullOrEmpty($Value)) { 420 | Throw 'No value!' 421 | } 422 | } 423 | New-Object V2UsingVariable -Property @{ 424 | Name = $Name 425 | NewName = '$__using_{0}' -f $Name 426 | Value = $Value 427 | NewVarName = ('__using_{0}') -f $Name 428 | } 429 | } 430 | Catch { 431 | Throw "Start-RSJob : The value of the using variable '$($Var.SubExpression.Extent.Text)' cannot be retrieved because it has not been set in the local session." 432 | } 433 | }) 434 | 435 | Write-Verbose ("Found {0} `$Using: variables!" -f $UsingVariableValues.count) 436 | } 437 | } 438 | Default { 439 | Write-Debug "Using AST with PowerShell V3+" 440 | $UsingVariables = @(GetUsingVariables $ScriptBlock) 441 | #region Get Variable Values 442 | If ($UsingVariables.count -gt 0) { 443 | $UsingVar = $UsingVariables | Group-Object SubExpression | ForEach-Object {$_.Group | Select-Object -First 1} 444 | Write-Debug "CommandOrigin: $($MyInvocation.CommandOrigin)" 445 | $UsingVariableValues = @(ForEach ($Var in $UsingVar) { 446 | Try { 447 | If ($MyInvocation.CommandOrigin -eq 'Runspace') { 448 | $Value = Get-Variable -Name $Var.SubExpression.VariablePath.UserPath 449 | } 450 | Else { 451 | $Value = ($PSCmdlet.SessionState.PSVariable.Get($Var.SubExpression.VariablePath.UserPath)) 452 | If ([string]::IsNullOrEmpty($Value)) { 453 | Throw 'No value!' 454 | } 455 | } 456 | [pscustomobject]@{ 457 | Name = $Var.SubExpression.Extent.Text 458 | Value = $Value.Value 459 | NewName = ('$__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) 460 | NewVarName = ('__using_{0}' -f $Var.SubExpression.VariablePath.UserPath) 461 | } 462 | } 463 | Catch { 464 | Throw "Start-RSJob : The value of the using variable '$($Var.SubExpression.Extent.Text)' cannot be retrieved because it has not been set in the local session." 465 | } 466 | }) 467 | #endregion Get Variable Values 468 | Write-Verbose ("Found {0} `$Using: variables!" -f $UsingVariableValues.count) 469 | } 470 | } 471 | } 472 | } 473 | $ConvertScriptParams = @{ 474 | ScriptBlock = $ScriptBlock 475 | HasParam = ($SBParamVars.Count -ne 0) 476 | UsingVariables = $UsingVariables 477 | UsingVariableValues = $UsingVariableValues 478 | InsertPSItem = $InsertPSItemParam 479 | } 480 | If ($UsingVariableValues.Count -gt 0 -OR $InsertPSItemParam) { 481 | Switch ($PSVersionTable.PSVersion.Major) { 482 | 2 { 483 | $NewScriptBlock = ConvertScriptBlockV2 @ConvertScriptParams 484 | } 485 | Default { 486 | $NewScriptBlock = ConvertScript @ConvertScriptParams 487 | } 488 | } 489 | } 490 | Else { 491 | $NewScriptBlock = $ScriptBlock 492 | } 493 | 494 | $ErrorActionPreference = $PreviousErrorAction 495 | #endregion Convert ScriptBlock for $Using: 496 | 497 | Write-Debug "NewScriptBlock: $($NewScriptBlock)" 498 | 499 | #region RunspacePool Creation 500 | [System.Threading.Monitor]::Enter($PoshRS_RunspacePools.syncroot) 501 | try { 502 | $__RSPObject = $PoshRS_RunspacePools | Where-Object { 503 | $_.RunspacePoolID -eq $Batch 504 | } 505 | If ($__RSPObject) { 506 | Write-Verbose "Using current runspacepool <$($__RSPObject.RunspacePoolID)>" 507 | $RunspacePoolID = $__RSPObject.RunspacePoolID 508 | $RSPObject = $__RSPObject 509 | $RSPObject.LastActivity = Get-Date 510 | } 511 | Else { 512 | Write-Verbose "Creating new runspacepool <$Batch>" 513 | $RunspacePoolID = $Batch 514 | $PSModulePath = $env:PSModulePath 515 | $RunspacePool = [runspacefactory]::CreateRunspacePool($InitialSessionState) 516 | If ($RunspacePool.psobject.Properties["ApartmentState"]) { 517 | #ApartmentState doesn't exist in Nano Server 518 | $RunspacePool.ApartmentState = 'STA' 519 | } 520 | [void]$RunspacePool.SetMaxRunspaces($Throttle) 521 | If ($PSVersionTable.PSVersion.Major -gt 2) { 522 | $RunspacePool.CleanupInterval = [timespan]::FromMinutes(2) 523 | } 524 | $RunspacePool.Open() 525 | $RSPObject = New-Object RSRunspacePool -Property @{ 526 | RunspacePool = $RunspacePool 527 | MaxJobs = $RunspacePool.GetMaxRunspaces() 528 | RunspacePoolID = $RunspacePoolID 529 | LastActivity = Get-Date 530 | } 531 | 532 | #[System.Threading.Monitor]::Enter($PoshRS_RunspacePools.syncroot) #Temp add 533 | [void]$PoshRS_RunspacePools.Add($RSPObject) 534 | $env:PSModulePath = $PSModulePath 535 | } 536 | } 537 | finally { 538 | [System.Threading.Monitor]::Exit($PoshRS_RunspacePools.syncroot) 539 | } 540 | #endregion RunspacePool Creation 541 | 542 | Write-Debug "ListCount: $($List.Count)" 543 | $RealPipeline = $List.Count -gt 0; 544 | if (-Not $RealPipeline) { 545 | [void]$List.Add($null) # fake job creation cycle 546 | } 547 | ForEach ($Item in $List) { 548 | $ID = Increment 549 | $PowerShell = [powershell]::Create().AddScript($NewScriptBlock, $True) 550 | $PowerShell.RunspacePool = $RSPObject.RunspacePool 551 | 552 | if ($RealPipeline) { 553 | Write-Verbose "Using $($Item) as pipeline variable" 554 | [void]$PowerShell.AddArgument($Item) 555 | } 556 | else { 557 | Write-Verbose "No InputObject" 558 | } 559 | Write-Verbose "Checking for Using: variables" 560 | If ($UsingVariableValues.count -gt 0) { 561 | For ($i=0;$i -lt $UsingVariableValues.count;$i++) { 562 | Write-Verbose "Adding Param: $($UsingVariableValues[$i].Name) Value: $($UsingVariableValues[$i].Value)" 563 | [void]$PowerShell.AddParameter($UsingVariableValues[$i].NewVarName, $UsingVariableValues[$i].Value) 564 | } 565 | } 566 | Write-Verbose "Checking for ArgumentList" 567 | # if ($ArgumentList.Count -eq 1) { 568 | # Write-Verbose "Adding Argument: $($ArgumentList[0]) <$($ArgumentList[0].GetType().Fullname)>" 569 | # [void]$PowerShell.AddArgument($ArgumentList[0]) 570 | # } 571 | # else { 572 | ForEach ($Argument in $ArgumentList) { 573 | Write-Verbose "Adding Argument: $($Argument) <$($Argument.GetType().Fullname)>" 574 | [void]$PowerShell.AddArgument($Argument) 575 | } 576 | # } 577 | 578 | Write-Verbose "Invoking Runspace" 579 | $Handle = $PowerShell.BeginInvoke() 580 | Write-Verbose "Determining Job Name" 581 | $_JobName = If ($PSVersionTable.PSVersion.Major -eq 2) { 582 | $JobName.Invoke() 583 | } 584 | Else { 585 | $JobName.InvokeReturnAsIs() 586 | } 587 | $Object = New-Object RSJob -Property @{ 588 | Name = $_JobName 589 | InputObject = $Item 590 | InstanceID = [guid]::NewGuid().ToString() 591 | ID = $ID 592 | Handle = $Handle 593 | InnerJob = $PowerShell 594 | Runspace = $PowerShell.Runspace 595 | Finished = $Handle.IsCompleted 596 | Command = $ScriptBlock.ToString() 597 | RunspacePoolID = $RunSpacePoolID 598 | Batch = $Batch 599 | } 600 | 601 | $RSPObject.LastActivity = Get-Date 602 | Write-Verbose "Adding RSJob to Jobs queue" 603 | [System.Threading.Monitor]::Enter($PoshRS_Jobs.syncroot) 604 | [void]$PoshRS_Jobs.Add($Object) 605 | [System.Threading.Monitor]::Exit($PoshRS_Jobs.syncroot) 606 | Write-Verbose "Display RSJob" 607 | $Object 608 | 609 | } 610 | } 611 | } 612 | -------------------------------------------------------------------------------- /PoshRSJob/Public/Stop-RSJob.ps1: -------------------------------------------------------------------------------- 1 | Function Stop-RSJob { 2 | <# 3 | .SYNOPSIS 4 | Stops a Windows PowerShell runspace job. 5 | 6 | .DESCRIPTION 7 | Stops a Windows PowerShell background job that has been started using Start-RSJob 8 | 9 | .PARAMETER Job 10 | The job object to stop. 11 | 12 | .PARAMETER Name 13 | The name of the jobs to stop.. 14 | 15 | .PARAMETER ID 16 | The ID of the jobs to stop. 17 | 18 | .PARAMETER InstanceID 19 | The GUID of the jobs to stop. 20 | 21 | .PARAMETER Batch 22 | Name of the set of jobs to stop. 23 | 24 | .NOTES 25 | Name: Stop-RSJob 26 | Author: Boe Prox/Max Kozlov 27 | 28 | .EXAMPLE 29 | Get-RSJob -State Completed | Stop-RSJob 30 | 31 | Description 32 | ----------- 33 | Stop all jobs with a State of Completed. 34 | 35 | .EXAMPLE 36 | Stop-RSJob -ID 1,5,78 37 | 38 | Description 39 | ----------- 40 | Stop jobs with IDs 1,5,78. 41 | #> 42 | [cmdletbinding( 43 | DefaultParameterSetName='Job' 44 | )] 45 | Param ( 46 | [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True, 47 | ParameterSetName='Job', Position=0)] 48 | [Alias('InputObject')] 49 | [RSJob[]]$Job, 50 | [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True, 51 | ParameterSetName='Name', Position=0)] 52 | [string[]]$Name, 53 | [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True, 54 | ParameterSetName='Id', Position=0)] 55 | [int[]]$Id, 56 | [parameter(ValueFromPipelineByPropertyName=$True, 57 | ParameterSetName='InstanceID')] 58 | [string[]]$InstanceID, 59 | [parameter(ValueFromPipelineByPropertyName=$True, 60 | ParameterSetName='Batch')] 61 | [string[]]$Batch 62 | ) 63 | Begin { 64 | If ($PSBoundParameters['Debug']) { 65 | $DebugPreference = 'Continue' 66 | } 67 | $List = New-Object System.Collections.ArrayList 68 | } 69 | Process { 70 | Write-Debug "ParameterSet: $($PSCmdlet.ParameterSetName)" 71 | $Property = $PSCmdlet.ParameterSetName 72 | if ($PSBoundParameters[$Property]) { 73 | Write-Verbose "Adding $($PSBoundParameters[$Property])" 74 | [void]$List.AddRange($PSBoundParameters[$Property]) 75 | } 76 | } 77 | End { 78 | if (-not $List.Count) { return } # No jobs selected to search 79 | $PSBoundParameters[$Property] = $List 80 | [array]$ToStop = Get-RSJob @PSBoundParameters 81 | 82 | If ($ToStop.Count) { 83 | [System.Threading.Monitor]::Enter($PoshRS_jobs.syncroot) 84 | try { 85 | $ToStop | ForEach-Object { 86 | Write-Verbose "Stopping $($_.InstanceId)" 87 | if ($_.State -ne 'Completed') { 88 | Write-Verbose "Killing job $($_.InstanceId)" 89 | [void] $_.InnerJob.Stop() 90 | } 91 | } 92 | } 93 | finally { 94 | [System.Threading.Monitor]::Exit($PoshRS_jobs.syncroot) 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /PoshRSJob/Public/Wait-RSJob.ps1: -------------------------------------------------------------------------------- 1 | Function Wait-RSJob { 2 | <# 3 | .SYNOPSIS 4 | Waits until all RSJobs are in one of the following states: 5 | 6 | .DESCRIPTION 7 | Waits until all RSJobs are in one of the following states: 8 | 9 | .PARAMETER Job 10 | The job object to wait for. 11 | 12 | .PARAMETER Name 13 | The name of the jobs to wait for. 14 | 15 | .PARAMETER ID 16 | The ID of the jobs that you want to wait for. 17 | 18 | .PARAMETER InstanceID 19 | The GUID of the jobs that you want to wait for. 20 | 21 | .PARAMETER State 22 | The State of the job that you want to wait for. Accepted values are: 23 | NotStarted 24 | Running 25 | Completed 26 | Failed 27 | Stopping 28 | Stopped 29 | Disconnected 30 | 31 | .PARAMETER Batch 32 | Name of the set of jobs that you want to wait for. 33 | 34 | .PARAMETER HasMoreData 35 | Waits for jobs that have data being outputted. You can specify -HasMoreData:$False to wait for jobs 36 | that have no data to output. 37 | 38 | .PARAMETER Timeout 39 | Timeout after specified number of seconds. This is a global timeout meaning that it is not a per 40 | job timeout. 41 | 42 | .PARAMETER ShowProgress 43 | Displays a progress bar 44 | 45 | .NOTES 46 | Name: Wait-RSJob 47 | Author: Ryan Bushe/Boe Prox/Max Kozlov 48 | 49 | .EXAMPLE 50 | Get-RSJob | Wait-RSJob 51 | Description 52 | ----------- 53 | Waits for jobs which have to be completed. 54 | #> 55 | [cmdletbinding( 56 | DefaultParameterSetName='All' 57 | )] 58 | Param ( 59 | [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True, 60 | ParameterSetName='Job', Position=0)] 61 | [Alias('InputObject')] 62 | [RSJob[]]$Job, 63 | [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True, 64 | ParameterSetName='Name', Position=0)] 65 | [string[]]$Name, 66 | [parameter(ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True, 67 | ParameterSetName='Id', Position=0)] 68 | [int[]]$Id, 69 | [parameter(ValueFromPipelineByPropertyName=$True, 70 | ParameterSetName='InstanceID')] 71 | [string[]]$InstanceID, 72 | [parameter(ValueFromPipelineByPropertyName=$True, 73 | ParameterSetName='Batch')] 74 | [string[]]$Batch, 75 | 76 | [parameter(ParameterSetName='Batch')] 77 | [parameter(ParameterSetName='Name')] 78 | [parameter(ParameterSetName='Id')] 79 | [parameter(ParameterSetName='InstanceID')] 80 | [parameter(ParameterSetName='All')] 81 | [ValidateSet('NotStarted','Running','Completed','Failed','Stopping','Stopped','Disconnected')] 82 | [string[]]$State, 83 | [int]$Timeout, 84 | [switch]$ShowProgress 85 | ) 86 | Begin { 87 | If ($PSBoundParameters['Debug']) { 88 | $DebugPreference = 'Continue' 89 | } 90 | $List = New-Object System.Collections.ArrayList 91 | } 92 | Process { 93 | Write-Debug "ParameterSet: $($PSCmdlet.ParameterSetName)" 94 | $Property = $PSCmdlet.ParameterSetName 95 | if ($PSCmdlet.ParameterSetName -ne 'All' -and $PSBoundParameters[$Property]) { 96 | Write-Verbose "Adding $($PSBoundParameters[$Property])" 97 | [void]$List.AddRange($PSBoundParameters[$Property]) 98 | } 99 | } 100 | End { 101 | if ($PSCmdlet.ParameterSetName -ne 'All') { 102 | $PSBoundParameters[$Property] = $List 103 | } 104 | if (-not $List.Count) { return } # No jobs selected to search 105 | [void]$PSBoundParameters.Remove('Timeout') 106 | [void]$PSBoundParameters.Remove('ShowProgress') 107 | [array]$WaitJobs = Get-RSJob @PSBoundParameters 108 | 109 | $TotalJobs = $WaitJobs.Count 110 | $Completed = 0 111 | Write-Verbose "Wait for $($TotalJobs) jobs" 112 | $Date = Get-Date 113 | while ($Waitjobs.Count -ne 0) { 114 | Start-Sleep -Milliseconds 100 115 | #only ever check $WaitJobs State once per loop, and do all operations based on that snapshot to avoid bugs where the state of a job may have changed mid loop 116 | $JustFinishedJobs = New-Object System.Collections.ArrayList 117 | $RunningJobs = New-Object System.Collections.ArrayList 118 | ForEach ($WaitJob in $WaitJobs) { 119 | If($WaitJob.State -match 'Completed|Failed|Stopped|Suspended|Disconnected' -and $WaitJob.Completed) { 120 | [void]$JustFinishedJobs.Add($WaitJob) 121 | } Else { 122 | [void]$RunningJobs.Add($WaitJob) 123 | } 124 | } 125 | $WaitJobs = $RunningJobs 126 | 127 | $JustFinishedJobs 128 | 129 | $Completed += $JustFinishedJobs.Count 130 | Write-Debug "Wait: $($Waitjobs.Count)" 131 | Write-Debug "Completed: ($Completed)" 132 | Write-Debug "Total: ($Totaljobs)" 133 | Write-Debug "Status: $($Completed/$TotalJobs)" 134 | If ($ShowProgress) { 135 | Write-Progress -Activity "RSJobs Tracker" -Status ("Remaining Jobs: {0}" -f $Waitjobs.Count) -PercentComplete (($Completed/$TotalJobs)*100) 136 | } 137 | if($Timeout -and (New-Timespan $Date).TotalSeconds -ge $Timeout){ 138 | break 139 | } 140 | } 141 | If ($ShowProgress) { 142 | Write-Progress -Activity "RSJobs Tracker" -Completed 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /PoshRSJob/Scripts/TabExpansion.ps1: -------------------------------------------------------------------------------- 1 | #region Custom Argument Completors 2 | #Global variables are required for this functionality (Invoke-ScriptAnalyzer) 3 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "global:options")] 4 | param() 5 | 6 | #region Job ID 7 | $completion_ID = { 8 | param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) 9 | 10 | Get-RSJob | Sort-Object -Property Id | Where-Object { $_.Id -like "$wordToComplete*" } |ForEach-Object { 11 | New-Object System.Management.Automation.CompletionResult $_.Id, $_.Id, 'ParameterValue', ('{0} ({1})' -f $_.Description, $_.ID) 12 | } 13 | } 14 | #endregion Job ID 15 | #region Job Name 16 | $completion_Name = { 17 | param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) 18 | 19 | Get-RSJob | Sort-Object -Property Name | Where-Object { $_.Name -like "$wordToComplete*" } |ForEach-Object { 20 | New-Object System.Management.Automation.CompletionResult $_.Name, $_.Name, 'ParameterValue', ('{0} ({1})' -f $_.Description, $_.ID) 21 | } 22 | } 23 | #endregion Job Name 24 | If (-not (Get-Variable -Scope Global | Where-Object {$_.Name -eq "options"})) { 25 | $global:options = @{ 26 | CustomArgumentCompleters = @{} 27 | NativeArgumentCompleters = @{} 28 | } 29 | } 30 | 31 | $global:options['CustomArgumentCompleters']['Get-RSJob:Id'] = $completion_ID 32 | $global:options['CustomArgumentCompleters']['Get-RSJob:Name'] = $completion_Name 33 | $global:options['CustomArgumentCompleters']['Remove-RSJob:Id'] = $completion_ID 34 | $global:options['CustomArgumentCompleters']['Remove-RSJob:Name'] = $completion_Name 35 | $global:options['CustomArgumentCompleters']['Stop-RSJob:Id'] = $completion_ID 36 | $global:options['CustomArgumentCompleters']['Stop-RSJob:Name'] = $completion_Name 37 | $global:options['CustomArgumentCompleters']['Receive-RSJob:Id'] = $completion_ID 38 | $global:options['CustomArgumentCompleters']['Receive-RSJob:Name'] = $completion_Name 39 | 40 | if (Get-Item function:tabexpansion[2]) { 41 | $function:tabexpansion2 = $function:tabexpansion2 -replace 'End\r\n{','End { if ($null -ne $options) { $options += $global:options} else {$options = $global:options}' 42 | } 43 | #endregion Custom Argument Completors 44 | -------------------------------------------------------------------------------- /PoshRSJob/TypeData/PoshRSJob.Format.ps1xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ComputerTarget 5 | 6 | RSJob 7 | 8 | 9 | 10 | 11 | 12 | 8 13 | Left 14 | 15 | 16 | 17 | 20 18 | Left 19 | 20 | 21 | 22 | 15 23 | Left 24 | 25 | 26 | 27 | 12 28 | Left 29 | 30 | 31 | 32 | 12 33 | Left 34 | 35 | 36 | 37 | 40 38 | Left 39 | 40 | 41 | 42 | 43 | 44 | 45 | Id 46 | 47 | 48 | Name 49 | 50 | 51 | State 52 | 53 | 54 | HasMoreData 55 | 56 | 57 | HasErrors 58 | 59 | 60 | Command 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | Update 69 | 70 | RSJob 71 | 72 | 73 | 74 | 75 | 76 | 77 | Id 78 | 79 | 80 | Name 81 | 82 | 83 | State 84 | 85 | 86 | HasMoreData 87 | 88 | 89 | HasErrors 90 | 91 | 92 | Error 93 | 94 | 95 | Command 96 | 97 | 98 | Handle 99 | 100 | 101 | InnerJob 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /PoshRSJob/TypeData/PoshRSJob.Types.ps1xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | RSJob 4 | 5 | 6 | HasMoreData 7 | 8 | $Flags = 'nonpublic','instance','static' 9 | $_Worker = $This.innerjob.GetType().GetField('worker',$Flags) 10 | if ($_Worker -eq $null) { $_Worker = $This.innerjob.GetType().GetField('_worker',$Flags) } 11 | $Worker = $_Worker.GetValue($This.innerjob) 12 | 13 | $_CRP = $worker.GetType().GetProperty('CurrentlyRunningPipeline',$Flags) 14 | $CRP = $_CRP.GetValue($Worker, $Null) 15 | $State = If (-NOT $This.handle.IsCompleted -AND -NOT [bool]$CRP) { 16 | [System.Management.Automation.PSInvocationState]::NotStarted 17 | } 18 | Else { 19 | $This.InnerJob.InvocationStateInfo.State 20 | } 21 | If ($PSVersionTable['PSEdition'] -and $PSVersionTable.PSEdition -eq 'Core') { 22 | $IsReceived =$This.IsReceived 23 | } 24 | Else { 25 | $Field = $This.gettype().GetField('IsReceived',$Flags) 26 | $IsReceived = $Field.GetValue($This) 27 | } 28 | If ($IsReceived) { 29 | $False 30 | } 31 | Else { 32 | If ($State -eq 'Completed' -AND $This.Handle.IsCompleted -AND (-Not $This.Completed)) { 33 | $Output = $This.Innerjob.GetType().GetProperty('OutputBuffer',$Flags) 34 | $Results = $Output.GetValue($This.Innerjob) 35 | If ($Results.count -gt 0 -AND (-NOT [string]::IsNullOrEmpty($Results))) { 36 | $True 37 | } 38 | Else { 39 | $False 40 | } 41 | } 42 | Else { 43 | If ($This.Output -AND (-NOT [string]::IsNullOrEmpty($This.Output))) { 44 | $True 45 | } 46 | Else { 47 | $False 48 | } 49 | } 50 | } 51 | 52 | 53 | 54 | 55 | 56 | RSJob 57 | 58 | 59 | State 60 | 61 | $Flags = 'nonpublic','instance','static' 62 | $_Worker = $This.innerjob.GetType().GetField('worker',$Flags) 63 | if ($_Worker -eq $null) { $_Worker = $This.innerjob.GetType().GetField('_worker',$Flags) } 64 | $Worker = $_Worker.GetValue($This.innerjob) 65 | 66 | $_CRP = $worker.GetType().GetProperty('CurrentlyRunningPipeline',$Flags) 67 | $CRP = $_CRP.GetValue($Worker, $Null) 68 | If (-NOT $This.handle.IsCompleted -AND -NOT [bool]$CRP) { 69 | [System.Management.Automation.PSInvocationState]::NotStarted 70 | } 71 | Else { 72 | $This.InnerJob.InvocationStateInfo.State 73 | } 74 | 75 | 76 | 77 | 78 | 79 | RSJob 80 | 81 | 82 | HasErrors 83 | 84 | If ($psversiontable.psversion.major -ge 3){ 85 | $this.innerjob.HadErrors 86 | } 87 | Else { 88 | ($this.innerjob.Streams.Error.Count -ne 0) 89 | } 90 | 91 | 92 | 93 | 94 | 95 | RSJob 96 | 97 | 98 | Verbose 99 | 100 | $this.InnerJob.Streams.Verbose 101 | 102 | 103 | 104 | 105 | 106 | RSJob 107 | 108 | 109 | Debug 110 | 111 | $this.InnerJob.Streams.Debug 112 | 113 | 114 | 115 | 116 | 117 | RSJob 118 | 119 | 120 | Warning 121 | 122 | $this.InnerJob.Streams.Warning 123 | 124 | 125 | 126 | 127 | 128 | RSJob 129 | 130 | 131 | Progress 132 | 133 | $this.InnerJob.Streams.Progress 134 | 135 | 136 | 137 | 138 | 139 | RSRunspacePool 140 | 141 | 142 | AvailableJobs 143 | 144 | $this.Runspacepool.GetAvailableRunspaces() 145 | 146 | 147 | 148 | 149 | 150 | RSRunspacePool 151 | 152 | 153 | State 154 | 155 | $this.RunspacePool.RunspacePoolStateInfo.State 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /PoshRSJob/en-US/about_PoshRSJob.help.txt: -------------------------------------------------------------------------------- 1 | TOPIC 2 | PoshRSJob 3 | 4 | SYNOPSIS 5 | PoshRSJob is a PowerShell jobs based framework using runspaces and runspacepools 6 | 7 | DESCRIPTION 8 | PoshRSJob uses runspaces and runspacepools to provide a PSJobs based framework without 9 | the overhead of PSJobs. This allows for the throttling of RSJobs as they are being process. Variables 10 | can be shared between runspaces using a synchronized collection and the locking of collections is recommnended. 11 | 12 | It is important that you do not use "ForEach" when handling data going into Start-RSJob as doing so 13 | will create a new runspacepool for each item and impact its ability to throttle the RSJobs. 14 | 15 | Support for all versions of PowerShell with the exception of V1 is supported in PoshRSJob. 16 | 17 | All elements of PSJobs are supported to include $Using: and legacy parameters such as ArgumentList. 18 | 19 | EXAMPLES 20 | A simple demonstration of how you send data via the pipeline to Start-RSJob to create RSJobs that 21 | run a command using the pipelined data. 22 | 23 | SEE ALSO 24 | Start-RSjob 25 | Stop-RSJob 26 | Get-RSJob 27 | Remove-RSJob 28 | Receive-RSJob -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PoshRSJob 1.7.4.4 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/svrd4ho4otugki24?svg=true)](https://ci.appveyor.com/project/proxb/poshrsjob) [![Join the chat at https://gitter.im/proxb/PoshRSJob](https://badges.gitter.im/proxb/PoshRSJob.svg)](https://gitter.im/proxb/PoshRSJob?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | Provides an alternative to PSjobs with greater performance and less overhead to run commands in the background, freeing up the console. 6 | 7 | ### Be sure to check out and contribute to the [Wiki](https://github.com/proxb/PoshRSJob/wiki)! 8 | 9 | #### Download and install PoshRSJob from the PowerShellGallery using PowerShell: 10 | ```PowerShell 11 | Install-Module -Name PoshRSJob 12 | ``` 13 | 14 | #### Download the latest release (1.7.4.4) 15 | https://github.com/proxb/PoshRSJob/releases/download/1.7.4.4/PoshRSJob.zip 16 | 17 | 18 | More information and examples here: http://learn-powershell.net/2015/04/19/latest-updates-to-poshrsjob/ 19 | 20 | Older post with some legacy examples found here: http://learn-powershell.net/2015/03/31/introducing-poshrsjob-as-an-alternative-to-powershell-jobs/ 21 | 22 | #### Now working on Linux/MacOS with PowerShell Core! 23 | ![alt tag](https://github.com/proxb/PoshRSJob/blob/master/Images/PoshRSJob.gif) 24 | 25 | #### Examples 26 | ================= 27 | ```PowerShell 28 | $Test = 'test' 29 | $Something = 1..10 30 | 1..5|start-rsjob -Name {$_} -ScriptBlock { 31 | [pscustomobject]@{ 32 | Result=($_*2) 33 | Test=$Using:Test 34 | Something=$Using:Something 35 | } 36 | } 37 | Get-RSjob | Receive-RSJob 38 | ``` 39 | ![alt tag](https://github.com/proxb/PoshRSJob/blob/master/Images/GetRSJob-ReceiveRSJob.gif) 40 | 41 | #### This shows the streaming aspect with Wait-RSJob 42 | ```PowerShell 43 | 1..10|Start-RSJob { 44 | if (1 -BAND $_){ 45 | "First ($_)" 46 | }Else{ 47 | Start-sleep -seconds 2 48 | "Last ($_)" 49 | } 50 | }|Wait-RSJob|Receive-RSJob|ForEach{"I am $($_)"} 51 | ``` 52 | ![alt tag](https://github.com/proxb/PoshRSJob/blob/master/Images/RSJobStreamingExample.gif) 53 | 54 | #### Nano Support for PoshRSJob 55 | ![alt tag](https://github.com/proxb/PoshRSJob/blob/master/Images/NanoPoshRSJob.png) 56 | -------------------------------------------------------------------------------- /ReleaseNotes.md: -------------------------------------------------------------------------------- 1 | --------- 2 | |1.7.4.4| 3 | --------- 4 | * #175 Save and restore $env:PSModulePath until we find why it's being clobbered on PS Core 5 | 6 | --------- 7 | |1.7.4.3| 8 | --------- 9 | * #48 Moved garbage collection in runspacepool cleanup 10 | * #48 Removed unnecessary Close() on runspacepool as Dispose() already in use 11 | * #157 (Start-RSJob - be able to import private functions) 12 | * #174 (Minor variable declaration) 13 | * Variables will now always use local scope in runspace by default; not global which prevents incorrect data being presented if a variable is created and not cleaned up in each runspace. 14 | 15 | --------- 16 | |1.7.3.11| 17 | --------- 18 | * #139 (Wait-RSJob is not working with parameter set Name or ID) 19 | * #140 (Wait-RSJob doesn't sleep) 20 | * #141 (-RSJob question) 21 | 22 | --------- 23 | |1.7.3.10| 24 | --------- 25 | * #139 (Wait-RSJob is not working with parameter set Name or ID.) 26 | 27 | --------- 28 | |1.7.3.9| 29 | --------- 30 | * #42 (V2 Pester test hangs in AppVeyor) 31 | * #121 (Missing output from Start-RSJob *bug*) 32 | * #122 (Output stream is not always cleared when using pooled runspaces) 33 | * #136 (RSJob on PSv2) 34 | 35 | --------- 36 | |1.7.3.8| 37 | --------- 38 | * #133 (Passing synchronized hashtable as argument does not work) 39 | * #132 (Not handling single parameter in ArgumentList correctly) 40 | * #136 (RSJob on PSv2) - Some parts may be fixed with how reflection is used 41 | 42 | --------- 43 | |1.7.3.7| 44 | --------- 45 | * #127 (Module loading failure with high throttle counts (and possibly related skipped jobs)) 46 | * #107 (Write-Stream throws lots of spurious errors on missing variables ) 47 | 48 | --------- 49 | |1.7.3.6| 50 | --------- 51 | * (FunctionsToLoad doesn't work if a script is called within a script) 52 | * #124 (powershell_ise processes are left around once PoshRSJob module is loaded) 53 | 54 | --------- 55 | |1.7.3.5| 56 | --------- 57 | * #119 (Passing an empty batch to Wait-RSJob stops all further processing in the caller) 58 | * #95 (RSJob State does not reflect actual state of job) 59 | * #111 (Exception calling "BeginInvoke" with "0" argument(s): "Cannot perform the operation because the runspace pool is not in the 'Opened' state. The current state is 'Closed'." ) 60 | 61 | --------- 62 | |1.7.3.3| 63 | --------- 64 | * Fixed Issue #116 (v1.7.3.0 is extremely slow) 65 | * Fixed Issue #75 (Feature Request: Add RunspaceID handling to Start-RSJob for better throttling support) 66 | * Fixed Issue #107 (Write-Stream throws lots of spurious errors on missing variables) 67 | * Added some better support for streams with Receive-RSJob 68 | 69 | --------- 70 | |1.7.3.0| 71 | --------- 72 | * Fixed Issue #112 (TabExpansion puts a small error in $error) 73 | * Fixed Issue #115 (Multiple runspaces are being created when running Start-RSJob) 74 | 75 | --------- 76 | |1.7.2.9| 77 | --------- 78 | * Fixed Issue #101 (Wait-RsJob -State Completed with no input returns Attempted to divide by zero.) 79 | * Fixed Issue #108 (Caveats of Start-RSJob -ModulesToImport) - Using ForEach loop with PoshRSJob no longer works with this update due to issues that it brought with -ModulesToImport, -FunctionsToLoad and -PSSnapinsToImport where these would fail due to a runspacepool already being used. 80 | 81 | --------- 82 | |1.7.2.7| 83 | --------- 84 | * Fixed Issue #102 (Receive-RsJob doesn't process -InputObject properly) 85 | 86 | --------- 87 | |1.7.2.6| 88 | --------- 89 | * Fixed Issue #99 (Add an InputObject property to RSJob) 90 | 91 | --------- 92 | |1.7.2.5| 93 | --------- 94 | * Fixed Issue #96 (Error when -FunctionsToLoad parameter is used and the Function does not have an alias) 95 | 96 | --------- 97 | |1.7.2.4| 98 | --------- 99 | * Fixed Issue #92 (Cannot load module in PS4 due to "class" keyword) 100 | 101 | 102 | --------- 103 | |1.7.2.3| 104 | --------- 105 | * Fixed Issue #87 (Stop-RSJob gives an error if it has no input) 106 | 107 | --------- 108 | |1.7.2.2| 109 | --------- 110 | * Fixed Issue #59 (Receive-RSJob doesn't clear a job's HasMoreData state) 111 | 112 | --------- 113 | |1.7.2.1| 114 | --------- 115 | * Fixed Issue #83 (FunctionsToImport should include the function's Alias where applicable) 116 | 117 | 118 | --------- 119 | |1.7.1.0| 120 | --------- 121 | * Replaced private apis with public apis (#85 Update RunspaceConfiguration apis to use InitialSessionState instead) 122 | 123 | --------- 124 | |1.7.0.0| 125 | --------- 126 | * Remove need for DLL file for building out the classes. Using pure PowerShell (mostly) via means of here-strings and Add-Type for PowerShell V2-4 and the new Classes keywords for PowerShell V5 which includes PowerShell Core/Nano. 127 | * Remove the prefixes for custom objects so they no longer start with PoshRS.PowerShell.. Now they are V2UsingVariable, RSJob and RSRunspacePool. 128 | 129 | --------- 130 | |1.6.2.1| 131 | --------- 132 | * Add support for PowerShell Core on Linux/MacOS (this still needs more work but should load types within a runspace now!) 133 | 134 | --------- 135 | |1.6.1.0| 136 | --------- 137 | * Fixed Issue #75 (Feature Request: Add RunspaceID handling to Start-RSJob for better throttling support) 138 | * Fixed Issue #82 (Exception setting "RunspacePool" in 1.6.0.0 build) 139 | 140 | --------- 141 | |1.5.7.7| 142 | --------- 143 | * Fixed Issue #69 (Module produces error if imported more than once (PS v2 only)) 144 | * Fixed Issue #64 (HadErrors in PoshRS.PowerShell.RSJob throws errors in PowerShell V2) 145 | * Fixed Issue #67 (Converted Add-Type code for C# classes to be created via Reflection for Nano server support) <- Created custom dll 146 | * Fixed Issue #61 (Receive-RSJob not allowing -Job parameter input) 147 | * Fixed Issue #63 (Replaced Global variables with Script scope) 148 | * Fixed Issue #66 (Parameters don't work with PowerShell V2) 149 | * Fixed Issue #65 (Bug with v2 variable substitution - single-quoted strings get $var references replaced) 150 | * Fixed Issue #68 (ApartmentState Does Not Exist in Nano) 151 | * Fixed Issue #76 (Jobs don't have output when using ADSI WinNT provider (Receive-RSJob)) 152 | -------------------------------------------------------------------------------- /Tests/PoshRSJob.Tests.ps1: -------------------------------------------------------------------------------- 1 | #handle PS2 2 | if(-not $PSScriptRoot) 3 | { 4 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 5 | } 6 | 7 | #Verbose output if this isn't master, or we are testing locally 8 | $Verbose = @{} 9 | if($env:APPVEYOR_REPO_BRANCH -and $env:APPVEYOR_REPO_BRANCH -notlike "master" -or -not $env:APPVEYOR_REPO_BRANCH) 10 | { 11 | $Verbose.add("Verbose",$False) 12 | } 13 | 14 | 15 | Import-Module $PSScriptRoot\..\PoshRSJob\PoshRSJob -Verbose -Force -ErrorAction SilentlyContinue 16 | 17 | <# 18 | $PSVersion = $PSVersionTable.PSVersion.Major 19 | Switch ($PSVersion) { 20 | 4 {Import-Module $PSScriptRoot\..\PoshRSJob\PoshRSJob -Force -ErrorAction SilentlyContinue} 21 | 2 {Import-Module PoshRSJob -Force -ErrorAction SilentlyContinue} 22 | } 23 | #> 24 | 25 | function Test-RSJob([bool]$FullPiping=$true) { 26 | $ScriptBlock = { "{0}: {1}" -f $_, [DateTime]::Now; Start-Sleep -Seconds 5 } 27 | $params = @{ Batch='throttletest'; ScriptBlock=$ScriptBlock; Throttle=5 } 28 | if ($FullPiping) { 29 | $jobs = 1..15 | Start-RSJob @params 30 | } 31 | else { 32 | $jobs = 1..15 | Foreach-Object { $_ | Start-RSJob @params } 33 | } 34 | $jobs | Wait-RSJob | Receive-RSJob 35 | $jobs | Remove-RSJob 36 | } 37 | 38 | $ParameterTestCases = @( 39 | @{ 40 | Case = 'by job object' 41 | Mode = 0 42 | Param = { @{ Job = $TestJob } } 43 | }, 44 | @{ 45 | Case = 'by job object positioned' 46 | Mode = 1 47 | Param = { $TestJob } 48 | }, 49 | @{ 50 | Case = 'by job object from pipeline (name)' 51 | Mode = 2 52 | Param = { '' | Select @{n='Job'; e={$TestJob}} } 53 | }, 54 | @{ 55 | Case = 'by job object from pipeline (value)' 56 | Mode = 2 57 | Param = { $TestJob } 58 | }, 59 | 60 | @{ 61 | Case = 'by id' 62 | Mode = 0 63 | Param = { @{ Id = ($TestJob | Select-Object -Expand id) } } 64 | }, 65 | @{ 66 | Case = 'by id positioned' 67 | Mode = 1 68 | Param = { $TestJob | Select-Object -Expand id } 69 | }, 70 | @{ 71 | Case = 'by id from pipeline (name)' 72 | Mode = 2 73 | Param = { $TestJob | Select-Object id } 74 | }, 75 | @{ 76 | Case = 'by id from pipeline (value)' 77 | Mode = 2 78 | Param = { $TestJob | Select-Object -Expand id } 79 | }, 80 | 81 | @{ 82 | Case = 'by name' 83 | Mode = 0 84 | Param = { @{ Name = ($TestJob | Select-Object -Expand Name) } } 85 | }, 86 | @{ 87 | Case = 'by name positioned' 88 | Mode = 1 89 | Param = { $TestJob | Select-Object -Expand Name } 90 | }, 91 | @{ 92 | Case = 'by name from pipeline (name)' 93 | Mode = 2 94 | Param = { $TestJob | Select-Object Name } 95 | }, 96 | @{ 97 | Case = 'by name from pipeline (value)' 98 | Mode = 2 99 | Param = { $TestJob | Select-Object -Expand Name } 100 | }, 101 | 102 | @{ 103 | Case = 'by InstanceID' 104 | Mode = 0 105 | Param = { @{ InstanceID = ($TestJob | Select-Object -Expand InstanceID) } } 106 | }, 107 | @{ 108 | Case = 'by InstanceID from pipeline (name)' 109 | Mode = 2 110 | Param = { $TestJob | Select-Object InstanceID } 111 | } 112 | ) 113 | 114 | Describe "PoshRSJob PS$($PSVersion)" { 115 | Context 'Strict mode' { 116 | Set-StrictMode -Version latest 117 | It 'should load all functions' { 118 | $Commands = @( Get-Command -CommandType Function -Module PoshRSJob | Select-Object -ExpandProperty Name) 119 | $Commands.count | Should be 6 120 | $Commands -contains "Get-RSJob" | Should be $True 121 | $Commands -contains "Receive-RSJob" | Should be $True 122 | $Commands -contains "Remove-RSJob" | Should be $True 123 | $Commands -contains "Start-RSJob" | Should be $True 124 | $Commands -contains "Stop-RSJob" | Should be $True 125 | $Commands -contains "Wait-RSJob" | Should be $True 126 | } 127 | It 'should load all aliases' { 128 | $Commands = @( Get-Command -CommandType Alias -Module PoshRSJob | Select-Object -ExpandProperty Name) 129 | $Commands.count | Should be 6 130 | $Commands -contains "gsj" | Should be $True 131 | $Commands -contains "rmsj" | Should be $True 132 | $Commands -contains "rsj" | Should be $True 133 | $Commands -contains "ssj" | Should be $True 134 | $Commands -contains "spsj" | Should be $True 135 | $Commands -contains "wsj" | Should be $True 136 | } 137 | It 'should initialize necessary variables' { 138 | $PSCmdlet.SessionState.PSVariable.Get('PoshRS_RunspacePools').Name | Should Be 'PoshRS_RunspacePools' 139 | $PSCmdlet.SessionState.PSVariable.Get('PoshRS_RunspacePoolCleanup').Name | Should Be 'PoshRS_RunspacePoolCleanup' 140 | $PSCmdlet.SessionState.PSVariable.Get('PoshRS_JobCleanup').Name | Should Be 'PoshRS_JobCleanup' 141 | $PSCmdlet.SessionState.PSVariable.Get('PoshRS_JobID').Name | Should Be 'PoshRS_JobID' 142 | $PSCmdlet.SessionState.PSVariable.Get('PoshRS_Jobs').Name | Should Be 'PoshRS_Jobs' 143 | } 144 | } 145 | } 146 | 147 | Describe "Start-RSJob PS$PSVersion" { 148 | Context 'Strict mode' { 149 | Set-StrictMode -Version latest 150 | It 'each job should increment Id by 1' { 151 | $FirstJob = Start-RSJob {$Null} 152 | $InitialID = $FirstJob.Id 153 | $SecondJob = Start-RSJob {$Null} 154 | $NextID = $SecondJob.Id 155 | $NextID - $InitialID | Should Be 1 156 | 157 | } 158 | It 'should return initial job details' { 159 | $Output1 = @( 1 | Start-RSJob @Verbose -ScriptBlock { 160 | Param($Object) 161 | $Object 162 | } ) 163 | $Output5 = @( 1..5 | Start-RSJob @Verbose -ScriptBlock { 164 | Param($Object) 165 | $Object 166 | } ) 167 | $Output1.Count | Should be 1 168 | $Output5.Count | Should be 5 169 | } 170 | InModuleScope PoshRSJob { 171 | It 'should get first param list, 4 variables' { 172 | $Output1 = @(GetParamVariable -ScriptBlock { 173 | [CmdletBinding()] 174 | #Comment 175 | Param($a = $b + $c, 176 | $d, 177 | [Parameter()] 178 | $e = [pscustomobject]@{ 179 | a = $c 180 | b = invoke-command { $args } -argumentlist $b,$c,3 181 | }, 182 | [ValidateScript({$_ -eq $c,$b})] 183 | $f) 184 | $a, $b, $c, $d, $e 185 | Invoke-Command { param($ip) $ip } 186 | }) -join '' 187 | $Output1 | Should Be 'adef' 188 | } 189 | It 'should not get internal param list' { 190 | $Output1 = @(GetParamVariable -ScriptBlock { 191 | $a, $b, $c, $d, $e 192 | Invoke-Command { param($ip) $ip } 193 | }).Count 194 | $Output1 | Should Be 0 195 | } 196 | } 197 | It 'should support $using syntax' { 198 | $Test = "5" 199 | $Output1 = 1 | Start-RSJob @Verbose -ScriptBlock { 200 | $Using:Test 201 | } | Wait-RSJob | Receive-RSJob 202 | $Output1 | Should Be 5 203 | } 204 | It 'should support pipeline $_ syntax' { 205 | $Output1 = @( 1 | Start-RSJob @Verbose -ScriptBlock { 206 | $_ 207 | } ) | Wait-RSJob | Receive-RSJob 208 | $Output1 | Should Be 1 209 | } 210 | It 'should support FunctionFilesToImport syntax' { 211 | $functionFile1 = Join-Path $env:TEMP ([GUID]::NewGuid()).Guid 212 | $functionFile2 = Join-Path $env:TEMP ([GUID]::NewGuid()).Guid 213 | "# test multi-lines scriptblock with comment 214 | function f1 { 215 | Write-Output 'r1' 216 | } 217 | function f2 {Write-Output 'r2'}" | Out-File $functionFile1 218 | "function f3 {Write-Output 'r3'} function f4 {Write-Output 'r4'}" | Out-File $functionFile2 219 | $Output = @( 220 | Start-RSJob @Verbose -ScriptBlock { 221 | f1 222 | f2 223 | f3 224 | f4 225 | } -FunctionFilesToImport $functionFile1, $functionFile2 | Wait-RSJob | Receive-RSJob) 226 | ($Output -join ',') | Should Be 'r1,r2,r3,r4' 227 | Remove-Item $functionFile1, $functionFile2 228 | } 229 | It 'should support VariablesToImport syntax' { 230 | $Output2 = @( 231 | $tester0 = 'tester012'; $testvar1 = 'testvar124'; $testvar2 = 'testvar248' 232 | Start-RSJob @Verbose -ScriptBlock { 233 | $tester0 234 | $testvar1 235 | $testvar2 236 | } -VariablesToImport tester0, testvar* | Wait-RSJob | Receive-RSJob) 237 | ($Output2 -join ',') | Should Be 'tester012,testvar124,testvar248' 238 | } 239 | 240 | } 241 | } 242 | 243 | Describe "Get-RSJob PS$PSVersion" { 244 | Context 'Strict mode' { 245 | Set-StrictMode -Version latest 246 | It 'should return all job details' { 247 | $Output = @( Get-RSJob @Verbose ) 248 | $Props = $Output[0].PSObject.Properties | Select-Object -ExpandProperty Name 249 | 250 | # Write-Output $Output 251 | $Output.count | Should be 12 252 | $Props -contains "Id" | Should be $True 253 | $Props -contains "State" | Should be $True 254 | $Props -contains "HasMoreData" | Should be $True 255 | } 256 | 257 | It 'should return job by state' { 258 | 1..10 | Start-RSJob { Start-Sleep -Seconds 5; $_ } -Throttle 5 259 | $Jobs = @(Get-RSJob -State NotStarted) 260 | $Jobs.Count | Should Be 5 261 | } 262 | 263 | It 'should return job details ' -TestCases $ParameterTestCases { 264 | param( 265 | $Case, 266 | $Mode, 267 | $Param 268 | ) 269 | 270 | $TestJob = Start-RSJob -Name "TestJob $Case" -ScriptBlock { $text=$using:Case; "Working on $text" } 271 | 272 | switch ($Mode) { 273 | 0 { 274 | $Parameters = & $Param 275 | $Output = @( Get-RSJob @Verbose @Parameters ) 276 | } 277 | 1 { 278 | $Parameters = & $Param 279 | $Output = @( Get-RSJob @Verbose $Parameters ) 280 | } 281 | 2 { 282 | $Parameters = & $Param 283 | $Output = @( $Parameters | Get-RSJob @Verbose ) 284 | } 285 | default { 286 | # Fail test on invalid mode 287 | 'Invalid mode !' | Should be 'Invalid mode !!!' 288 | } 289 | } 290 | 291 | $Output.Count | Should be 1 292 | $Output[0] -is 'RSJob' | Should be $true 293 | $Output[0].Name | Should be "TestJob $Case" 294 | } 295 | } 296 | } 297 | 298 | Describe "Stop-RSJob PS$PSVersion" { 299 | Context 'Strict mode' { 300 | Set-StrictMode -Version latest 301 | It 'should not stop a job' { 302 | Stop-RSJob | Should BeNullOrEmpty 303 | } 304 | It 'should stop a job' { 305 | $Job = 1 | Start-RSJob -ScriptBlock { 306 | While ($True) {$Null} 307 | } 308 | $Job | Stop-RSJob 309 | Start-Sleep -Milliseconds 100 310 | $Job.State | Should be 'Stopped' 311 | } 312 | It 'should stop a job ' -TestCases $ParameterTestCases { 313 | param( 314 | $Case, 315 | $Mode, 316 | $Param 317 | ) 318 | $TestJob = 1 | Start-RSJob -ScriptBlock { 319 | While ($True) {$Null} 320 | } 321 | switch ($Mode) { 322 | 0 { 323 | $Parameters = & $Param 324 | Stop-RSJob @Parameters 325 | } 326 | 1 { 327 | $Parameters = & $Param 328 | Stop-RSJob $Parameters 329 | } 330 | 2 { 331 | $Parameters = & $Param 332 | $Parameters | Stop-RSJob 333 | } 334 | default { 335 | # Fail test on invalid mode 336 | 'Invalid mode !' | Should be 'Invalid mode !!!' 337 | } 338 | } 339 | Start-Sleep -Milliseconds 100 340 | $TestJob.State | Should be 'Stopped' 341 | } 342 | } 343 | } 344 | 345 | Describe "Wait-RSJob PS$PSVersion" { 346 | Context 'Strict mode' { 347 | Set-StrictMode -Version latest 348 | 349 | It 'should not wait a job' { 350 | Wait-RSJob | Should BeNullOrEmpty 351 | } 352 | 353 | It 'should wait for jobs ' -TestCases $ParameterTestCases { 354 | param( 355 | $Case, 356 | $Mode, 357 | $Param 358 | ) 359 | $StartDate = Get-Date 360 | $TestJob = 0 | Start-RSJob @Verbose -ScriptBlock { 361 | Start-Sleep -seconds 3 362 | Get-Date 363 | } 364 | switch ($Mode) { 365 | 0 { 366 | $Parameters = & $Param 367 | Wait-RSJob @Parameters # Omitted verbose to avoid clutter 368 | } 369 | 1 { 370 | $Parameters = & $Param 371 | Wait-RSJob $Parameters # Omitted verbose to avoid clutter 372 | } 373 | 2 { 374 | $Parameters = & $Param 375 | $Parameters | Wait-RSJob # Omitted verbose to avoid clutter 376 | } 377 | default { 378 | # Fail test on invalid mode 379 | 'Invalid mode !' | Should be 'Invalid mode !!!' 380 | } 381 | } 382 | $EndDate = Get-Date 383 | ( $EndDate - $StartDate ).TotalSeconds -gt 3 | Should be $True 384 | } 385 | } 386 | } 387 | 388 | Describe "Receive-RSJob PS$PSVersion" { 389 | Context 'Strict mode' { 390 | Set-StrictMode -Version latest 391 | 392 | It 'should not retrieve a job' { 393 | Receive-RSJob | Should BeNullOrEmpty 394 | } 395 | 396 | It 'should retrieve job data ' -TestCases $ParameterTestCases { 397 | param( 398 | $Case, 399 | $Mode, 400 | $Param 401 | ) 402 | $TestJob = Get-RSJob -Name "TestJob $Case" 403 | 404 | switch ($Mode) { 405 | 0 { 406 | $Parameters = & $Param 407 | $Output = @( Receive-RSJob @Verbose @Parameters ) 408 | } 409 | 1 { 410 | $Parameters = & $Param 411 | $Output = @( Receive-RSJob @Verbose $Parameters ) 412 | } 413 | 2 { 414 | $Parameters = & $Param 415 | $Output = @( $Parameters | Receive-RSJob @Verbose) 416 | } 417 | default { 418 | # Fail test on invalid mode 419 | 'Invalid mode !' | Should be 'Invalid mode !!!' 420 | } 421 | } 422 | $Output.Count | Should be 1 423 | $Output[0] | Should be "Working on $Case" 424 | } 425 | } 426 | } 427 | 428 | Describe "Remove-RSJob PS$PSVersion" { 429 | Context 'Strict mode' { 430 | Set-StrictMode -Version latest 431 | 432 | It 'should not remove a job' { 433 | Remove-RSJob | Should BeNullOrEmpty 434 | } 435 | 436 | It 'should only remove specified jobs ' -TestCases $ParameterTestCases { 437 | param( 438 | $Case, 439 | $Mode, 440 | $Param 441 | ) 442 | $TestJobs = @(Get-RSJob | Where-Object { $_.Name -match "^TestJob " }) 443 | $TestJobs.Count -gt 0 | Should Be $True 444 | 445 | $TestJob = $TestJobs | Where-Object { $_.Name -eq "TestJob $Case" } 446 | $TestJob -is 'RSJob' | Should be $true 447 | 448 | $AllIDs = @( $TestJobs | Select-Object -ExpandProperty Id ) 449 | 450 | switch ($Mode) { 451 | 0 { 452 | $Parameters = & $Param 453 | Remove-RSJob @Verbose @Parameters 454 | } 455 | 1 { 456 | $Parameters = & $Param 457 | Remove-RSJob @Verbose $Parameters 458 | } 459 | 2 { 460 | $Parameters = & $Param 461 | $Parameters | Remove-RSJob @Verbose 462 | } 463 | default { 464 | # Fail test on invalid mode 465 | 'Invalid mode !' | Should be 'Invalid mode !!!' 466 | } 467 | } 468 | 469 | $RemainingIDs = @( Get-RSJob @Verbose -Id $AllIDs | Select-Object -ExpandProperty Id ) 470 | #We only removed one 471 | $RemainingIDs.Count -eq ($AllIDs.Count - 1) | Should Be $True 472 | #We removed the right ID 473 | $RemainingIDs -notcontains $TestJob.Id | Should Be $True 474 | } 475 | 476 | It 'should not remove job' { 477 | $TestJob = 1 | Start-RSJob -Name 'ByForce' -ScriptBlock { 478 | While ($True) {$Null} 479 | } 480 | $TestJob -is 'RSJob' | Should be $true 481 | { Remove-RSJob $TestJob -ErrorAction Stop } | Should Throw 482 | } 483 | It 'should remove job by force' { 484 | $TestJob = Get-RSJob -Name 'ByForce' 485 | $TestJob -is 'RSJob' | Should be $true 486 | { Remove-RSJob $TestJob -Force } | Should Not Throw 487 | $TestJob = Get-RSJob -Name 'ByForce' 488 | $TestJob | Should BeNullOrEmpty 489 | } 490 | It 'should remove all jobs' { 491 | Get-RSJob @Verbose | Remove-RSJob @Verbose 492 | $Output = @( Get-RSJob @Verbose ) 493 | 494 | $Output.Count | Should be 0 495 | } 496 | } 497 | } 498 | 499 | Describe "Test RSJob Throttling" { 500 | It "Full Pipe input" { 501 | $StartDate = Get-Date 502 | Test-RSJob $true 503 | $EndDate = Get-Date 504 | ( $EndDate - $StartDate ).TotalSeconds -gt 15 | Should be $True 505 | } 506 | It "OneByOne Pipe input" { 507 | $StartDate = Get-Date 508 | Test-RSJob $false 509 | $EndDate = Get-Date 510 | ( $EndDate - $StartDate ).TotalSeconds -gt 15 | Should be $True 511 | } 512 | } 513 | 514 | Describe "Module OnRemove Actions PS$PSVersion" { 515 | Context 'Strict mode' { 516 | Get-RSJob | Remove-RSJob 517 | Remove-Module -Name PoshRSJob -ErrorAction SilentlyContinue 518 | It 'should remove all variables' { 519 | {Get-Variable PoshRS_Jobs -ErrorAction Stop} | Should Throw 520 | {Get-Variable PoshRS_JobCleanup -ErrorAction Stop} | Should Throw 521 | {Get-Variable PoshRS_JobID -ErrorAction Stop} | Should Throw 522 | {Get-Variable PoshRS_RunspacePoolCleanup -ErrorAction Stop} | Should Throw 523 | {Get-Variable PoshRS_RunspacePools -ErrorAction Stop} | Should Throw 524 | } 525 | } 526 | } 527 | -------------------------------------------------------------------------------- /Tests/appveyor.pester.ps1: -------------------------------------------------------------------------------- 1 | # This script will invoke pester tests 2 | # It should invoke on PowerShell v2 and later 3 | # We serialize XML results and pull them in appveyor.yml 4 | 5 | #If Finalize is specified, we collect XML output, upload tests, and indicate build errors 6 | param( 7 | [switch]$Finalize, 8 | [switch]$Test, 9 | [string]$ProjectRoot = $ENV:APPVEYOR_BUILD_FOLDER 10 | ) 11 | 12 | #Initialize some variables, move to the project root 13 | $Timestamp = Get-date -uformat "%Y%m%d-%H%M%S" 14 | $PSVersion = $PSVersionTable.PSVersion.Major 15 | $TestFile = "TestResults_PS$PSVersion`_$TimeStamp.xml" 16 | 17 | $Address = "https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)" 18 | Set-Location $ProjectRoot 19 | 20 | $Verbose = @{} 21 | if($env:APPVEYOR_REPO_BRANCH -and $env:APPVEYOR_REPO_BRANCH -notlike "master") 22 | { 23 | $Verbose.add("Verbose",$True) 24 | } 25 | 26 | #Run a test with the current version of PowerShell, upload results 27 | if($Test) 28 | { 29 | "`n`tSTATUS: Testing with PowerShell $PSVersion`n" 30 | 31 | if ($PSVersionTable.PSVersion.Major -gt 2) { 32 | #Import-Module Pester -ErrorAction SilentlyContinue 33 | Get-Module Pester | Select-Object -ExpandProperty Path | Set-Content -Path "$ProjectRoot\PesterPath.txt" 34 | } 35 | 36 | Invoke-Pester @Verbose -Path "$ProjectRoot\Tests" -OutputFormat NUnitXml -OutputFile "$ProjectRoot\$TestFile" -PassThru | 37 | Export-Clixml -Path "$ProjectRoot\PesterResults_PS$PSVersion`_$Timestamp.xml" 38 | 39 | If($env:APPVEYOR_JOB_ID) 40 | { 41 | (New-Object 'System.Net.WebClient').UploadFile( $Address, "$ProjectRoot\$TestFile" ) 42 | } 43 | } 44 | 45 | #If finalize is specified, display errors and fail build if we ran into any 46 | If($Finalize) 47 | { 48 | #Show status... 49 | $AllFiles = Get-ChildItem -Path $ProjectRoot\PesterResults*.xml | Select-Object -ExpandProperty FullName 50 | "`n`tSTATUS: Finalizing results`n" 51 | "COLLATING FILES:`n$($AllFiles | Out-String)" 52 | 53 | #What failed? 54 | $Results = @( Get-ChildItem -Path "$ProjectRoot\PesterResults_PS*.xml" | Import-Clixml ) 55 | 56 | $FailedCount = $Results | 57 | Select-Object -ExpandProperty FailedCount | 58 | Measure-Object -Sum | 59 | Select-Object -ExpandProperty Sum 60 | 61 | if ($FailedCount -gt 0) { 62 | 63 | $FailedItems = $Results | 64 | Select-Object -ExpandProperty TestResult | 65 | Where-Object {$_.Passed -notlike $True} 66 | 67 | "FAILED TESTS SUMMARY:`n" 68 | $FailedItems | ForEach-Object { 69 | $Item = $_ 70 | [pscustomobject]@{ 71 | Describe = $Item.Describe 72 | Context = $Item.Context 73 | Name = "It $($Item.Name)" 74 | Result = $Item.Result 75 | } 76 | } | 77 | Sort-Object Describe, Context, Name, Result | 78 | Format-List 79 | 80 | throw "$FailedCount tests failed." 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # See http://www.appveyor.com/docs/appveyor-yml for many more options 2 | 3 | # Skip on updates to the readme. 4 | # We can force this by adding [skip ci] or [ci skip] anywhere in commit message 5 | skip_commits: 6 | message: /updated readme.*/ 7 | 8 | install: 9 | - cinst pester -y 10 | 11 | build: false 12 | 13 | test_script: 14 | # Test with native PS version 15 | - ps: . .\Tests\appveyor.pester.ps1 -Test 16 | # Finalize pass - collect and upload results 17 | - ps: . .\Tests\appveyor.pester.ps1 -Finalize 18 | --------------------------------------------------------------------------------