GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Give each runspace in a `RunspacePool` its own `InitialSessionState` by ryanriesMSFT · Pull Request #28286 · PowerShell/PowerShell

Conversation

@ryanriesMSFT

@ryanriesMSFT Joseph "Ryan" Ries (ryanriesMSFT) commented Oct 29, 2026

Copy link
Copy Markdown

PR Summary

Give each runspace in a local RunspacePool its own copy of the pool's InitialSessionState, instead of sharing a single instance across all runspaces in the pool.

Sharing the instance can make a pooled runspace silently skip a module's type and format data (TypesToProcess / FormatsToProcess). ETS members are then missing in that runspace. For example, the enum script properties that CDXML modules generate are missing, so (Get-NetConnectionProfile).NetworkCategory intermittently returns [uint32] 2 instead of DomainAuthenticated. The same happens with Get-Disk, Get-Partition, and other cmdlets whose output relies on module type data, whenever they run in a pooled runspace.

Changes:

  • RunspacePoolInternal.CreateRunspace() now creates runspaces with RunspaceFactory.CreateRunspace(host, _initialSessionState), which clones the InitialSessionState just like a standalone runspace.
  • The no-clone path is now unused, so it is removed: RunspaceFactory.CreateRunspaceFromSessionStateNoClone, the LocalRunspace(..., bool suppressClone) constructor, and the RunspaceBase(..., bool suppressClone) constructor. All three are internal.
  • A new CI test in RunspacePool.Tests.ps1 fails without this change and passes with it.

PR Context

RunspacePoolInternal.CreateRunspace() created every runspace with CreateRunspaceFromSessionStateNoClone, so all runspaces in a pool shared one InitialSessionState object. That object behaves as mutable, per-runspace state:

  • When a module is imported, InitialSessionState.Bind(..., updateOnly: true) → UpdateTypes / UpdateFormats loads the module's type and format files into the importing runspace's TypeTable and format database. It also appends them to context.InitialSessionState.Types / .Formats, which is the shared instance.
  • ModuleCmdletBase.LoadModuleManifest decides whether a manifest's TypesToProcess / FormatsToProcess file is already loaded by looking it up in Context.InitialSessionState.Types / .Formats. If the file is listed, the binding is skipped.

Each runspace still has its own TypeTable and format database, which are populated from the InitialSessionState when the runspace is opened. So a failure follows when both of these happen:

  1. Runspace B is opened, for example because runspace A was busy when another command was queued.
  2. Runspace A imports a module, which records the module's type and format files in the shared InitialSessionState.

When B then imports the same module, the files are already listed, so B never loads them. Member lookup falls back to the CIM/.NET adapter and returns the raw value. No error or warning is written.

The problem is intermittent and only shows up in some hosts because it requires all of the following:

  • a pool that has more than one runspace, as created by scripts and frameworks that parallelize work with RunspaceFactory.CreateRunspacePool(min, max, ...), by custom hosts, or (by code inspection) by remoting endpoints when the client opens a pool with more than one runspace;
  • a specific order of runspace creation and module import, which depends on load and timing;
  • runspaces that live long enough. The pool's idle cleanup timer periodically destroys runspaces, so affected runspaces come and go.

Code paths that create standalone runspaces already clone the InitialSessionState and are not affected. This includes RunspaceFactory.CreateRunspace(...), the console host, and ForEach-Object -Parallel.

Not sharing the instance also removes other cross-runspace effects of the shared object:

  • One runspace could enumerate Types / Formats (UpdateTypes uses Parallel.ForEach) while a sibling appends to them during Import-Module.
  • Remove-Module, Update-TypeData, and Remove-TypeData in one runspace clear and rebuild the shared lists.
  • For a pool created from an InitialSessionState with a shared TypeTable entry, only the first runspace actually shared the TypeTable, because UpdateTypes replaces that entry in the shared instance.

Cost: one InitialSessionState.Clone() per runspace that the pool creates. For InitialSessionState.CreateDefault() that is about 1 ms, compared with about 50 ms to open a runspace.

Windows repro. Without the fix, the second line typically shows 0 (UInt32) or the corresponding integer:

$pool = [runspacefactory]::CreateRunspacePool(1, 2)
$pool.Open()
$script = {
    param($delay)
    Start-Sleep -Seconds $delay          # the first command imports NetConnection at ~2s, the second at ~4s
    $p = Get-NetConnectionProfile | Select-Object -First 1
    Start-Sleep -Seconds (6 - $delay)    # keep the first runspace busy so the pool opens a second one up front
    '{0}: {1} ({2})' -f [runspace]::DefaultRunspace.Name, $p.NetworkCategory, $p.NetworkCategory.GetType().Name
}
$invocations = 2, 4 | ForEach-Object {
    $ps = [powershell]::Create().AddScript($script).AddArgument($_)
    $ps.RunspacePool = $pool
    [pscustomobject]@{ PowerShell = $ps; Handle = $ps.BeginInvoke() }
}
$invocations | ForEach-Object { $_.PowerShell.EndInvoke($_.Handle) }
$pool.Dispose()

The new cross-platform test reproduces the same ordering deterministically with a self-contained module that has a types file and a format file. It uses events instead of sleeps. Results:

  • Built from this branch: the test passes.
  • PowerShell 7.6.9 without this change: the test fails with Expected 42, because the type data should be loaded in the 'Second' runspace, but got $null.

Locally, I also ran the existing tests that use runspace pools, along with the Import-Module, Update-TypeData, Remove-TypeData, type-data, and runspace cmdlet tests. Only the WinRM-based remote pool test failed, because WinRM isn't configured on the test machine.

PR Checklist

  • PR has a meaningful title
    • Use the present tense and imperative mood when describing your changes
  • Summarized changes
  • Make sure all .h, .cpp, .cs, .ps1 and .psm1 files have the correct copyright header
  • This PR is ready to merge. If this PR is a work in progress, please open this as a Draft Pull Request and mark it as Ready to Review when it is ready to merge.
  • Breaking changes
    • None
    • OR
    • Experimental feature(s) needed
      • Experimental feature name(s):
  • User-facing changes
    • Not Applicable
    • OR
    • Documentation needed
      • Issue filed:
  • Testing - New and feature
    • N/A or can only be tested interactively
    • OR
    • Make sure you've added a new test if existing tests do not effectively test the code changed

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@azure-pipelines

azure-pipelines Bot commented Oct 29, 2026

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ryanriesMSFT
Give each runspace in a RunspacePool its own InitialSessionState
b3a7e4b
RunspacePoolInternal created every runspace with
CreateRunspaceFromSessionStateNoClone, so all runspaces in a pool shared a
single InitialSessionState instance. The InitialSessionState is updated when a
runspace is opened and when modules are imported into it (for example, the
type and format files that were loaded are recorded in its Types and Formats
collections), and Import-Module uses those collections to decide whether a
module's TypesToProcess and FormatsToProcess files are already loaded.

As a result, a runspace that was opened before another runspace in the same
pool imported a module skipped that module's type and format data when it
imported the module itself. ETS members such as the enum script properties
generated for CDXML modules (NetConnection, Storage, ...) were missing in that
runspace, so properties like NetworkCategory surfaced as raw integers. Whether
a given runspace was affected depended on runspace creation order and timing.

Create pooled runspaces with RunspaceFactory.CreateRunspace, which clones the
pool's InitialSessionState exactly like a standalone runspace, and remove the
now-unused no-clone code path.

Co-authored-by: Copilot <[email protected]>
Copilot-Session: 7411f14d-b619-4f89-9a9e-b0880f11ef33
@ryanriesMSFT
Joseph "Ryan" Ries (ryanriesMSFT) force-pushed the fix-runspacepool-per-runspace-iss branch from 94e152f to b3a7e4b Compare October 29, 2026 20:36
@ryanriesMSFT
Joseph "Ryan" Ries (ryanriesMSFT) marked this pull request as ready for review October 29, 2026 20:39
@ryanriesMSFT
Joseph "Ryan" Ries (ryanriesMSFT) requested a review from a team as a code owner October 29, 2026 20:39
Copilot AI balanced review requested due to automatic review settings October 29, 2026 20:39
Copilot started reviewing on behalf of Joseph "Ryan" Ries (ryanriesMSFT) October 29, 2026 20:40
View session
Copilot AI reviewed Oct 29, 2026
View reviewed changes

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues were identified for the change isolating each pooled runspace’s session state.

No open issues 🎉

What changed in this PR

This PR isolates each local pooled runspace’s InitialSessionState, preventing module imports in one runspace from causing another to skip type and format data.

Changes:

  • Use the standard cloning factory when creating pooled runspaces.
  • Remove the unused internal factory and constructors that bypassed cloning.
  • Add a deterministic, cross-platform regression test covering type and format data loading in two pooled runspaces.
File Description
test/​powershell/​engine/​Remoting/​RunspacePool.Tests.ps1 Adds event-coordinated regression coverage for per-runspace module data.
src/​System.Management.Automation/​engine/​hostifaces/​RunspacePoolInternal.cs Creates each runspace with its own session-state copy.
src/​System.Management.Automation/​engine/​hostifaces/​LocalConnection.cs Removes the constructor supporting clone suppression.
src/​System.Management.Automation/​engine/​hostifaces/​ConnectionFactory.cs Removes the unused no-clone factory method.
src/​System.Management.Automation/​engine/​hostifaces/​ConnectionBase.cs Removes the base constructor supporting clone suppression.

Review effort: Balanced

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@SteveL-MSFT Steve Lee (SteveL-MSFT) added WG-NeedsReview Needs a review by the labeled Working Group WG-Engine core PowerShell engine, interpreter, and runtime labels Oct 29, 2026
@microsoft-github-policy-service microsoft-github-policy-service Bot added the Review - Needed The PR is being reviewed label Nov 6, 2026
@microsoft-github-policy-service

microsoft-github-policy-service Bot commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days.
Maintainer, please provide feedback and/or mark it as Waiting on Author

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

Copilot code review Copilot
Copilot review effort
Applies to this pull request for everyone.Learn more about Copilot code review.
Copilot left review comments

At least 1 approving review is required to merge this pull request.

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

Review - Needed The PR is being reviewed WG-Engine core PowerShell engine, interpreter, and runtime WG-NeedsReview Needs a review by the labeled Working Group

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

3 participants

@ryanriesMSFT @SteveL-MSFT
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.