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