From 269c50bdada65cc9d3df6cc0845b60d3a2275b17 Mon Sep 17 00:00:00 2001
From: ergosteur <1992147+ergosteur@users.noreply.github.com>
Date: Wed, 10 Sep 2025 13:39:45 -0400
Subject: [PATCH] vs project add
---
.github/workflows/build.yml | 0
.gitignore | 21 +++++
SshHandler.sln | 21 +++++
SshHandler/Form1.Designer.cs | 113 ++++++++++++++++++++++++++
SshHandler/Form1.cs | 40 +++++++++
SshHandler/Form1.resx | 67 +++++++++++++++
SshHandler/Program.cs | 106 ++++++++++++++++++++++++
SshHandler/Properties/AssemblyInfo.cs | 0
SshHandler/SshHandler.csproj | 12 +++
SshHandler/ssh_icon.ico | Bin 0 -> 4710 bytes
10 files changed, 380 insertions(+)
create mode 100644 .github/workflows/build.yml
create mode 100644 .gitignore
create mode 100644 SshHandler.sln
create mode 100644 SshHandler/Form1.Designer.cs
create mode 100644 SshHandler/Form1.cs
create mode 100644 SshHandler/Form1.resx
create mode 100644 SshHandler/Program.cs
create mode 100644 SshHandler/Properties/AssemblyInfo.cs
create mode 100644 SshHandler/SshHandler.csproj
create mode 100644 SshHandler/ssh_icon.ico
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..e69de29
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f825da6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,21 @@
+# .gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
diff --git a/SshHandler.sln b/SshHandler.sln
new file mode 100644
index 0000000..deffb31
--- /dev/null
+++ b/SshHandler.sln
@@ -0,0 +1,21 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{50924D75-2903-46D4-86DD-02269EFDBCA9}") = "SshHandler", "SshHandler\SshHandler.csproj", "{960BAB18-309B-42D0-B67E-B4EF8E7F7490}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {960BAB18-309B-42D0-B67E-B4EF8E7F7490}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {960BAB18-309B-42D0-B67E-B4EF8E7F7490}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {960BAB18-309B-42D0-B67E-B4EF8E7F7490}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {960BAB18-309B-42D0-B67E-B4EF8E7F7490}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/SshHandler/Form1.Designer.cs b/SshHandler/Form1.Designer.cs
new file mode 100644
index 0000000..b1e5ad7
--- /dev/null
+++ b/SshHandler/Form1.Designer.cs
@@ -0,0 +1,113 @@
+partial class Form1
+{
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.label1 = new System.Windows.Forms.Label();
+ this.targetTextBox = new System.Windows.Forms.TextBox();
+ this.legacyCheckBox = new System.Windows.Forms.CheckBox();
+ this.connectButton = new System.Windows.Forms.Button();
+ this.cancelButton = new System.Windows.Forms.Button();
+ this.SuspendLayout();
+ //
+ // label1
+ //
+ this.label1.AutoSize = true;
+ this.label1.Location = new System.Drawing.Point(12, 15);
+ this.label1.Name = "label1";
+ this.label1.Size = new System.Drawing.Size(73, 15);
+ this.label1.TabIndex = 0;
+ this.label1.Text = "Connect to:";
+ //
+ // targetTextBox
+ //
+ this.targetTextBox.Location = new System.Drawing.Point(15, 33);
+ this.targetTextBox.Name = "targetTextBox";
+ this.targetTextBox.Size = new System.Drawing.Size(357, 23);
+ this.targetTextBox.TabIndex = 1;
+ //
+ // legacyCheckBox
+ //
+ this.legacyCheckBox.AutoSize = true;
+ this.legacyCheckBox.Location = new System.Drawing.Point(15, 71);
+ this.legacyCheckBox.Name = "legacyCheckBox";
+ this.legacyCheckBox.Size = new System.Drawing.Size(206, 19);
+ this.legacyCheckBox.TabIndex = 2;
+ this.legacyCheckBox.Text = "Enable Legacy Mode (for old devices)";
+ this.legacyCheckBox.UseVisualStyleBackColor = true;
+ //
+ // connectButton
+ //
+ this.connectButton.Location = new System.Drawing.Point(297, 106);
+ this.connectButton.Name = "connectButton";
+ this.connectButton.Size = new System.Drawing.Size(75, 23);
+ this.connectButton.TabIndex = 3;
+ this.connectButton.Text = "Connect";
+ this.connectButton.UseVisualStyleBackColor = true;
+ this.connectButton.Click += new System.EventHandler(this.connectButton_Click);
+ //
+ // cancelButton
+ //
+ this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
+ this.cancelButton.Location = new System.Drawing.Point(216, 106);
+ this.cancelButton.Name = "cancelButton";
+ this.cancelButton.Size = new System.Drawing.Size(75, 23);
+ this.cancelButton.TabIndex = 4;
+ this.cancelButton.Text = "Cancel";
+ this.cancelButton.UseVisualStyleBackColor = true;
+ this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click);
+ //
+ // Form1
+ //
+ this.AcceptButton = this.connectButton;
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.CancelButton = this.cancelButton;
+ this.ClientSize = new System.Drawing.Size(384, 141);
+ this.Controls.Add(this.cancelButton);
+ this.Controls.Add(this.connectButton);
+ this.Controls.Add(this.legacyCheckBox);
+ this.Controls.Add(this.targetTextBox);
+ this.Controls.Add(this.label1);
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+ this.Name = "Form1";
+ this.ShowIcon = false;
+ this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
+ this.Text = "SSH Connection";
+ this.ResumeLayout(false);
+ this.PerformLayout();
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.TextBox targetTextBox;
+ private System.Windows.Forms.CheckBox legacyCheckBox;
+ private System.Windows.Forms.Button connectButton;
+ private System.Windows.Forms.Button cancelButton;
+}
diff --git a/SshHandler/Form1.cs b/SshHandler/Form1.cs
new file mode 100644
index 0000000..90cc8c5
--- /dev/null
+++ b/SshHandler/Form1.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Windows.Forms;
+
+public partial class Form1 : Form
+{
+ // This property will store the result for the main program
+ public string FinalSshCommand { get; private set; }
+
+ public Form1(string initialTarget)
+ {
+ InitializeComponent();
+ targetTextBox.Text = initialTarget;
+ // Select the text for easy editing
+ targetTextBox.SelectAll();
+ targetTextBox.Focus();
+ }
+
+ private void connectButton_Click(object sender, EventArgs e)
+ {
+ string target = targetTextBox.Text;
+ bool useLegacy = legacyCheckBox.Checked;
+
+ string legacyArgs = "-o KexAlgorithms=+diffie-hellman-group1-sha1,diffie-hellman-group14-sha1 " +
+ "-o HostKeyAlgorithms=+ssh-rsa " +
+ "-o MACs=+hmac-sha1,hmac-sha1-96";
+
+ // Build the arguments for ssh.exe
+ string arguments = $"-A -C {(useLegacy ? legacyArgs : "")} {target}";
+
+ FinalSshCommand = arguments;
+ this.DialogResult = DialogResult.OK; // Set dialog result to OK
+ this.Close(); // Close the form
+ }
+
+ private void cancelButton_Click(object sender, EventArgs e)
+ {
+ this.DialogResult = DialogResult.Cancel; // Set dialog result to Cancel
+ this.Close();
+ }
+}
diff --git a/SshHandler/Form1.resx b/SshHandler/Form1.resx
new file mode 100644
index 0000000..5aada26
--- /dev/null
+++ b/SshHandler/Form1.resx
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 384, 141
+
+
+ SSH Connection
+
+
diff --git a/SshHandler/Program.cs b/SshHandler/Program.cs
new file mode 100644
index 0000000..ff98f3c
--- /dev/null
+++ b/SshHandler/Program.cs
@@ -0,0 +1,106 @@
+using Microsoft.Win32;
+using System;
+using System.Diagnostics;
+using System.Security.Principal;
+using System.Windows.Forms;
+
+static class Program
+{
+ private const string ProtocolName = "ssh";
+
+ [STAThread]
+ static void Main(string[] args)
+ {
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+
+ // If no arguments, run setup mode.
+ if (args.Length == 0)
+ {
+ SetupHandler();
+ }
+ // Otherwise, run handler mode.
+ else
+ {
+ HandleConnection(args[0]);
+ }
+ }
+
+ static void HandleConnection(string url)
+ {
+ string target = url.Replace("ssh://", "").TrimEnd('/');
+ if (!target.Contains("@"))
+ {
+ target = $"{Environment.UserName}@{target}";
+ }
+
+ // Show the form as a dialog
+ using (var form = new Form1(target))
+ {
+ if (form.ShowDialog() == DialogResult.OK)
+ {
+ // If user clicked "Connect", run the command
+ ProcessStartInfo psi = new ProcessStartInfo
+ {
+ FileName = "ssh.exe",
+ Arguments = form.FinalSshCommand,
+ UseShellExecute = true,
+ CreateNoWindow = false
+ };
+ Process.Start(psi);
+ }
+ }
+ }
+
+ static void SetupHandler()
+ {
+ if (!IsAdmin())
+ {
+ MessageBox.Show("Please run this application as an administrator to register the URL handler.", "Admin Privileges Required", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ return;
+ }
+
+ // The path is simple and correct because it's a native app
+ string exePath = Application.ExecutablePath;
+ string commandStr = $"\"{exePath}\" \"%1\"";
+
+ var confirmResult = MessageBox.Show(
+ "This will register this application to handle all ssh:// links.\n\n" +
+ $"Command:\n{commandStr}\n\n" +
+ "Do you want to proceed?",
+ "Registry Setup",
+ MessageBoxButtons.YesNo,
+ MessageBoxIcon.Information);
+
+ if (confirmResult == DialogResult.Yes)
+ {
+ try
+ {
+ // Write the keys to HKEY_CLASSES_ROOT
+ using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(ProtocolName))
+ {
+ key.SetValue("", $"URL:{ProtocolName} Protocol");
+ key.SetValue("URL Protocol", "");
+ }
+ using (RegistryKey key = Registry.ClassesRoot.CreateSubKey($"{ProtocolName}\\shell\\open\\command"))
+ {
+ key.SetValue("", commandStr);
+ }
+ MessageBox.Show("Successfully registered ssh:// protocol handler!", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show($"Failed to write to registry:\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+ }
+
+ private static bool IsAdmin()
+ {
+ using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
+ {
+ WindowsPrincipal principal = new WindowsPrincipal(identity);
+ return principal.IsInRole(WindowsBuiltInRole.Administrator);
+ }
+ }
+}
diff --git a/SshHandler/Properties/AssemblyInfo.cs b/SshHandler/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..e69de29
diff --git a/SshHandler/SshHandler.csproj b/SshHandler/SshHandler.csproj
new file mode 100644
index 0000000..b695334
--- /dev/null
+++ b/SshHandler/SshHandler.csproj
@@ -0,0 +1,12 @@
+
+
+
+ WinExe
+ net8.0-windows
+ enable
+ true
+ enable
+ ssh_icon.ico
+
+
+
diff --git a/SshHandler/ssh_icon.ico b/SshHandler/ssh_icon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..3334a3d1e3c670b8e52e0c00cd96e8b42847e6e0
GIT binary patch
literal 4710
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