[release/v7.4.15] Close pipe client handles after creating the child ssh process by adityapatwardhan · Pull Request #27139 · PowerShell/PowerShell · GitHub
Skip to content

[release/v7.4.15] Close pipe client handles after creating the child ssh process#27139

Merged
adityapatwardhan merged 2 commits intoPowerShell:release/v7.4.15from
adityapatwardhan:backport/release/v7.4.15/26491-9ee3c61c2
Apr 3, 2026
Merged

[release/v7.4.15] Close pipe client handles after creating the child ssh process#27139
adityapatwardhan merged 2 commits intoPowerShell:release/v7.4.15from
adityapatwardhan:backport/release/v7.4.15/26491-9ee3c61c2

Conversation

@adityapatwardhan
Copy link
Copy Markdown
Member

Backport of #26491 to release/v7.4.15

Triggered by @adityapatwardhan on behalf of @daxian-dbw

Original CL Label: CL-General

/cc @PowerShell/powershell-maintainers

Impact

REQUIRED: Choose either Tooling Impact or Customer Impact (or both). At least one checkbox must be selected.

Tooling Impact

  • Required tooling change
  • Optional tooling change (include reasoning)

Customer Impact

  • Customer reported
  • Found internally

Fixes issues #25203 and #26228 where Invoke-Command -HostName and New-PSSession -HostName hang indefinitely on Windows when connecting to a non-existent SSH host. On Windows, pipe client handles were not closed after ssh.exe was created, causing the process to remain open even after ssh.exe exited.

Regression

REQUIRED: Check exactly one box.

  • Yes
  • No

This is not a regression.

Testing

New Pester tests added in SSHRemotingCmdlets.Tests.ps1 that verify New-PSSession and Invoke-Command with non-existing hosts return errors instead of hanging indefinitely.

Risk

REQUIRED: Check exactly one box.

  • High
  • Medium
  • Low

The fix is surgical - it adds lpStartupInfo.Dispose() in a finally block after process creation to close inherited pipe client handles. This prevents a hang but doesn't change the core logic. Also includes a bug fix where stdOutReaderVar and stdErrReaderVar were incorrectly disposing stdInWriterVar.

Merge Conflicts

Conflict in src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs: The release branch uses the 2-param context.CommandDiscovery.LookupCommandInfo(sshCommand, CommandOrigin.Internal) API while the backport uses the 5-param static overload. Resolved by keeping the release branch's API form and adjusting the variable reference from appInfo.Path to cmdInfo.Path. The key bug fix (lpStartupInfo.Dispose() in the finally block) and other changes applied cleanly.

@adityapatwardhan adityapatwardhan added the CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log label Apr 1, 2026
Copilot AI review requested due to automatic review settings April 1, 2026 22:59
@adityapatwardhan adityapatwardhan added the CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log label Apr 1, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Backport to release/v7.4.15 that prevents SSH-based remoting (Invoke-Command -HostName / New-PSSession -HostName) from hanging indefinitely on Windows by ensuring inherited pipe client handles are closed after ssh.exe is created.

Changes:

  • Dispose STARTUPINFO in the Win32 CreateProcess path to close inherited pipe client handles after process creation.
  • Fix incorrect disposal of stdOutReaderVar/stdErrReaderVar on exception.
  • Add CI Pester coverage for “non-existent host” scenarios and minor type/namespace cleanup in remoting transport code.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
test/powershell/engine/Remoting/SSHRemotingCmdlets.Tests.ps1 Adds CI tests intended to ensure SSH remoting errors out (doesn’t hang) for non-existent hosts.
src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs Minor cleanups (type qualification reductions) and uses OutOfProcessTextWriter.ErrorPrefix.
src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs Closes inherited pipe client handles via lpStartupInfo.Dispose() and fixes incorrect stream disposal in error paths.

Comment on lines +78 to +83
ScriptBlock = { New-PSSession -HostName "test-notexist" -UserName "test" -ErrorAction Stop }
FullyQualifiedErrorId = 'PSSessionOpenFailed'
},
@{
Name = 'Verifies no hang for Invoke-Command with non-existing host name'
ScriptBlock = { Invoke-Command -HostName "test-notexist" -UserName "test" -ScriptBlock { 1 } -ErrorAction Stop }
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

test-notexist is not guaranteed to be non-resolvable (DNS search suffixes and wildcard DNS can resolve it), which can make this CI test flaky or slow. Use a reserved non-existent domain like *.invalid (RFC 2606) and/or pass a small -ConnectingTimeout to keep the test deterministic.

Suggested change
ScriptBlock = { New-PSSession -HostName "test-notexist" -UserName "test" -ErrorAction Stop }
FullyQualifiedErrorId = 'PSSessionOpenFailed'
},
@{
Name = 'Verifies no hang for Invoke-Command with non-existing host name'
ScriptBlock = { Invoke-Command -HostName "test-notexist" -UserName "test" -ScriptBlock { 1 } -ErrorAction Stop }
ScriptBlock = { New-PSSession -HostName "nonexistent.invalid" -UserName "test" -ErrorAction Stop }
FullyQualifiedErrorId = 'PSSessionOpenFailed'
},
@{
Name = 'Verifies no hang for Invoke-Command with non-existing host name'
ScriptBlock = { Invoke-Command -HostName "nonexistent.invalid" -UserName "test" -ScriptBlock { 1 } -ErrorAction Stop }

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +92
Describe "No hangs when host doesn't exist" -Tags "CI" {
$testCases = @(
@{
Name = 'Verifies no hang for New-PSSession with non-existing host name'
ScriptBlock = { New-PSSession -HostName "test-notexist" -UserName "test" -ErrorAction Stop }
FullyQualifiedErrorId = 'PSSessionOpenFailed'
},
@{
Name = 'Verifies no hang for Invoke-Command with non-existing host name'
ScriptBlock = { Invoke-Command -HostName "test-notexist" -UserName "test" -ScriptBlock { 1 } -ErrorAction Stop }
FullyQualifiedErrorId = 'PSSessionStateBroken'
}
)

It "<Name>" -TestCases $testCases {
param ($ScriptBlock, $FullyQualifiedErrorId)

$ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -ExceptionType 'System.Management.Automation.Remoting.PSRemotingTransportException'
}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

This new CI Describe block doesn’t skip when ssh isn’t available, while other SSH remoting tests do (e.g. SSHRemotingAPI.Tests.ps1 uses a $skipTest guard). To avoid CI failures on images/environments without OpenSSH, add a similar skip condition (or explicitly assert the prerequisite and skip with a clear reason).

Copilot uses AI. Check for mistakes.
It "<Name>" -TestCases $testCases {
param ($ScriptBlock, $FullyQualifiedErrorId)

$ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -ExceptionType 'System.Management.Automation.Remoting.PSRemotingTransportException'
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

These tests aim to ensure the cmdlets don’t hang, but the current assertion can still hang the entire Pester run if the bug regresses (the remoting call never returns, so Should -Throw never completes). To keep CI reliable, run the remoting call in a background job/process and enforce a hard timeout, failing the test if the timeout is exceeded (and cleaning up the job).

Suggested change

Copilot uses AI. Check for mistakes.
Comment thread src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs Outdated
@TravisEz13 TravisEz13 enabled auto-merge (squash) April 3, 2026 16:32
@adityapatwardhan adityapatwardhan merged commit 5e71058 into PowerShell:release/v7.4.15 Apr 3, 2026
70 of 86 checks passed
@adityapatwardhan adityapatwardhan deleted the backport/release/v7.4.15/26491-9ee3c61c2 branch April 3, 2026 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants