Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ PSFramework/PSFramework.psproj
TestResults/*

# ignore the publishing Directory
publish/*
publish/*

# Ignore local test files (should be migrated to pester tests anyway)
localTests/*
3 changes: 2 additions & 1 deletion PSFramework/PSFramework.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
RootModule = 'PSFramework.psm1'

# Version number of this module.
ModuleVersion = '1.14.441'
ModuleVersion = '1.14.449'

# ID used to uniquely identify this module
GUID = '8028b914-132b-431f-baa9-94a6952f21ff'
Expand Down Expand Up @@ -144,6 +144,7 @@
'Remove-PSFTempItem'
'Remove-PSFTeppCompletion'
'Reset-PSFConfig'
'Reset-PSFRsAgentInactivity'
'Resolve-PSFDefaultParameterValue'
'Resolve-PSFItem'
'Resolve-PSFPath'
Expand Down
Binary file modified PSFramework/bin/PSFramework.dll
Binary file not shown.
Binary file modified PSFramework/bin/PSFramework.pdb
Binary file not shown.
182 changes: 178 additions & 4 deletions PSFramework/bin/PSFramework.xml

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions PSFramework/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# CHANGELOG

## 1.14.449 (2026-06-12)

- New: Configuration PSFramework.Message.LogErrorStack - When calling Write-PSFMessage and also specifying an ErrorRecord, should the ErrorRecord location of the error be used, rather than from where Write-PSFMessage was called?
- New: Reset-PSFRsAgentInactivity - Signals the current Runspace Workflow Worker Agent is active.
- Upd: Write-PSFMessage - added parameter `-ErrorStack` - logs the stacktrace of the error record provided, rather than its own call.
- Upd: Add-PSFRunspaceWorker - added new generation of runspace workers, with a c#-based orchestration infrastructure,
- Upd: Add-PSFRunspaceWorker - added option to retry failed input (including an optional condition logic on when to retry)
- Upd: Add-PSFRunspaceWorker - added option to include per-item timeout (total time or idle time)
- Upd: Set-PSFDynamicContentObject - add `-Cache` parameter, generating a DCO implementing a PSFramework cache (same as returned by New-PSFCache)
- Fix: New-PSFCache - the cache object does not correctly remove objects

## 1.14.441 (2026-06-03)

- Fix: New-PSFCache - timer only removes one expired item at a time
Expand Down
311 changes: 311 additions & 0 deletions PSFramework/en-us/PSFramework.dll-Help.xml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions PSFramework/en-us/stringsRunspaces.psd1
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@{
'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
'Add-PSFRunspaceWorker.Error.VersionTooLow' = 'Invalid Runspace Worker Version specified! Version specified: {0} | Minimum Version required for the selected parameters: {1}' # $WorkerVersion, $useVersion

'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
Expand Down
80 changes: 80 additions & 0 deletions PSFramework/functions/runspace/Add-PSFRunspaceWorker.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@

In the wider flow of a Runspace Workflow, one Worker's Output Queue is alse another Worker's Input Queue.
Thus we create a chain of workers from original input to finished output, each step individually with as many runspaces as needed.

Note: Worker Versions
There are different runtime versions for the Runspace Workers.
They affect features available, performance, and - possibly - bugs.
Older (lower) versions are more tested, but some features are only available with later versions.
If you encounter an issue with any of the later versions, that works on an older one, please file a bug report and provide as much information as possible.

Versions and their features:
1: Baseline
2: Added RetryCount, RetryCondition, Timeout, TimeoutType parameters, as well as support for overriding settings per-item with New-PSFRsWorkItem

.PARAMETER Name
Name of the worker.
Expand Down Expand Up @@ -104,6 +114,33 @@
.PARAMETER SessionState
A fully prepared session state object to use when creating the worker runspaces.
Be aware that if your session state does not contain basic language tools, the background runspace will likely fail.

.PARAMETER Timeout
How long each individual item may run before timing out.
Note: This parameter forces a V2 worker or later (see description).

.PARAMETER TimeoutType
What kind of timeout processing we perform.
- Start: Time from the start of the current item.
- Idle: Time since last activity
Last Activity is measured by the last Write-PSFMessage or Reset-PSFRsAgentInactivity call.
Note: This parameter forces a V2 worker or later (see description).

.PARAMETER RetryCount
How many times to try again if processing an object fails.
Note: This parameter forces a V2 worker or later (see description).

.PARAMETER RetryCondition
If an object fails and retries are configured, only retries are attempted for cases where this condition is true.
This scriptblock has access to two variables:
- $_: The Error that happened
- $this: The object currently being processed
It is executed in the context of the runspace where the issue happend (so modules and commands are available, but the direct scope of the execution code is not.)
Note: This parameter forces a V2 worker or later (see description).

.PARAMETER WorkerVersion
What version of worker to create.
Later versions offer more features, older versions more stability.

.PARAMETER WorkflowName
Name of the Runspace Workflow this worker belongs to.
Expand Down Expand Up @@ -191,6 +228,22 @@
[initialsessionstate]
$SessionState,

[PSFTimeSpanParameter]
$Timeout,

[PSFramework.Runspace.RSTimeout]
$TimeoutType = 'Start',

[int]
$RetryCount,

[PsfScriptBlock]
$RetryCondition,

[ValidateSet(1,2)]
[int]
$WorkerVersion,

[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[PsfArgumentCompleter('PSFramework-runspace-workflow-name')]
[string[]]
Expand All @@ -202,6 +255,26 @@
)

begin {
$versionMap = @{
2 = @('Timeout', 'RetryCount', 'RetryCondition')
}
$useVersion = 1

foreach ($version in $versionMap.Keys | Sort-Object) {
foreach ($parameterName in $versionMap[$version]) {
if ($PSBoundParameters.ContainsKey($parameterName)) {
$useVersion = $version
}
}
}

if ($WorkerVersion) {
if ($WorkerVersion -lt $useVersion) {
Stop-PSFFunction -String 'Add-PSFRunspaceWorker.Error.VersionTooLow' -StringValues $WorkerVersion, $useVersion -EnableException $true -Cmdlet $PSCmdlet -Category InvalidArgument
}
$useVersion = $WorkerVersion
}

$functionsResolved = @{ }

if (-not $Functions) { return }
Expand All @@ -227,12 +300,19 @@

foreach ($resolvedWorkflow in $resolvedWorkflows) {
$worker = $resolvedWorkflow.AddWorker($Name, $InQueue, $OutQueue, $ScriptBlock, $Count)
$worker.WorkerVersion = $useVersion

if ($Begin) { $worker.Begin = $Begin }
if ($End) { $worker.End = $End }
if ($MaxItems) { $worker.MaxItems = $MaxItems }
if ($CloseOutQueue) { $worker.CloseOutQueue = $true }
if ($QueuesToClose) { $worker.QueuesToClose = $QueuesToClose }
if ($Timeout) {
$worker.Timeout = $Timeout
$worker.TimeoutType = $TimeoutType
}
if ($RetryCount) { $worker.RetryCount = $RetryCount }
if ($RetryCondition) { $worker.RetryCondition = $RetryCondition }

if ($SessionState) { $worker.SessionState = $SessionState }
foreach ($module in $Modules) { $worker.Modules.Add($module) }
Expand Down
24 changes: 24 additions & 0 deletions PSFramework/functions/runspace/Reset-PSFRsAgentInactivity.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
function Reset-PSFRsAgentInactivity {
<#
.SYNOPSIS
Signals the current Runspace Workflow Worker Agent(tm) is active.

.DESCRIPTION
Signals the current Runspace Workflow Worker Agent is active.
When called from within the code of a Runspace Workflow - specifically, within the code operated by a Generation 2+ Worker - it signals to the Worker-Agent that the current workload is being processed and is not hanging.

This is used by Generation 2+ Workers when they are configure for timeout type "Idle", where a timeout is performed based on how long the script code has not shown a sign of activity.
An alternative way of showing activity is using the Write-PSFMessage command.

.EXAMPLE
PS C:\> Reset-PSFRsAgentInactivity

Signals the current Runspace Workflow Worker Agent is active.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
[CmdletBinding()]
param ()
process {
[PSFramework.Runspace.RunspaceHost]::SignalActive()
}
}
11 changes: 11 additions & 0 deletions PSFramework/functions/runspace/Set-PSFDynamicContentObject.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
Set the object to be a threadsafe dictionary.
Safe to use in multiple runspaces in parallel.
Will not apply changes if the current value is already such an object.

.PARAMETER Cache
Set the object to be a threadsafe cache.
Safe to use in multiple runspaces in parallel.
Will not apply changes if the current value is already such an object.

.PARAMETER PassThru
Has the command returning the object just set.
Expand Down Expand Up @@ -90,6 +95,10 @@
[switch]
$Dictionary,

[Parameter(Mandatory = $true, ParameterSetName = 'Cache')]
[switch]
$Cache,

[switch]
$PassThru,

Expand All @@ -105,6 +114,7 @@
elseif ($Stack) { [PSFramework.Utility.DynamicContentObject]::Set($item, $Value, 'Stack') }
elseif ($List) { [PSFramework.Utility.DynamicContentObject]::Set($item, $Value, 'List') }
elseif ($Dictionary) { [PSFramework.Utility.DynamicContentObject]::Set($item, $Value, 'Dictionary') }
elseif ($Cache) { [PSFramework.Utility.DynamicContentObject]::Set($item, $Value, 'Cache') }
else { [PSFramework.Utility.DynamicContentObject]::Set($item, $Value, 'Common') }

if ($PassThru) { [PSFramework.Utility.DynamicContentObject]::Get($item) }
Expand All @@ -117,6 +127,7 @@
if ($Stack) { $item.ConcurrentStack($Reset) }
if ($List) { $item.ConcurrentList($Reset) }
if ($Dictionary) { $item.ConcurrentDictionary($Reset) }
if ($Cache) { $item.ConcurrentCache($Reset) }

if ($PassThru) { $item }
}
Expand Down
1 change: 1 addition & 0 deletions PSFramework/internal/configurations/message.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

Set-PSFConfig -Module PSFramework -Name 'Message.LogErrorStack' -Value $false -Initialize -Validation "bool" -Handler { [PSFramework.Message.MessageHost]::LogErrorStack = $_ } -Description "When calling Write-PSFMessage and also specifying an ErrorRecord, should the ErrorRecord location of the error be used, rather than from where Write-PSFMessage was called?"
Set-PSFConfig -Module PSFramework -Name 'Message.Info.Minimum' -Value 1 -Initialize -Validation "integer0to9" -Handler { [PSFramework.Message.MessageHost]::MinimumInformation = $_ } -Description "The minimum required message level for messages that will be shown to the user."
Set-PSFConfig -Module PSFramework -Name 'Message.Info.Maximum' -Value 3 -Initialize -Validation "integer0to9" -Handler { [PSFramework.Message.MessageHost]::MaximumInformation = $_ } -Description "The maximum message level to still display to the user directly."
Set-PSFConfig -Module PSFramework -Name 'Message.Verbose.Minimum' -Value 4 -Initialize -Validation "integer0to9" -Handler { [PSFramework.Message.MessageHost]::MinimumVerbose = $_ } -Description "The minimum required message level where verbose information is written."
Expand Down
30 changes: 28 additions & 2 deletions PSFramework/internal/scripts/runspaceWorkerCode.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@
Start-Sleep -Milliseconds 250
continue
}
if ($inputData -is [PSFramework.Runspace.RSWorkItem]) {
$inputData = $inputData.Item
}

try {
$results = $__PSF_ScriptBlock.InvokeGlobal($inputData)
Expand Down Expand Up @@ -87,9 +90,32 @@

[PSFramework.Runspace.RSWorker]::WorkerBeginCode = {
param ($Code)
& $Code
$ErrorActionPreference = 'Stop'
try { $Code.InvokeGlobal() }
catch { throw $_ }
}
[PSFramework.Runspace.RSWorker]::WorkerProcessCode = {
param ($Code, $Data)
$Code.InvokeGlobal($Data)
$ErrorActionPreference = 'Stop'
try {
$results = $Code.InvokeGlobal($Data)
}
catch { throw $_ }
foreach ($result in $results) {
if ($__PSF_Worker.NoOutput) { break }
$__PSF_Workflow.Queues.$($__PSF_Worker.OutQueue).Enqueue($result)
$__PSF_Worker.IncrementOutput()
}
}
[PSFramework.Runspace.RSWorker]::WorkerRetryCode = {
param ($Code, $ErrorRecord, $Item)
$Code.InvokeEx(
$false, # Do not dotsource
$ErrorRecord, # $_
$null, # $input
$Item.Item, # $this
$true, # Do import into a context (the local one by default)
$true, # Do use the global context for the import, not the local one
$null # $args
)
}
Loading
Loading