Windows Support¶
Overview¶
cygnus-ssh-mcp supports Windows Server as a target system via SSH. This document covers supported versions, requirements, and behavioral differences from Linux/macOS.
Supported Windows Versions¶
| Windows Version | PowerShell | Status |
|---|---|---|
| Windows Server 2022 | 5.1 | Fully supported |
| Windows Server 2019 | 5.1 | Fully supported |
| Windows Server 2016 | 5.0 | Fully supported |
| Windows 11 | 5.1 | Fully supported |
| Windows 10 | 5.0+ | Fully supported |
| Windows Server 2012 R2 | 4.0 | Not supported (requires WMF 5.1) |
| Windows 8.1 and earlier | < 5.0 | Not supported |
Why PowerShell 5.0+?¶
The following PowerShell features require version 5.0 or later:
| Feature | Cmdlet | Used For |
|---|---|---|
| Archive operations | Compress-Archive, Expand-Archive |
Creating/extracting archives |
| Depth-limited listing | Get-ChildItem -Depth |
Directory listings with max depth |
Connecting to a Windows system with PowerShell < 5.0 will result in an error:
PowerShell 4.x detected. This tool requires PowerShell 5.0 or later.
Windows Server 2016+, Windows 10+ have PowerShell 5.0+ built-in.
For older Windows versions, install Windows Management Framework (WMF) 5.1.
Prerequisites¶
1. OpenSSH Server¶
Windows Server 2019+ includes OpenSSH as an optional feature. To enable:
# Check if installed
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*'
# Install OpenSSH Server
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
# Start and enable the service
Start-Service sshd
Set-Service -Name sshd -StartupType 'Automatic'
# Confirm firewall rule exists
Get-NetFirewallRule -Name *ssh*
For Windows Server 2016, download OpenSSH from Microsoft's GitHub releases.
2. Administrator Access¶
Unlike Linux where sudo elevates privileges per-command, Windows elevation works differently:
- Elevated session: Connect as a user in the Administrators group
- Non-elevated session: Limited to user-level operations
The use_sudo parameter on tools is ignored on Windows. If an operation requires admin rights, the SSH session must be connected as an Administrator.
Behavioral Differences¶
Path Separators¶
| Platform | Separator | Example |
|---|---|---|
| Linux/macOS | / |
/home/user/file.txt |
| Windows | \ |
C:\Users\user\file.txt |
Tools accept Windows-style paths:
ssh_file_write with file_path: "C:\Users\admin\config.txt"
ssh_dir_list_advanced with path: "C:\Program Files"
Archive Format¶
| Platform | Default Format |
|---|---|
| Linux/macOS | .tar.gz |
| Windows | .zip |
The ssh_archive_create tool creates .zip files on Windows using Compress-Archive.
Permissions Model¶
| Aspect | Linux/macOS | Windows |
|---|---|---|
| Permission format | Octal (755, 644) | ACLs |
| Owner/Group | UID/GID | SID-based |
| Elevation | sudo per command |
Session-level |
The ssh_file_stat tool returns owner information but not Unix-style permission bits on Windows.
Line Endings¶
Windows uses \r\n (CRLF) line endings. The tools handle this automatically:
- File writes preserve the content as-is
- File reads may include \r\n in output
Temp Directory¶
| Platform | Default Temp Path |
|---|---|
| Linux | /tmp |
| macOS | /tmp or /var/folders/... |
| Windows | C:\Users\<user>\AppData\Local\Temp |
Tool-Specific Notes¶
Command Execution (ssh_cmd_run)¶
Commands run with cmd.exe semantics by default - the same as typing them at a
cmd.exe prompt - regardless of the SSH server's configured DefaultShell. There's
no need to manually prefix commands with cmd /c or powershell -Command "...";
ssh_cmd_run always spawns the command through an internal wrapper that handles this
consistently:
ssh_cmd_run with command: "dir C:\Windows"
For PowerShell-specific syntax (cmdlets, pipelines using PowerShell semantics), invoke
powershell -Command "..." explicitly yourself, same as you would at a cmd.exe
prompt:
ssh_cmd_run with command: "powershell -Command \"Get-Process | Sort-Object CPU -Descending | Select-Object -First 5\""
The wrapper captures a real remote process ID (not a local placeholder), so
runtime_timeout and ssh_cmd_kill correctly target the actual process. It also
recovers the real exit code via an internal marker mechanism rather than trusting
the SSH channel's own exit status - a plain SSH exec channel through cmd.exe
silently reports the wrong exit code (a known Win32-OpenSSH/cmd.exe interaction)
whenever a nested child process is involved, which is exactly what running a command
this way requires; commands that fail now correctly report their real exit code
instead of a generic 1.
Directory Operations¶
| Operation | Linux | Windows |
|---|---|---|
| List files | find |
Get-ChildItem |
| Directory size | du -sb |
Measure-Object -Sum Length |
| Search content | grep -r |
Select-String -Recurse |
File Operations¶
| Operation | Linux | Windows |
|---|---|---|
| Find pattern | grep |
Select-String |
| File stats | stat |
Get-Item, Get-Acl |
| Copy file | cp |
Copy-Item |
| Move file | mv |
Move-Item |
Background Tasks¶
| Aspect | Linux | Windows |
|---|---|---|
| Launch method | nohup cmd & |
WMI (Invoke-CimMethod -ClassName Win32_Process -MethodName Create) |
| Check running | kill -0 $pid |
Get-Process -Id $pid |
| Terminate | kill -15 / kill -9 |
taskkill /F /T (kills the whole process tree) |
Windows background tasks are launched via WMI rather than Start-Process: a
Start-Process-launched child inherits membership in the SSH session's Windows Job
Object, and gets killed the instant that session ends, regardless of any
"detached"/hidden-window flags. WMI process creation goes through a separate service
process, so the task genuinely survives independently until it finishes or is
explicitly killed. Termination uses taskkill /F /T (not Stop-Process) because
every task PID is a cmd.exe/powershell.exe wrapper, not the real workload -
Stop-Process on just the wrapper orphans the actual process; /T kills the whole
tree in one call.
System Information (ssh_conn_host_info)¶
Windows system info is gathered via CIM/WMI:
| Info | Linux Source | Windows Source |
|---|---|---|
| CPU | /proc/cpuinfo |
Win32_Processor |
| Memory | free -m |
Win32_OperatingSystem |
| OS version | /etc/os-release |
Win32_OperatingSystem |
| Disk | df -h |
Win32_LogicalDisk |
| Network | ip addr |
Get-NetIPAddress |
Known Limitations¶
1. Unicode in Command Output¶
Issue: PowerShell over SSH doesn't properly encode UTF-8 output. Emojis and special characters may appear as ? or garbled text. This is a fundamental Windows console encoding limitation—PowerShell uses the system's OEM code page (typically CP437 or CP1252) for stdout, not UTF-8.
Affected: Reading files via ssh_cmd_run with PowerShell commands like Get-Content.
Not affected: SFTP-based operations bypass the console entirely and work correctly with Unicode.
Solution: Use ssh_file_read to read file contents. This tool uses SFTP to transfer raw bytes and decodes them on the client side, completely avoiding the Windows console encoding problem. Works correctly with emojis, international characters, and any valid UTF-8 content.
2. No Per-Command Elevation¶
Issue: Windows doesn't have sudo. The use_sudo parameter is ignored.
Workaround: Connect as an Administrator user for operations requiring elevation.
3. Non-Empty Directory Removal¶
Behavior: When ssh_dir_remove is called with recursive=False on a non-empty directory, Windows will return an error immediately (same as Linux).
4. Local PowerShell Falling Back to a Local ssh Call¶
Issue: If you (or an LLM agent) run this server's ssh_cmd_run-equivalent shell command from a local Windows PowerShell prompt rather than through the MCP tool - e.g. shelling out to ssh user@host "some | command with | pipes" directly from PowerShell - PowerShell parses pipe/redirect characters in the command string before they ever reach the remote shell. This can silently split or reinterpret a command that was meant to run as one pipeline entirely on the remote host (e.g. apt|dpkg patterns).
Not an issue for: Commands sent through the MCP ssh_cmd_run tool itself - those go through paramiko's exec_command, not a local shell, so this doesn't apply.
Workaround: If constructing a local ssh ... command by hand in PowerShell, quote the entire remote command as a single string and be aware PowerShell's own parsing happens first.
Host Configuration Example¶
# Windows Server with password auth
["administrator@winserver.example.com"]
password = "SecurePassword123"
port = 22
alias = "win-prod"
description = "Windows production server"
# Windows with SSH key
["deploy@winserver.example.com"]
keyfile = "~/.ssh/id_ed25519"
port = 22
alias = "win-staging"
description = "Windows staging server"
Note: sudo_password is not used for Windows connections.
Troubleshooting¶
Connection Refused¶
Ensure OpenSSH Server is running:
Get-Service sshd
Start-Service sshd
Permission Denied¶
Verify the user is in the Administrators group for admin operations:
net localgroup Administrators
PowerShell Version Error¶
Check PowerShell version:
$PSVersionTable.PSVersion
If below 5.0, install WMF 5.1.
Commands Not Found¶
Ensure PowerShell is the default shell for SSH. Check C:\ProgramData\ssh\sshd_config:
# Should NOT have this line, or it should point to PowerShell:
# Subsystem powershell C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe