diff --git a/.gitignore b/.gitignore
index 9c528b6f..beab25e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,5 @@
# ignore the settings folder and files for VSCode and PSS
-.vscode/*
*.psproj
*TempPoint*
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..7b58a8e7
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,11 @@
+{
+ "editor.detectIndentation": false,
+ "editor.insertSpaces": false,
+ "powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationAfterEveryPipeline",
+ "powershell.codeFormatting.trimWhitespaceAroundPipe": true,
+ "powershell.codeFormatting.whitespaceBetweenParameters": true,
+ "powershell.codeFormatting.autoCorrectAliases": true,
+ "powershell.codeFormatting.useCorrectCasing": true,
+
+ "files.encoding": "utf8bom",
+}
\ No newline at end of file
diff --git a/PSFramework/PSFramework.psd1 b/PSFramework/PSFramework.psd1
index 4c82df50..c2fad0ec 100644
--- a/PSFramework/PSFramework.psd1
+++ b/PSFramework/PSFramework.psd1
@@ -4,7 +4,7 @@
RootModule = 'PSFramework.psm1'
# Version number of this module.
- ModuleVersion = '1.13.406'
+ ModuleVersion = '1.13.414'
# ID used to uniquely identify this module
GUID = '8028b914-132b-431f-baa9-94a6952f21ff'
@@ -95,6 +95,7 @@
'Install-PSFLoggingProvider'
'Invoke-PSFCommand'
'Invoke-PSFFilter'
+ 'Invoke-PSFRunspace'
'Join-PSFPath'
'New-PSFFilter'
'New-PSFFilterCondition'
diff --git a/PSFramework/PSFramework.psproj.psbuild b/PSFramework/PSFramework.psproj.psbuild
deleted file mode 100644
index 07a24531..00000000
Binary files a/PSFramework/PSFramework.psproj.psbuild and /dev/null differ
diff --git a/PSFramework/bin/PSFramework.dll b/PSFramework/bin/PSFramework.dll
index b7090b20..a8fe0c64 100644
Binary files a/PSFramework/bin/PSFramework.dll and b/PSFramework/bin/PSFramework.dll differ
diff --git a/PSFramework/bin/PSFramework.pdb b/PSFramework/bin/PSFramework.pdb
index 274101c9..6bbb17b4 100644
Binary files a/PSFramework/bin/PSFramework.pdb and b/PSFramework/bin/PSFramework.pdb differ
diff --git a/PSFramework/bin/PSFramework.xml b/PSFramework/bin/PSFramework.xml
index f5048d37..d1d56fcf 100644
--- a/PSFramework/bin/PSFramework.xml
+++ b/PSFramework/bin/PSFramework.xml
@@ -8573,6 +8573,49 @@
Purge all RBVs of datasets from all expired runspaces
+
+
+ The result of a runspace task
+
+
+
+
+ The object that triggered the task
+
+
+
+
+ All output
+
+
+
+
+ All Information Messages
+
+
+
+
+ All verbose messages
+
+
+
+
+ All warning messages
+
+
+
+
+ All error records
+
+
+
+
+ Creates a result object, representing the completed result of the runspace task.
+
+ The original argument for the task
+ The output result of the task
+ The streams the task sent
+
Contains the state a managed, unique runspace can be in.
@@ -8593,6 +8636,245 @@
The runspace has followed its order to stop and is currently disabled
+
+
+ An individual task executed in the runspace pool of its hosting RunspaceWrapper
+
+
+
+
+ The item to process in this task
+
+
+
+
+ Whether the task has completed successfully
+
+
+
+
+ Create a new runspace task. If the host has already stared execution, it is immediately queued for execution.
+
+ The hosting RunspaceWrapper
+ The item to process in this task
+
+
+
+ If the task is complete, collect results and direct the streams. Do nothing if not complete yet.
+ Delists itself from the hosting RunspaceWrapper, if completed.
+
+ The command runtime to whose streams to write the results
+ Do not write to additional streams
+ Whether it successfully collected the results
+
+
+
+ Wait until the task completes, then get the full result with all stream information
+
+ A result object, containing output and streams
+ Don't try to collect results before starting the task
+
+
+
+ Wait until the task completes, then get the output
+
+ All output results of the task
+ Don't try to collect results before starting the task
+
+
+
+ Wait until the task completes, then collect results and direct the streams.
+
+ The command runtime to whose streams to write the results
+ hether to NOT write to the different streams.
+ Don't try to collect results before starting the task
+
+
+
+ Start this task, queueing the code as a runspace in the runspace pool for execution
+
+ If the runspacepool of the hosting RunspaceWrapper has not been opened yet, we cannot start yet
+
+
+
+ Cancel and destroy this task.
+
+
+
+
+ Clean up this object
+
+
+
+
+ Runspace managing class used by Invoke-PSFRunspace.
+
+
+
+
+ Nme of the workload
+
+
+
+
+ The code to run in parallel
+
+
+
+
+ How many runspace tasks to execute in parallel
+
+
+
+
+ Total number of tasks in this wrapper
+
+
+
+
+ Number of Tasks still pending
+
+
+
+
+ Number of Tasks completed
+
+
+
+
+ What each runspace task will have available
+
+
+
+
+ List of tasks to execute
+
+
+
+
+ Variables available to all tasks
+
+
+
+
+ Functions available to all tasks
+
+
+
+
+ Modules available to all tasks
+
+
+
+
+ Whether the RunspaceWrapper is currently open for tasks
+
+
+
+
+ Add a variable to the initial sessionstate
+
+ name of the variable
+ Value of the variable
+
+
+
+ Add multiple variables to the initial sessionstate
+
+ Name/value map of variables to inclue
+
+
+
+ Add a module by name or path
+
+ Name or path to the module
+
+
+
+ Add a module by its module info object
+
+ The module info object
+
+
+
+ Define a function available to all tasks
+
+
+
+
+
+
+
+ Define a function available to all tasks
+
+ Function info object to copy over
+
+
+
+
+ Start the entire wrapper, creating a runspace pool and preparing for execution
+
+
+
+
+ Close the runspace pool, terminate everything and clean up.
+
+
+
+
+ Make sure everything is cleaned out after the job is done
+
+
+
+
+ Add a task that should be executed
+
+ The argument for which the task should be executed
+
+
+
+ Add a list of tasks that all should be executed
+
+ The arugments for each of which the task should be executed
+
+
+
+ Wait for all task results and receive results directly into the streams of the calling command
+
+ The command runtime whose streams to write to
+ Whether additional streams should be hidden and only output shown
+
+
+
+ Collect all tasks that already completed, and directly write the results to the streams of the calling command
+
+ The command runtime whose streams to write to
+ Whether additional streams should be hidden and only output shown
+
+
+
+ Wait for all task results and return result report objects, including information on all streams of the runspace
+
+ List of completion reports, including all output, warnings, errors, informational messages, etc.
+
+
+
+ Retrieve result report objects for each task already completed, including information on all streams of the runspace
+
+ List of completion reports, including all output, warnings, errors, informational messages, etc.
+
+
+
+ Wait for all task results and return the output.
+
+ The resulting output of all tasks
+
+
+
+ Receive the output of all currently completed tasks
+
+ The output of all currently completed tasks
+
The serialization output options available
@@ -8927,6 +9209,16 @@
Whether to execute the scriptblock in the global scope
+
+
+ When enabled, do not filter based on user input.
+
+
+
+
+ Maximum number of results to show when tab-completing.
+
+
If true: Match input against any part of the options, not just the beginning
@@ -9030,6 +9322,11 @@
Whether PSFramework completion should use fuzzy-matching when matching completion values with the already typed text.
+
+
+ The maximum number of results shown to the user, before truncating data sets.
+
+
Registers a new completion scriptblock
diff --git a/PSFramework/changelog.md b/PSFramework/changelog.md
index 2728856e..13c5a70f 100644
--- a/PSFramework/changelog.md
+++ b/PSFramework/changelog.md
@@ -1,5 +1,16 @@
# CHANGELOG
+## 1.13.414 (2025-10-14)
+
+- Upd: Wait-PSFRunspaceWorkflow - adding ProgressBar with `-ShowProgress` (#698 | @fslef)
+- Upd: Register-PSFArgumentCompleter - adding parameter `-DontFilter`, disabling automatic filtering by user input and enabling complex custom filtering inside of the completer (#696)
+- Upd: Register-PSFArgumentCompleter - adding parameter `-MaxResults`, truncating large result-sets for a more userfriendly display (#694)
+- Fix: Invoke-PSFProtectedCommand - $paramStopPSFFunction Leaks To Global Scope (#697)
+- Fix: Register-PSFArgumentCompleter - ignores `-DontSort`
+- Fix: Configuration import - simple persistence from Environment fails for arrays (#692)
+- Fix: ConvertTo-PSFPsd1 - Error: The property 'Depth' cannot be found on this object (#695)
+- Fix: Filter "Environment" - "Elevated" miss-detects MacOS (#693)
+
## 1.13.406 (2025-08-29)
- New: Assert-PSFInternalCommand - Verifies, that the command calling it in turn was only called from another command within the same module. (#685)
diff --git a/PSFramework/en-us/stringsRunspaces.psd1 b/PSFramework/en-us/stringsRunspaces.psd1
index bb57809d..762e6371 100644
--- a/PSFramework/en-us/stringsRunspaces.psd1
+++ b/PSFramework/en-us/stringsRunspaces.psd1
@@ -2,6 +2,10 @@
'Add-PSFRunspaceWorker.Error.UntrustedFunctionCode' = 'Failed to load function {0}: The provided function code is not trusted (in Constrained language Mode) and cannot be imported. Ensure the code building the scriptblock is trusted to create a non-constrained scriptblock.' # $pair.Key
'Add-PSFRunspaceWorker.Error.UntrustedTextFunction' = 'Failed to load function {0}: String-based code is not trusted in a secured console. Provide its code as a scriptblock, rather than a string to enable code trust verification.' # $pair.Key
+ 'Invoke-PSFRunspace.Error.ModuleImport' = 'Failed to include module: "{0}"' # $module
+ 'Invoke-PSFRunspace.Error.UntrustedTextFunction' = 'Failed to import function "{0}". Providing function-code as text is not supported in a hardened PowerShell process. Provide the function-code instead as a scriptblock.' # $pair.Key
+ 'Invoke-PSFRunspace.Error.UntrustedFunctionCode' = 'Failed to import function "{0}". Scriptblock is not in FullLanguage mode and thus not trusted!' # $pair.Key
+
'New-PSFRunspaceWorkflow.Error.ExistsAlready' = 'Failed to create workflow {0}: It already exists! Use "-Force" to overwrite the existing Runspace Workflow, interrupting all currently ongoing processing.' # $Name
'Read-PSFRunspaceQueue.Error.Continual.TooManyWorkflows' = 'Error resolving queue to read from in continuous mode: {0}. Multiple workflows found, while continuous read only supports a single workflow. Workflows found: {1}' # $Name, ($resolvedWorkflows.Name -join ', ')
diff --git a/PSFramework/functions/data/ConvertTo-PSFPsd1.ps1 b/PSFramework/functions/data/ConvertTo-PSFPsd1.ps1
index 6f43f5ce..7443cfe2 100644
--- a/PSFramework/functions/data/ConvertTo-PSFPsd1.ps1
+++ b/PSFramework/functions/data/ConvertTo-PSFPsd1.ps1
@@ -47,7 +47,7 @@
)
begin {
$converter = [PSFramework.Data.Psd1Converter]::new()
- $converter.Depth = $Depth
+ $converter.MaxDepth = $Depth
$converter.EnableVerbose = $EnableVerbose
$converter.Config = $Configuration
$converter.Cmdlet = $PSCmdlet
diff --git a/PSFramework/functions/runspace/Invoke-PSFRunspace.ps1 b/PSFramework/functions/runspace/Invoke-PSFRunspace.ps1
new file mode 100644
index 00000000..8fc5c843
--- /dev/null
+++ b/PSFramework/functions/runspace/Invoke-PSFRunspace.ps1
@@ -0,0 +1,207 @@
+function Invoke-PSFRunspace {
+ <#
+ .SYNOPSIS
+ Execute a scriptblock in parallel.
+
+ .DESCRIPTION
+ Execute a scriptblock in parallel.
+
+ This command offers two separate "modes" of operation:
+ - Similar to "ForEach-Object -Parallel" within a PowerShell pipeline.
+ - Similar to "Start-ThreadJob", in that it returns a task object you can collect reults from later.
+
+ In the former scenario, it offers redirecting all the streams (verbose, warning, errors, ...) for each task into the main runspace.
+ Supports importing variables into the background runspaces using the "$using:"-statement
+
+ .PARAMETER ScriptBlock
+ The code to execute in parallel.
+
+ .PARAMETER InputObject
+ The items for which to execute the scriptblock.
+ One instance per item, whether piped or provided explicitly.
+
+ .PARAMETER AsTask
+ Rather than wait for the processing to complete, return an object representing the overall execution.
+ To collect the results, call one of the following methods on the object:
+ - Collect(): Wait until everything is completed and collect the output.
+ - CollectCurrent(): Collect the output of tasks that have completed so far
+ - CollectResult(): Wait until everything is completed and collect report objects for each item, including the different streams, input and output
+ - CollectCurrentResult(): Collect report objects for each task already completed, including the different streams, input and output
+
+ .PARAMETER Name
+ Name of the runspace workload.
+ Documentary only.
+ Defaults to:
+
+ .PARAMETER OutputStyle
+ How should output be processed.
+ - Output: Produce the output of each task as output of this command. Redirect all background-streams into this command's streams unless combined ith "-NoStreams"
+ - Result: Each task is completed with a results object, including the different streams, input and output
+ Has no effect when used with "-AsTask"
+ Defaults to: Output
+
+ .PARAMETER ThrottleLimit
+ How man tasks are executed in parallel.
+ Defaults to: 5
+
+ .PARAMETER Variables
+ Any variables to provide to the background runspaces.
+ Maps name to value.
+ You can also import variables into the background runspaces by using the "$using:"-statement
+
+ .PARAMETER Functions
+ Any functions to import into the background runspace.
+ Maps name to code.
+ Code can be either text or scriptblock:
+ - @{ 'Get-Example' = (Get-Command Get-Example).Definition }
+ - @{ 'Get-Example' = [scriptblock]::Create((Get-Command Get-Example).Definition) }
+
+ Note:
+ In a secured environment, where PowerShell Constrained Language Mode has been deployed, only the scriptblock-variant will work!
+
+ .PARAMETER Modules
+ Any modules to include in the background runspaces.
+
+ .PARAMETER ImportPSFramework
+ Import the PSFramework into the background runspaces.
+
+ .PARAMETER NoStreams
+ Do not redirect background streams into the streams of Invoke-PSFRunspace.
+ Has no effect when using either "-AsTask" or "-OutputStyle Result".
+
+ .PARAMETER InitialSessionState
+ A full initial session state object you preconfigured to operate the background tasks.
+ Note: Variables, type references & method invocations must work for this command to succeed.
+
+ .EXAMPLE
+ PS C:\> Get-ADUser -Filter * | Invoke-PSFRunspace { $_ | Get-ADPrincipalGroupMembership }
+
+ Retrieve all users from Active Directory, then retrieve their group memberships
+
+ .EXAMPLE
+ PS C:\> Get-ADUser -Filter * | Invoke-PSFRunspace { $_ | Get-ADPrincipalGroupMembership } -OutputStyle Result
+
+ Retrieve all users from Active Directory, then retrieve their group memberships, returning a report object, mapping each user to their group memberships.
+
+ .EXAMPLE
+ PS C:\> $task = Get-ADUser -Filter * | Invoke-PSFRunspace { $_ | Get-ADPrincipalGroupMembership }
+ PS C:\> $task.CollectResult()
+
+ First start searching for all users' group memberships.
+ Then later collect all the results in convenient result datasets, mapping input to output and including all errors / warnings / etc.
+ #>
+ [OutputType([PSFramework.Runspace.RunspaceWrapper])]
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory = $true, Position = 0)]
+ [ScriptBlock]
+ $ScriptBlock,
+
+ [Parameter(ValueFromPipeline = $true)]
+ $InputObject,
+
+ [switch]
+ $AsTask,
+
+ [string]
+ $Name = '',
+
+ [ValidateSet('Output', 'Result')]
+ [string]
+ $OutputStyle = 'Output',
+
+ [int]
+ $ThrottleLimit = 5,
+
+ [ValidateNotNull()]
+ [hashtable]
+ $Variables = @{},
+
+ [ValidateNotNull()]
+ [hashtable]
+ $Functions = @{},
+
+ [object[]]
+ $Modules,
+
+ [switch]
+ $ImportPSFramework,
+
+ [switch]
+ $NoStreams,
+
+ [initialsessionstate]
+ $InitialSessionState
+ )
+ begin {
+ $runspaceWrapper = [PSFramework.Runspace.RunspaceWrapper]::new()
+ $runspaceWrapper.ThrottleLimit = $ThrottleLimit
+ $runspaceWrapper.Name = $Name
+
+ #region Provide Context
+ if ($InitialSessionState) { $runspaceWrapper.initialsessionstate = $InitialSessionState }
+
+ $runspaceWrapper.AddVariable($Variables)
+
+ # See usually invisible background streams
+ $runspaceWrapper.AddVariable("VerbosePreference", $VerbosePreference)
+ $runspaceWrapper.AddVariable("InformationPreference", $InformationPreference)
+
+ if ($ImportPSFramework) { $runspaceWrapper.AddModule((Get-Module PSFramework)) }
+ foreach ($module in $Modules) {
+ try { $runspaceWrapper.AddModule($module) }
+ catch { Stop-PSFFunction -String 'Invoke-PSFRunspace.Error.ModuleImport' -StringValues $module -EnableException $true -Cmdlet $PSCmdlet }
+ }
+
+ foreach ($pair in $Functions.GetEnumerator()) {
+ if ($consoleConstrained -and $pair.Value -isnot [ScriptBlock]) {
+ Stop-PSFFunction -String 'Invoke-PSFRunspace.Error.UntrustedTextFunction' -StringValues $pair.Key -EnableException $true -Cmdlet $PSCmdlet -Category SecurityError
+ }
+ if ($consoleConstrained -and ([PsfScriptBlock]$pair.Value).LanguageMode -ne 'FullLanguage') {
+ Stop-PSFFunction -String 'Invoke-PSFRunspace.Error.UntrustedFunctionCode' -StringValues $pair.Key -EnableException $true -Cmdlet $PSCmdlet -Category SecurityError
+ }
+ if ($pair.Value -is [ScriptBlock]) {
+ $runspaceWrapper.AddFunction($pair.Key, $pair.Value)
+ $functionsResolved[$pair.Key] = $pair.Value
+ continue
+ }
+ $runspaceWrapper.AddFunction($pair.Key, [scriptblock]::Create($pair.Value))
+ }
+ #endregion Provide Context
+
+ #region Handle Code
+ $actualCode = $ScriptBlock
+
+ if ($actualCode.Ast.Extent.Text -match '\$using:') {
+ $convertedCodeData = ConvertFrom-PsfUsingStatement -ScriptBlock $actualCode
+ $actualCode = $convertedCodeData.Code
+ foreach ($variableName in $convertedCodeData.Variables) {
+ $runspaceWrapper.AddVariable($variableName, $PSCmdlet.SessionState.PSVariable.Get($variableName).Value)
+ }
+ }
+
+ $runspaceWrapper.Code = $actualCode
+ #endregion Handle Code
+ $runspaceWrapper.Start()
+ }
+ process {
+ if ($PSBoundParameters.Keys -contains 'InputObject') {
+ $runspaceWrapper.AddTaskBulk(@($InputObject))
+ }
+ if ($AsTask) { return }
+
+ switch ($OutputStyle) {
+ 'Result' { $runspaceWrapper.CollectCurrentResult() }
+ default { $runspaceWrapper.CollectCurrent($PSCmdlet, $NoStreams.ToBool()) }
+ }
+ }
+ end {
+ if ($AsTask) { return $runspaceWrapper }
+
+ switch ($OutputStyle) {
+ 'Result' { $runspaceWrapper.CollectResult() }
+ default { $runspaceWrapper.Collect($PSCmdlet, $NoStreams.ToBool()) }
+ }
+ $runspaceWrapper.Stop()
+ }
+}
\ No newline at end of file
diff --git a/PSFramework/functions/runspace/Wait-PSFRunspaceWorkflow.ps1 b/PSFramework/functions/runspace/Wait-PSFRunspaceWorkflow.ps1
index 39376550..a883a513 100644
--- a/PSFramework/functions/runspace/Wait-PSFRunspaceWorkflow.ps1
+++ b/PSFramework/functions/runspace/Wait-PSFRunspaceWorkflow.ps1
@@ -2,10 +2,10 @@
<#
.SYNOPSIS
Wait for a Runspace Workflow to complete.
-
+
.DESCRIPTION
Wait for a Runspace Workflow to complete.
-
+
.PARAMETER Queue
The name of the queue to measure completion by.
Usually the last output queue in the chain of steps.
@@ -13,17 +13,17 @@
.PARAMETER WorkerName
The name of the worker to measure completion by.
Usually the last step in the chain of steps.
-
+
.PARAMETER Closed
The workflow is considered completed, when the queue or worker selected is closed.
-
+
.PARAMETER Count
The workflow is considered completed, when the queue selected has received the specified number of results.
This looks at the total amount ever provided, not current number queued.
-
+
.PARAMETER ReferenceQueue
The workflow is considered completed, when the queue selected has received the same number of items as the reference queue.
-
+
.PARAMETER ReferenceMultiplier
When comparing the result queue with a reference queue, multiply the number of items in the reference queue by this value.
Use when the number of output items, based from the original input, scales by a constant multiplier.
@@ -31,7 +31,7 @@
.PARAMETER QueueTimeout
Wait based on how long ago the last item was added to the specified queue.
-
+
.PARAMETER PassThru
Pass through the workflow object waiting for.
Useful to stop it once waiting has completed.
@@ -39,16 +39,19 @@
.PARAMETER Timeout
Maximum wait time. Throws an error if exceeded.
Defaults to 1 day.
-
+
.PARAMETER Name
Name of the workflow to wait for.
-
+
.PARAMETER InputObject
A runspace workflow object to wait for.
-
+
+ .PARAMETER ShowProgress
+ To show a progressbar while waiting for runspace completion
+
.EXAMPLE
PS C:\> $workflow | Wait-PSFRunspaceWorkflow -Queue Done -Count 1000
-
+
Wait until 1000 items have been queued to "Done" in total.
.EXAMPLE
@@ -60,10 +63,11 @@
PS C:\> $workflow | Wait-PSFRunspaceWorkflow -Queue Done -ReferenceQueue Input
Wait until the "Done" queue has processed as many items as there were written to the "Input" queue.
-
+
.LINK
https://psframework.org/documentation/documents/psframework/runspace-workflows.html
#>
+
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSReviewUnusedParameter", "")]
[CmdletBinding(DefaultParameterSetName = 'Closed')]
param (
@@ -107,6 +111,9 @@
[switch]
$PassThru,
+ [switch]
+ $ShowProgress,
+
[PSFramework.Parameter.TimeSpanParameter]
$Timeout,
@@ -119,18 +126,119 @@
[PSFramework.Runspace.RSWorkflow[]]
$InputObject
)
+ begin {
+ #region Utility Functions
+ function Write-WorkflowProgress {
+ [CmdletBinding()]
+ param (
+ [string]
+ $Mode,
+
+ [DateTime]
+ $Start,
+
+ $Workflow,
+
+ [int]
+ $CurrentCount,
+
+ [string]
+ $QueueName,
+
+ $QueueObject,
+
+ [string]
+ $WorkerName,
+
+ [int]
+ $WorkflowProgressID,
+
+ [hashtable]
+ $WorkerIDs,
+
+ [int]
+ $TargetCount
+ )
+
+ $elapsed = ([int]((Get-Date) - $Start).TotalSeconds)
+ $overallCurrent = 0
+ if ($QueueName) { $overallCurrent = $Workflow.Queues.$QueueName.TotalItemCount }
+ if ($WorkerName) { $overallCurrent = $Workflow.Workers.$WorkerName.CountInputCompleted }
+ if ($QueueObject) { $overallCurrent = $QueueObject.TotalItemCount }
+ if ($PSBoundParameters.Keys -contains 'CurrentCount') { $overallCurrent = $CurrentCount }
+
+ $targetString = ''
+ if ($TargetCount) { $targetString = "/$TargetCount" }
+
+ Write-Progress -Id $WorkflowProgressID -Activity "Workflow: $($Workflow.Name)" -Status "Mode:$Mode | Current:$overallCurrent$($targetString) | Elapsed:$($elapsed)s" -PercentComplete -1
+
+ foreach ($workerObjName in $Workflow.Workers.Keys) {
+ $workerObj = $Workflow.Workers.$workerObjName
+
+ $inQueueName = if ($workerObj.PSObject.Properties.Name -contains 'InQueue') { $workerObj.InQueue } else { $null }
+ $outQueueName = $workerObj.OutQueue
+
+ $inQeue = $null
+ if ($inQueueName) { $inQeue = $Workflow.Queues.($inQueueName) }
+ $outQueue = $null
+ if ($outQueueName) { $outQueue = $Workflow.Queues.($outQueueName) }
+
+ $inTotal = if ($inQeue) { $inQeue.TotalItemCount } else { 0 }
+ $outTotal = if ($outQueue) { $outQueue.TotalItemCount } else { 0 }
+ $outClosed = if ($outQueue) { $outQueue.Closed } else { $false }
+ $current = $workerObj.CountInputCompleted
+
+ Write-Progress -Id $WorkerIDs[$workerObjName].RunnerId -ParentId $WorkflowProgressID -Activity "Runner: $workerObjName" -Status "Progress: $current$($targetString) | Elapsed:$($elapsed)s" -PercentComplete -1
+
+ # Items status (only print non-empty fields)
+ $parts = @("Completed:$current")
+ if ($inQueueName) { $parts += "InQ:$inQueueName total:$inTotal" }
+ if ($outQueueName) { $parts += "OutQ:$outQueueName total:$outTotal closed:$outClosed" }
+ $itemStatus = ($parts -join ' | ')
+ Write-Progress -Id $WorkerIDs[$workerObjName].ItemsId -ParentId $WorkerIDs[$workerObjName].RunnerId -Activity "# of items" -Status $itemStatus -PercentComplete -1
+ }
+ }
+ #endregion Utility Functions
+ }
process {
$resolvedWorkflows = Resolve-PsfRunspaceWorkflow -Name $Name -InputObject $InputObject -Cmdlet $PSCmdlet
$limit = (Get-Date).AddDays(1)
if ($Timeout) { $limit = (Get-Date).Add($Timeout) }
+ # base progress id counter (simple, monotonic)
+ $progressId = 1
+
foreach ($resolvedWorkflow in $resolvedWorkflows) {
+ $start = Get-Date
+
+ if ($ShowProgress) {
+ $workflowProgressID = $progressId
+ $progressId += 1
+ Write-Progress -Id $workflowProgressID -Activity "Workflow: $($resolvedWorkflow.Name)" -Status "Elapsed: 0s" -PercentComplete -1
+
+ $workerIds = @{}
+ foreach ($workerObjName in $resolvedWorkflow.Workers.Keys) {
+ $runnerId = $progressId
+ $itemsId = $progressId + 1
+ $progressId += 2
+ $workerIds[$workerObjName] = @{
+ RunnerId = $runnerId
+ ItemsId = $itemsId
+ }
+ Write-Progress -Id $runnerId -ParentId $workflowProgressID -Activity "Runner: $workerObjName" -Status "Initializing..." -PercentComplete -1
+ Write-Progress -Id $itemsId -ParentId $runnerId -Activity "Nb of items" -Status "Initializing..." -PercentComplete -1
+ }
+ }
+
switch ($PSCmdlet.ParameterSetName) {
'Closed' {
while (-not $resolvedWorkflow.Queues.$Queue.Closed) {
if ($limit -lt (Get-Date)) {
Stop-PSFFunction -String 'Wait-PSFRunspaceWorkflow.Error.Timeout' -StringValues $limit, $resolvedWorkflow.Name -Target $resolvedWorkflow -EnableException $true -Cmdlet $PSCmdlet -Category OperationTimeout
}
+ if ($ShowProgress) {
+ Write-WorkflowProgress -Mode $PSCmdlet.ParameterSetName -Start $start -Workflow $resolvedWorkflow -QueueName $Queue -WorkflowProgressID $workflowProgressID -WorkerIDs $workerIds
+ }
Start-Sleep -Milliseconds 200
}
}
@@ -140,6 +248,9 @@
if ($limit -lt (Get-Date)) {
Stop-PSFFunction -String 'Wait-PSFRunspaceWorkflow.Error.Timeout' -StringValues $limit, $resolvedWorkflow.Name -Target $resolvedWorkflow -EnableException $true -Cmdlet $PSCmdlet -Category OperationTimeout
}
+ if ($ShowProgress) {
+ Write-WorkflowProgress -Mode $PSCmdlet.ParameterSetName -Start $start -Workflow $resolvedWorkflow -QueueObject $queueObject -WorkflowProgressID $workflowProgressID -WorkerIDs $workerIds
+ }
Start-Sleep -Milliseconds 200
}
}
@@ -148,6 +259,9 @@
if ($limit -lt (Get-Date)) {
Stop-PSFFunction -String 'Wait-PSFRunspaceWorkflow.Error.Timeout' -StringValues $limit, $resolvedWorkflow.Name -Target $resolvedWorkflow -EnableException $true -Cmdlet $PSCmdlet -Category OperationTimeout
}
+ if ($ShowProgress) {
+ Write-WorkflowProgress -Mode $PSCmdlet.ParameterSetName -Start $start -Workflow $resolvedWorkflow -QueueName $Queue -WorkflowProgressID $workflowProgressID -WorkerIDs $workerIds -TargetCount $Count
+ }
Start-Sleep -Milliseconds 200
}
}
@@ -156,6 +270,9 @@
if ($limit -lt (Get-Date)) {
Stop-PSFFunction -String 'Wait-PSFRunspaceWorkflow.Error.Timeout' -StringValues $limit, $resolvedWorkflow.Name -Target $resolvedWorkflow -EnableException $true -Cmdlet $PSCmdlet -Category OperationTimeout
}
+ if ($ShowProgress) {
+ Write-WorkflowProgress -Mode $PSCmdlet.ParameterSetName -Start $start -Workflow $resolvedWorkflow -WorkerName $WorkerName -WorkflowProgressID $workflowProgressID -WorkerIDs $workerIds -TargetCount $Count
+ }
Start-Sleep -Milliseconds 200
}
}
@@ -164,6 +281,9 @@
if ($limit -lt (Get-Date)) {
Stop-PSFFunction -String 'Wait-PSFRunspaceWorkflow.Error.Timeout' -StringValues $limit, $resolvedWorkflow.Name -Target $resolvedWorkflow -EnableException $true -Cmdlet $PSCmdlet -Category OperationTimeout
}
+ if ($ShowProgress) {
+ Write-WorkflowProgress -Mode $PSCmdlet.ParameterSetName -Start $start -Workflow $resolvedWorkflow -QueueName $Queue -WorkflowProgressID $workflowProgressID -WorkerIDs $workerIds -TargetCount ($resolvedWorkflow.Queues.$ReferenceQueue.TotalItemCount * $ReferenceMultiplier)
+ }
Start-Sleep -Milliseconds 200
}
}
@@ -172,6 +292,9 @@
if ($limit -lt (Get-Date)) {
Stop-PSFFunction -String 'Wait-PSFRunspaceWorkflow.Error.Timeout' -StringValues $limit, $resolvedWorkflow.Name -Target $resolvedWorkflow -EnableException $true -Cmdlet $PSCmdlet -Category OperationTimeout
}
+ if ($ShowProgress) {
+ Write-WorkflowProgress -Mode $PSCmdlet.ParameterSetName -Start $start -Workflow $resolvedWorkflow -WorkerName $WorkerName -WorkflowProgressID $workflowProgressID -WorkerIDs $workerIds -TargetCount ($resolvedWorkflow.Queues.$ReferenceQueue.TotalItemCount * $ReferenceMultiplier)
+ }
Start-Sleep -Milliseconds 200
}
}
@@ -180,12 +303,24 @@
if ($limit -lt (Get-Date)) {
Stop-PSFFunction -String 'Wait-PSFRunspaceWorkflow.Error.Timeout' -StringValues $limit, $resolvedWorkflow.Name -Target $resolvedWorkflow -EnableException $true -Cmdlet $PSCmdlet -Category OperationTimeout
}
+ if ($ShowProgress) {
+ $sinceLast = (Get-Date) - $resolvedWorkflow.Queues.$Queue.LastUpdate
+ Write-WorkflowProgress -Mode $PSCmdlet.ParameterSetName -Start $start -Workflow $resolvedWorkflow -CurrentCount $sinceLast.TotalSeconds -WorkflowProgressID $workflowProgressID -WorkerIDs $workerIds -TargetCount $QueueTimeout.Value.TotalSeconds
+ }
Start-Sleep -Milliseconds 200
}
}
}
+ if ($ShowProgress) {
+ foreach ($workerObjName in $workerIds.Keys) {
+ Write-Progress -Id $workerIds[$workerObjName].ItemsId -Activity "# of items" -Completed
+ Write-Progress -Id $workerIds[$workerObjName].RunnerId -Activity "Runner" -Completed
+ }
+ Write-Progress -Id $WorkflowProgressID -Activity "Workflow" -Completed
+ }
+
if ($PassThru) { $resolvedWorkflow }
}
}
-}
\ No newline at end of file
+}
diff --git a/PSFramework/functions/tabexpansion/Register-PSFTeppScriptblock.ps1 b/PSFramework/functions/tabexpansion/Register-PSFTeppScriptblock.ps1
index 11aacccf..56a01fe1 100644
--- a/PSFramework/functions/tabexpansion/Register-PSFTeppScriptblock.ps1
+++ b/PSFramework/functions/tabexpansion/Register-PSFTeppScriptblock.ps1
@@ -40,6 +40,10 @@
Whether the scriptblock should be executed in the global context.
This parameter is needed to reliably execute in background runspaces, but means no direct access to module content.
+ .PARAMETER MaxResults
+ The maximum number of results shown to the user.
+ If more completions would be viable, only the first X are shown, as well as a message informaing about the truncation.
+
.PARAMETER MatchAnywhere
Match input against any part of the completion text, not just the beginning.
@@ -52,6 +56,10 @@
.PARAMETER DontSort
Completion results are no longer sorted alphabetically.
+ .PARAMETER DontFilter
+ Do not automatically filter by the words the user typed.
+ This allows for the scriptblock to provide its own, more complex filtering.
+
.PARAMETER AutoTraining
Automatically train the tab completion by caching user inputs.
Requires using Update-PSFTeppCompletion inside of the respective command, to automatically pick up new values.
@@ -89,6 +97,9 @@
[PSFramework.Parameter.TimeSpanParameter]
$CacheDuration = 0,
+
+ [int]
+ $MaxResults,
[switch]
$Global,
@@ -105,6 +116,9 @@
[switch]
$DontSort,
+ [switch]
+ $DontFilter,
+
[switch]
$AutoTraining
)
@@ -114,9 +128,11 @@
[PSFramework.TabExpansion.TabExpansionHost]::RegisterCompletion($Name, $ScriptBlock, $Mode, $CacheDuration, $Global)
$scriptContainer = [PSFramework.TabExpansion.TabExpansionHost]::Scripts[$Name]
if ($PSBoundParameters.Keys -contains 'MatchAnywhere') { $scriptContainer.MatchAnywhere = $MatchAnywhere }
+ if ($PSBoundParameters.Keys -contains 'MaxResults') { $scriptContainer.MaxResults = $MaxResults }
if ($PSBoundParameters.Keys -contains 'FuzzyMatch') { $scriptContainer.MatchAnywhere = $FuzzyMatch }
if ($PSBoundParameters.Keys -contains 'AlwaysQuote') { $scriptContainer.MatchAnywhere = $AlwaysQuote }
- if ($PSBoundParameters.Keys -contains 'DontSort') { $scriptContainer.MatchAnywhere = $DontSort }
+ if ($PSBoundParameters.Keys -contains 'DontSort') { $scriptContainer.DontSort = $DontSort }
+ if ($PSBoundParameters.Keys -contains 'DontFilter') { $scriptContainer.DoNotFilter = $DontFilter }
if ($PSBoundParameters.Keys -contains 'AutoTraining') { $scriptContainer.AutoTraining = $AutoTraining }
}
}
diff --git a/PSFramework/internal/configurations/tabexpansion.ps1 b/PSFramework/internal/configurations/tabexpansion.ps1
index c85fad9b..3ab12fff 100644
--- a/PSFramework/internal/configurations/tabexpansion.ps1
+++ b/PSFramework/internal/configurations/tabexpansion.ps1
@@ -1,4 +1,5 @@
# Settings around the Tab Expansion Experience
Set-PSFConfig -Module PSFramework -Name 'TabExpansion.FuzzyMatch' -Value $false -Initialize -Handler { [PSFramework.TabExpansion.TabExpansionHost]::FuzzyMatch = $args[0] } -Validation 'bool' -Description 'Whether to match tab completions with Fuzzy-Matching by default.'
Set-PSFConfig -Module PSFramework -Name 'TabExpansion.AlwaysQuote' -Value $false -Initialize -Handler { [PSFramework.TabExpansion.TabExpansionHost]::AlwaysQuote = $args[0] } -Validation 'bool' -Description 'Wrap all completion results into quotes, whitespace or not.'
-Set-PSFConfig -Module PSFramework -Name 'TabExpansion.MatchAnywhere' -Value $false -Initialize -Handler { [PSFramework.TabExpansion.TabExpansionHost]::MatchAnywhere = $args[0] } -Validation 'bool' -Description 'Whether to match tab completions with Fuzzy-Matching by default.'
\ No newline at end of file
+Set-PSFConfig -Module PSFramework -Name 'TabExpansion.MatchAnywhere' -Value $false -Initialize -Handler { [PSFramework.TabExpansion.TabExpansionHost]::MatchAnywhere = $args[0] } -Validation 'bool' -Description 'Whether to match tab completions with Fuzzy-Matching by default.'
+Set-PSFConfig -Module PSFramework -Name 'TabExpansion.MaxResults' -Value 0 -Initialize -Handler { [PSFramework.TabExpansion.TabExpansionHost]::MaxResults = $args[0] } -Validation integerpositive -Description 'The maximum number of results show for tab expansion. This is the global setting for ALL completions, specific setting defined via Register-PSFTeppScriptblock take precedence.'
\ No newline at end of file
diff --git a/PSFramework/internal/filter/environment.filter.ps1 b/PSFramework/internal/filter/environment.filter.ps1
index 2c405363..134709bb 100644
--- a/PSFramework/internal/filter/environment.filter.ps1
+++ b/PSFramework/internal/filter/environment.filter.ps1
@@ -52,7 +52,7 @@ $null = New-PSFFilterConditionSet -Module PSFramework -Name Environment -Version
#region Elevation
New-PSFFilterCondition @paramCon -Name Elevated -ScriptBlock {
if ($PSVersionTable.PSVersion.Major -ge 6 -and $global:IsLinux) { return $true }
- if ($PSVersionTable.PSVersion.Major -ge 6 -and $global:IsLinux) { return $true }
+ if ($PSVersionTable.PSVersion.Major -ge 6 -and $global:IsMacOS) { return $true }
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal $identity
diff --git a/PSFramework/internal/functions/configuration/Read-PsfConfigEnvironment.ps1 b/PSFramework/internal/functions/configuration/Read-PsfConfigEnvironment.ps1
index e46a21ed..1e9374e8 100644
--- a/PSFramework/internal/functions/configuration/Read-PsfConfigEnvironment.ps1
+++ b/PSFramework/internal/functions/configuration/Read-PsfConfigEnvironment.ps1
@@ -1,5 +1,5 @@
function Read-PsfConfigEnvironment {
-<#
+ <#
.SYNOPSIS
Reads configuration settings from environment variables.
@@ -33,6 +33,7 @@
begin {
function ConvertFrom-EnvironmentSetting {
+ [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingEmptyCatchBlock", "")]
[CmdletBinding()]
param (
[Parameter(ValueFromPipelineByPropertyName = $true)]
@@ -56,7 +57,7 @@
try {
[pscustomobject]@{
FullName = $Name.SubString(($Prefix.Length + 1))
- Value = [PSFramework.Configuration.ConfigurationHost]::ConvertFromPersistedValue($Value)
+ Value = [PSFramework.Configuration.ConfigurationHost]::ConvertFromPersistedValue($Value)
}
}
catch {
@@ -82,14 +83,26 @@
if ([double]::TryParse($Value, 'Any', [System.Globalization.NumberFormatInfo]::InvariantInfo, [ref]$tempVal)) {
return [PSCustomObject]@{ FullName = $fullName; Value = $tempVal }
}
- $tempVal = $null
- if ([datetime]::TryParse($Value, [System.Globalization.DateTimeFormatInfo]::InvariantInfo, 'AssumeUniversal', [ref]$tempVal)) {
+ $tempVal = Get-Date
+ if ([datetime]::TryParse($Value, [System.Globalization.DateTimeFormatInfo]::InvariantInfo, [System.Globalization.DateTimeStyles]::AssumeUniversal, [ref]$tempVal)) {
return [PSCustomObject]@{ FullName = $fullName; Value = $tempVal }
}
- if ($Value -match "^.|*") {
- return [PSCustomObject]@{ FullName = $fullName; Value = $Value.SubString(2).Split($Value.Substring(0, 1)) }
+ if ($Value -match "^.\|.+") {
+ $values = $Value.SubString(2).Split($Value.Substring(0, 1))
+ $valueObjects = foreach ($value in $values) { ConvertFrom-EnvironmentSetting -Name "PSF_Whatever" -Value $value -Simple $true -Prefix 'PSF' }
+ return [PSCustomObject]@{ FullName = $fullName; Value = $valueObjects.Value }
+ }
+ if ($Value -match '^\S+:') {
+ try {
+ [pscustomobject]@{
+ FullName = $fullName
+ Value = [PSFramework.Configuration.ConfigurationHost]::ConvertFromPersistedValue($Value)
+ }
+ return
+ }
+ catch { <# If it's a legal export, that's fine, but don't count on it #> }
}
- return [PSCustomObject]@{ FullName = $fullName; Value = $Value }
+ return [PSCustomObject]@{ FullName = $fullName; Value = $valueObjects.Value }
}
#endregion Simple Mode
}
diff --git a/PSFramework/internal/functions/runspace/ConvertFrom-PsfUsingStatement.ps1 b/PSFramework/internal/functions/runspace/ConvertFrom-PsfUsingStatement.ps1
new file mode 100644
index 00000000..ab2b8648
--- /dev/null
+++ b/PSFramework/internal/functions/runspace/ConvertFrom-PsfUsingStatement.ps1
@@ -0,0 +1,45 @@
+function ConvertFrom-PsfUsingStatement {
+ <#
+ .SYNOPSIS
+ Removes all using statements from a scriptblock.
+
+ .DESCRIPTION
+ Removes all using statements from a scriptblock.
+ Will return an object with two pieces of information:
+
+ + Code: The original scriptblock, just with all $using:-statements replaced with simple variable names.
+ + Variables: A deduplicated list of all variables used by the scriptblock, that used to be covered under a $using:-Statement
+
+ .PARAMETER ScriptBlock
+ The scriptblock to remove $using:-statements from.
+
+ .EXAMPLE
+ PS C:\> ConvertFrom-PsfUsingStatement -ScriptBlock $code
+
+ Removes all $using:-statements from $code
+ #>
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory = $true)]
+ [ScriptBlock]
+ $ScriptBlock
+ )
+ process {
+ $allUsings = $ScriptBlock.Ast.FindAll({ $args[0] -is [System.Management.Automation.Language.UsingExpressionAst] }, $true)
+ $varNames = $allUsings.SubExpression.VariablePath.UserPath | Microsoft.PowerShell.Utility\Sort-Object -Unique
+
+ $scriptBlockText = $ScriptBlock.Ast.Extent.Text
+ $offset = $ScriptBlock.Ast.Extent.StartOffset
+ foreach ($usingInstance in $allUsings | Microsoft.PowerShell.Utility\Sort-Object { $_.Extent.StartOffset } -Descending) {
+ $scriptBlockText = $scriptBlockText.SubString(0, ($usingInstance.Extent.StartOffset - $offset)) + "`${$($usingInstance.SubExpression.VariablePath.UserPath)}" + $scriptBlockText.Substring(($usingInstance.Extent.EndOffset - $offset))
+ }
+
+ $code = [PsfScriptBlock]::new([scriptblock]::Create($scriptBlockText), $true) -as [scriptblock]
+ [PSFramework.Utility.UtilityHost]::SetPrivateProperty("LanguageMode", $code, ([PsfScriptBlock]$ScriptBlock).LanguageMode)
+
+ [PSCustomObject]@{
+ Variables = $varNames
+ Code = $code
+ }
+ }
+}
\ No newline at end of file
diff --git a/PSFramework/internal/scripts/teppSimpleCompleter.ps1 b/PSFramework/internal/scripts/teppSimpleCompleter.ps1
index 0a23c385..0472cc2f 100644
--- a/PSFramework/internal/scripts/teppSimpleCompleter.ps1
+++ b/PSFramework/internal/scripts/teppSimpleCompleter.ps1
@@ -145,16 +145,22 @@
}
$alwaysQuote = $scriptContainer.AlwaysQuote
$sortParam = @{ Property = 'ListItemText' }
- if ($scriptContainer.DontSort) { $sortParam = @{ Property = { 1 }}}
+ if ($scriptContainer.DontSort) { $sortParam = @{ Property = { 1 } } }
if (-not $scriptContainer.ShouldExecute) {
if ($scriptContainer.Trained.Count -gt 0) {
$allItems = @($scriptContainer.LastCompletion) + ($scriptContainer.Trained | ConvertTo-TeppCompletionEntry)
}
else { $allItems = $scriptContainer.LastCompletion }
- foreach ($item in ($scriptContainer.LastCompletion | Where-Object Text -match $scriptContainer.GetPattern($wordToComplete) | Sort-Object @sortParam)) {
+ $allResults = foreach ($item in ($scriptContainer.LastCompletion | Where-Object Text -Match $scriptContainer.GetPattern($wordToComplete) | Sort-Object @sortParam)) {
New-PSFTeppCompletionResult -CompletionText $item.Text -ToolTip $item.ToolTip -ListItemText $item.ListItemText -AlwaysQuote:$alwaysQuote
}
+
+ if ($scriptContainer.MaxResults -gt 0 -and @($allResults).Count -gt $scriptContainer.MaxResults) {
+ @($allResults)[0..($scriptContainer.MaxResults - 1)]
+ New-Object System.Management.Automation.CompletionResult("", "... showing the first $($scriptContainer.MaxResults) / $(@($allResults).Count) results", "ParameterValue", 'Type more until fewer viable results are returned')
+ }
+ else { $allResults }
return
}
@@ -172,9 +178,14 @@
else { $allItems = $items }
- foreach ($item in ($allItems | Where-Object Text -match $scriptContainer.GetPattern($wordToComplete) | Sort-Object @sortParam)) {
+ $allResults = foreach ($item in ($allItems | Where-Object Text -Match $scriptContainer.GetPattern($wordToComplete) | Sort-Object @sortParam)) {
New-PSFTeppCompletionResult -CompletionText $item.Text -ToolTip $item.ToolTip -ListItemText $item.ListItemText -AlwaysQuote:$alwaysQuote
}
+ if ($scriptContainer.MaxResults -gt 0 -and @($allResults).Count -gt $scriptContainer.MaxResults) {
+ @($allResults)[0..($scriptContainer.MaxResults - 1)]
+ New-Object System.Management.Automation.CompletionResult(" ", "... showing the first $($scriptContainer.MaxResults) / $(@($allResults).Count) results", "ParameterValue", 'Type more until fewer viable results are returned')
+ }
+ else { $allResults }
$scriptContainer.LastDuration = (Get-Date) - $start
if ($items) {
diff --git a/PSFramework/tests/functions/tabexpansion/input.Tests.ps1 b/PSFramework/tests/functions/tabexpansion/input.Tests.ps1
index 1e1eb214..fbe28688 100644
--- a/PSFramework/tests/functions/tabexpansion/input.Tests.ps1
+++ b/PSFramework/tests/functions/tabexpansion/input.Tests.ps1
@@ -19,7 +19,8 @@
}
It 'can complete input from Get-ChildItem' {
(Complete -Expression 'Get-ChildItem | Select-PSFObject ').CompletionText | Should -Match '^Attributes$|^BaseName$|^CreationTime$|^CreationTimeUtc$|^Directory$|^DirectoryName$|^Exists$|^Extension$|^FullName$|^IsReadOnly$|^LastAccessTime$|^LastAccessTimeUtc$|^LastWriteTime$|^LastWriteTimeUtc$|^Length$|^Name$|^Parent$|^PSChildName$|^PSDrive$|^PSIsContainer$|^PSParentPath$|^PSPath$|^PSProvider$|^Root$|^VersionInfo$|^LinkTarget$|^UnixFileMode$'
- (Complete -Expression 'Get-ChildItem | Select-PSFObject ').Count | Should -Be 25
+ if ($PSVersionTable.PSVersion.Major -le 5) { (Complete -Expression 'Get-ChildItem | Select-PSFObject ').Count | Should -Be 25 }
+ else { (Complete -Expression 'Get-ChildItem | Select-PSFObject ').Count | Should -Be 27 }
}
<#
diff --git a/PSFramework/xml/PSFramework.Format.ps1xml b/PSFramework/xml/PSFramework.Format.ps1xml
index 7bfe5518..106ccc9e 100644
--- a/PSFramework/xml/PSFramework.Format.ps1xml
+++ b/PSFramework/xml/PSFramework.Format.ps1xml
@@ -714,6 +714,80 @@
+
+
+ PSFramework.Runspace.RunspaceResult
+
+ PSFramework.Runspace.RunspaceResult
+
+
+
+
+
+
+
+
+
+
+
+
+ InputObject
+
+
+ Output
+
+
+ Errors
+
+
+
+
+
+
+
+
+
+ PSFramework.Runspace.RunspaceWrapper
+
+ PSFramework.Runspace.RunspaceWrapper
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+
+
+ IsRunning
+
+
+ ThrottleLimit
+
+
+ CountTotal
+
+
+ CountPending
+
+
+ CountCompleted
+
+
+
+
+
+
+
PSFramework.TabExpansion.ScriptContainer
diff --git a/library/PSFramework/Commands/InvokePSFProtectedCommand.cs b/library/PSFramework/Commands/InvokePSFProtectedCommand.cs
index 24e47a75..434df3dd 100644
--- a/library/PSFramework/Commands/InvokePSFProtectedCommand.cs
+++ b/library/PSFramework/Commands/InvokePSFProtectedCommand.cs
@@ -166,44 +166,9 @@ private string _ErrorMessage
///
private string _ErrorScript = @"
param (
- $__PSFramework__Message,
-
- $__PSFramework__Exception,
-
- $__PSFramework__Target,
-
- $__PSFramework__Continue,
-
- $__PSFramework__ContinueLabel,
-
- $__PSFramework__FunctionName,
-
- $__PSFramework__ModuleName,
-
- $__PSFramework__File,
-
- $__PSFramework__Line,
-
- $__PSFramework__Cmdlet,
-
- $__PSFramework__EnableException
+ $__PSFramework__Parameters
)
-
-$paramStopPSFFunction = @{
- Message = $__PSFramework__Message
- Exception = $__PSFramework__Exception
- Target = $__PSFramework__Target
- Continue = $__PSFramework__Continue
- FunctionName = $__PSFramework__FunctionName
- ModuleName = $__PSFramework__ModuleName
- File = $__PSFramework__File
- Line = $__PSFramework__Line
- Cmdlet = $__PSFramework__Cmdlet
- EnableException = $__PSFramework__EnableException
- StepsUpward = 1
-}
-if ($__PSFramework__ContinueLabel) { $paramStopPSFFunction['ContinueLabel'] = $__PSFramework__ContinueLabel }
-Stop-PSFFunction @paramStopPSFFunction
+Stop-PSFFunction @__PSFramework__Parameters
return
";
#endregion Private Fields
@@ -289,7 +254,7 @@ protected override void ProcessRecord()
Thread.Sleep(nextWait);
nextWait = (int)((double)nextWait * RetryWaitEscalation);
}
-
+
}
#endregion Cmdlet Implementation
@@ -313,14 +278,29 @@ private void Terminate(Exception error)
)
);
}
-
+
if (Continue)
DoContinue(ContinueLabel);
return;
}
+ Hashtable parameters = new Hashtable(StringComparer.OrdinalIgnoreCase);
+ parameters["Message"] = _ErrorMessage;
+ parameters["Exception"] = error;
+ parameters["Target"] = Target;
+ parameters["Continue"] = Continue;
+ if (!String.IsNullOrEmpty(ContinueLabel))
+ parameters["ContinueLabel"] = ContinueLabel;
+ parameters["FunctionName"] = _Caller.CallerFunction;
+ parameters["ModuleName"] = _Caller.CallerModule;
+ parameters["File"] = _Caller.CallerFile;
+ parameters["Line"] = _Caller.CallerLine;
+ parameters["Cmdlet"] = PSCmdlet;
+ parameters["EnableException"] = EnableException;
+ parameters["StepsUpward"] = 1;
+
ScriptBlock errorBlock = ScriptBlock.Create(_ErrorScript);
- object[] arguments = new object[] { _ErrorMessage, error, Target, Continue, ContinueLabel, _Caller.CallerFunction, _Caller.CallerModule, _Caller.CallerFile, _Caller.CallerLine, PSCmdlet, EnableException };
+ object[] arguments = new object[] { parameters };
PSCmdlet.InvokeCommand.InvokeScript(false, errorBlock, null, arguments);
}
@@ -332,7 +312,8 @@ private void ErrorEventAction(Exception error)
object errorRecord = error;
if (error is RuntimeException)
errorRecord = ((RuntimeException)error).ErrorRecord;
- try {
+ try
+ {
WriteMessage(Localization.LocalizationHost.Read("PSFramework.FlowControl.Invoke-PSFProtectedCommand.ErrorEvent", new object[] { _Message, Target }), Level, _Caller.CallerFunction, _Caller.CallerModule, _Caller.CallerFile, _Caller.CallerLine, Tag, Target);
table["Result"] = PSCmdlet.InvokeCommand.InvokeScript(false, ErrorEvent, null, errorRecord);
WriteMessage(Localization.LocalizationHost.Read("PSFramework.FlowControl.Invoke-PSFProtectedCommand.ErrorEvent.Success", new object[] { _Message, Target }), Level, _Caller.CallerFunction, _Caller.CallerModule, _Caller.CallerFile, _Caller.CallerLine, Tag, Target, table);
@@ -344,4 +325,4 @@ private void ErrorEventAction(Exception error)
}
}
}
-}
+}
\ No newline at end of file
diff --git a/library/PSFramework/Data/Converters/FileSystemInfoConverter.cs b/library/PSFramework/Data/Converters/FileSystemInfoConverter.cs
index 67fb674d..3a74aac3 100644
--- a/library/PSFramework/Data/Converters/FileSystemInfoConverter.cs
+++ b/library/PSFramework/Data/Converters/FileSystemInfoConverter.cs
@@ -54,7 +54,7 @@ public string Convert(object Value, object[] Parents, int Depth, Psd1Converter C
catch { }
- if (property.Name == "Root" || property.Name == "Parent" || property.Name == "Directory")
+ if (property.Name == "Root" || property.Name == "Parent" || property.Name == "Directory" || property.Name == "BaseName")
propValue = $"{propValue}";
sb.AppendLine($"{newIndent}{CodeGeneration.EscapeSingleQuotedStringContent(LanguagePrimitives.ConvertTo(property.Name))} = {DataHost.Convert(propValue, newParents, Depth + 1, Converter)}");
diff --git a/library/PSFramework/PSFramework.csproj b/library/PSFramework/PSFramework.csproj
index 7deebd98..b356959b 100644
--- a/library/PSFramework/PSFramework.csproj
+++ b/library/PSFramework/PSFramework.csproj
@@ -218,7 +218,10 @@
+
+
+
diff --git a/library/PSFramework/Runspace/RunspaceResult.cs b/library/PSFramework/Runspace/RunspaceResult.cs
new file mode 100644
index 00000000..db8a3fcb
--- /dev/null
+++ b/library/PSFramework/Runspace/RunspaceResult.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Management.Automation.Runspaces;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PSFramework.Runspace
+{
+ ///
+ /// The result of a runspace task
+ ///
+ public class RunspaceResult
+ {
+ ///
+ /// The object that triggered the task
+ ///
+ public object InputObject;
+ ///
+ /// All output
+ ///
+ public List Output = new List();
+ ///
+ /// All Information Messages
+ ///
+ public List