Created
January 18, 2026 23:14
-
-
Save linktohack/77adda04fcac7854003a4c32e194177e to your computer and use it in GitHub Desktop.
Ansible on Windows with Plink and shared connection
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [defaults] | |
| inventory = inventory.ini | |
| host_key_checking = False | |
| [ssh_connection] | |
| ssh_executable = ./plink-wrapper.sh | |
| transfer_method = piped |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # plink-wrapper.sh | |
| PLINK="plink.exe" | |
| HOST="" | |
| CMD="" | |
| # Parse args - skip all OpenSSH options | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| -o) shift ;; # Skip -o and its value | |
| -C|-T|-q|-a|-x|-n) ;; # Skip single flags | |
| -p|-l|-i) shift ;; # Skip flag + value | |
| -*) ;; # Skip unknown flags | |
| *) | |
| if [[ -z "$HOST" ]]; then | |
| HOST="$1" | |
| else | |
| CMD="$*" | |
| break | |
| fi | |
| ;; | |
| esac | |
| shift | |
| done | |
| # No -t flag! It breaks piped transfers | |
| exec $PLINK -load "$HOST" -batch -share "$CMD" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """Import OpenSSH config hosts into PuTTY sessions.""" | |
| import re | |
| import os | |
| import winreg | |
| from pathlib import Path | |
| from urllib.parse import quote | |
| def parse_ssh_config(config_path=None): | |
| """Parse ~/.ssh/config and return list of host configurations.""" | |
| if config_path is None: | |
| config_path = Path.home() / ".ssh" / "config" | |
| hosts = [] | |
| current_host = None | |
| with open(config_path, 'r') as f: | |
| for line in f: | |
| line = line.strip() | |
| if not line or line.startswith('#'): | |
| continue | |
| match = re.match(r'^(\S+)\s+(.+)$', line) | |
| if not match: | |
| continue | |
| key, value = match.groups() | |
| key = key.lower() | |
| if key == 'host': | |
| if current_host and '*' not in current_host.get('host', ''): | |
| hosts.append(current_host) | |
| current_host = {'host': value} | |
| elif current_host: | |
| current_host[key] = value | |
| # Don't forget the last host | |
| if current_host and '*' not in current_host.get('host', ''): | |
| hosts.append(current_host) | |
| return hosts | |
| def create_putty_session(host_config, dry_run=False): | |
| """Create a PuTTY session in the registry.""" | |
| session_name = host_config['host'] | |
| # PuTTY URL-encodes session names | |
| session_key = quote(session_name, safe='') | |
| reg_path = rf"Software\SimonTatham\PuTTY\Sessions\{session_key}" | |
| # Map SSH config to PuTTY registry values | |
| values = {} | |
| # HostName | |
| if 'hostname' in host_config: | |
| values['HostName'] = host_config['hostname'] | |
| else: | |
| values['HostName'] = host_config['host'] | |
| # User | |
| if 'user' in host_config: | |
| values['UserName'] = host_config['user'] | |
| # Port | |
| if 'port' in host_config: | |
| values['PortNumber'] = int(host_config['port']) | |
| # IdentityFile (convert to Windows path and PuTTY format) | |
| if 'identityfile' in host_config: | |
| key_path = host_config['identityfile'] | |
| key_path = os.path.expanduser(key_path) | |
| # Convert to .ppk if needed (PuTTY uses different key format) | |
| values['PublicKeyFile'] = key_path.replace('/', '\\') | |
| # ProxyJump -> PuTTY proxy command | |
| if 'proxyjump' in host_config: | |
| values['ProxyMethod'] = 6 # Local proxy command | |
| proxy_host = host_config['proxyjump'] | |
| values['ProxyTelnetCommand'] = f'plink -batch -share {proxy_host} -nc %host:%port' | |
| # Connection sharing | |
| values['ConnectionSharing'] = 1 | |
| values['ConnectionSharingUpstream'] = 1 | |
| values['ConnectionSharingDownstream'] = 1 | |
| if dry_run: | |
| print(f"\n[Session: {session_name}]") | |
| for k, v in values.items(): | |
| print(f" {k} = {v}") | |
| return | |
| # Write to registry | |
| try: | |
| key = winreg.CreateKey(winreg.HKEY_CURRENT_USER, reg_path) | |
| for name, value in values.items(): | |
| if isinstance(value, int): | |
| winreg.SetValueEx(key, name, 0, winreg.REG_DWORD, value) | |
| else: | |
| winreg.SetValueEx(key, name, 0, winreg.REG_SZ, str(value)) | |
| winreg.CloseKey(key) | |
| print(f"Created session: {session_name}") | |
| except Exception as e: | |
| print(f"Error creating {session_name}: {e}") | |
| def main(): | |
| import argparse | |
| parser = argparse.ArgumentParser(description='Import SSH config to PuTTY') | |
| parser.add_argument('--config', '-c', help='Path to SSH config file') | |
| parser.add_argument('--dry-run', '-n', action='store_true', | |
| help='Show what would be created without writing to registry') | |
| args = parser.parse_args() | |
| hosts = parse_ssh_config(args.config) | |
| print(f"Found {len(hosts)} hosts in SSH config") | |
| for host in hosts: | |
| create_putty_session(host, dry_run=args.dry_run) | |
| if not args.dry_run: | |
| print("\nDone! Restart PuTTY to see the new sessions.") | |
| print("Note: If you use SSH keys, convert them to .ppk format with PuTTYgen") | |
| if __name__ == '__main__': | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment