Skip to content

Conversation

@daxian-dbw
Copy link
Member

@daxian-dbw daxian-dbw commented Nov 19, 2025

PR Summary

Fix #25203
Fix #26228

Symptom

Running the following commands on Windows will hang indefinitely today.

  • Invoke-Command -HostName "test" -UserName "test" -ScriptBlock { 1 }
  • New-PSSession -HostName "test" -UserName "test"

The reported hang happens on Windows only. On Linux and macOS, both commands return to the prompt with an error written out.

Root Cause

What happens on Windows is -- the ssh.exe process already exited, but the stdin writer and stdout/stderr readers were not closed as expected. That led to the hang, as both the output and error reader threads were still blocked on the reader.ReadLine call.

This is because when creating the ssh.exe using the Win32 function CreateProcess, we don't close the client handles of the pipes (stdInPipeClient, stdOutPipeClient, and stdErrPipeClient) after they get inherited by the child process (see code).

When inherited by a child process, the parent side of those handles remain valid and unchanged. Windows duplicates that handle into the child process's handle table.

  • The parent keeps its original handle.
  • The child gets a new handle value (different numeric value) that refers to the same underlying kernel object.
  • Both processes now have separate references to that object, each with its own handle entry.

So, when the child process ssh.exe exits, the pipe client handles in the child process are automatically closed. However, the pipe client handles in the parent process powershell are still open, so the pipes will remain functional and thus the pipe server handles (i.e. the readers used in the stdout/stderr reading threads) are still open. Therefore, the hang happens.

Fix Changes

To fix the issue, we need to close the parent side pipe client handles right after creating the ssh.exe process, so, only the ssh.exe process holds the handles of client side of the pipes. Then later, when ssh.exe exits, the client side of the pipe will be closed, which will, as expected, lead the server side of the pipes to be closed too.

The main changes of the fix are:

  • Close the client pipe handles after creating the child process (by adding lpStartupInfo.Dispose() in the finally block).
  • A bit cleanup of the redundant namespace-qualified usages of types (e.g., System.Diagnostics.Process, System.IO.Path, etc.)
  • Added new tests to verify that PowerShell does not hang when attempting SSH connections to non-existent hosts.

PR Checklist

Copilot AI review requested due to automatic review settings November 19, 2025 22:13
@daxian-dbw daxian-dbw added the CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log label Nov 19, 2025

This comment was marked as outdated.

@daxian-dbw daxian-dbw marked this pull request as draft November 20, 2025 05:45
@daxian-dbw daxian-dbw marked this pull request as ready for review November 20, 2025 19:27
@daxian-dbw daxian-dbw changed the title Close streams when the ssh process exits on Windows to avoid hang Close pipe client handles after creating the child ssh process Nov 20, 2025
Copy link
Collaborator

@iSazonov iSazonov left a comment

Choose a reason for hiding this comment

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

Great!

One notice.
PlatformInvokes.STARTUPINFO lpStartupInfo is disposable too. It dispose the same handles but formally we should dispose it too.

@daxian-dbw
Copy link
Member Author

Good point. Actually, disposing lpStartupInfo will dispose the parent side pipe client handles. So, I changed to just call lpStartupInfo.Dispose().

@iSazonov
Copy link
Collaborator

I believe we need to backport the commit.

Copy link
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

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


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@TravisEz13 TravisEz13 enabled auto-merge (squash) November 24, 2025 19:52
@TravisEz13 TravisEz13 merged commit 9ee3c61 into PowerShell:master Nov 24, 2025
36 checks passed
@daxian-dbw daxian-dbw deleted the ssh_hang branch November 24, 2025 20:28
adityapatwardhan pushed a commit to adityapatwardhan/PowerShell that referenced this pull request Dec 2, 2025
SIRMARGIN pushed a commit to SIRMARGIN/PowerShell that referenced this pull request Dec 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BackPort-7.4.x-Consider BackPort-7.5.x-Consider Backport-7.6.x-Migrated 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.

SSH connection hangs when session is unexpectedly terminated Invoke-Command and New-PSSession hang indefinitely when ssh.exe unexpectedly terminates

4 participants