diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
index 0c6cf503092..e4565d4869e 100644
--- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
+++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
@@ -13,6 +13,7 @@
#include "PowershellCoreProfileGenerator.h"
#include "VisualStudioGenerator.h"
#include "WslDistroGenerator.h"
+#include "SshHostGenerator.h"
// The following files are generated at build time into the "Generated Files" directory.
// defaults(-universal).h is a file containing the default json settings in a std::string_view.
@@ -148,6 +149,7 @@ void SettingsLoader::GenerateProfiles()
_executeGenerator(WslDistroGenerator{});
_executeGenerator(AzureCloudShellGenerator{});
_executeGenerator(VisualStudioGenerator{});
+ _executeGenerator(SshHostGenerator{});
}
// A new settings.json gets a special treatment:
diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj
index 04f0f38d7b8..dc667fe44a9 100644
--- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj
+++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj
@@ -92,6 +92,7 @@
+
@@ -165,6 +166,7 @@
+
diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters
index fb21fccdc31..3a4aab82329 100644
--- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters
+++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters
@@ -18,6 +18,9 @@
profileGeneration
+
+ profileGeneration
+
@@ -61,6 +64,9 @@
profileGeneration
+
+ profileGeneration
+
diff --git a/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp b/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp
new file mode 100644
index 00000000000..2f2da435793
--- /dev/null
+++ b/src/cascadia/TerminalSettingsModel/SshHostGenerator.cpp
@@ -0,0 +1,114 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
+
+#include "SshHostGenerator.h"
+#include "../../inc/DefaultSettings.h"
+
+#include "DynamicProfileUtils.h"
+
+static constexpr std::wstring_view SshHostGeneratorNamespace{ L"Windows.Terminal.SSH" };
+
+static constexpr const wchar_t* PROFILE_TITLE_PREFIX = L"SSH - ";
+
+static constexpr const wchar_t* SSH_EXE_PATH1 = L"%SystemRoot%\\System32\\OpenSSH\\ssh.exe";
+static constexpr const wchar_t* SSH_EXE_PATH2 = L"%ProgramFiles%\\OpenSSH\\ssh.exe";
+static constexpr const wchar_t* SSH_EXE_PATH3 = L"%ProgramFiles(x86)%\\OpenSSH\\ssh.exe";
+
+static constexpr const wchar_t* SSH_SYSTEMCONFIG_PATH = L"%ProgramData%\\ssh\\ssh_config";
+static constexpr const wchar_t* SSH_USERCONFIG_PATH = L"%UserProfile%\\.ssh\\config";
+
+static constexpr std::wstring_view SSH_HOST_PREFIX{ L"Host " };
+
+using namespace ::Microsoft::Terminal::Settings::Model;
+using namespace winrt::Microsoft::Terminal::Settings::Model;
+
+std::wstring_view SshHostGenerator::GetNamespace() const noexcept
+{
+ return SshHostGeneratorNamespace;
+}
+
+static bool tryFindSshExePath(std::wstring& sshExePath) noexcept
+{
+ try
+ {
+ // OpenSSH is installed under System32 when installed via Optional Features
+ if (std::filesystem::exists(wil::ExpandEnvironmentStringsW(SSH_EXE_PATH1)))
+ {
+ sshExePath = SSH_EXE_PATH1;
+ return true;
+ }
+
+ // OpenSSH (x86/x64) is installed under Program Files when installed via MSI
+ if (std::filesystem::exists(wil::ExpandEnvironmentStringsW(SSH_EXE_PATH2)))
+ {
+ sshExePath = SSH_EXE_PATH2;
+ return true;
+ }
+
+ // OpenSSH (x86) is installed under Program Files x86 when installed via MSI on x64 machine
+ if (std::filesystem::exists(wil::ExpandEnvironmentStringsW(SSH_EXE_PATH3)))
+ {
+ sshExePath = SSH_EXE_PATH3;
+ return true;
+ }
+ }
+ CATCH_LOG();
+
+ return false;
+}
+
+static winrt::com_ptr makeProfile(const std::wstring& sshExePath, const std::wstring& hostName)
+{
+ const auto profile{ CreateDynamicProfile(PROFILE_TITLE_PREFIX + hostName) };
+
+ profile->Commandline(winrt::hstring{ L"\"" + sshExePath + L"\" " + hostName });
+
+ return profile;
+}
+
+static void tryGetHostNamesFromConfigFile(const std::wstring configPath, std::vector& hostNames) noexcept
+{
+ try
+ {
+ const std::filesystem::path resolvedConfigPath{ wil::ExpandEnvironmentStringsW(configPath.c_str()) };
+ if (std::filesystem::exists(resolvedConfigPath))
+ {
+ std::wifstream inputStream(resolvedConfigPath);
+
+ std::wstring line;
+ while (std::getline(inputStream, line))
+ {
+ if (line.starts_with(SSH_HOST_PREFIX))
+ {
+ hostNames.emplace_back(line.substr(SSH_HOST_PREFIX.length()));
+ }
+ }
+ }
+ }
+ CATCH_LOG();
+}
+
+// Method Description:
+// - Generate a list of profiles for each detected OpenSSH host.
+// Arguments:
+// -
+// Return Value:
+// -
+void SshHostGenerator::GenerateProfiles(std::vector>& profiles) const
+{
+ std::wstring sshExePath;
+ if (tryFindSshExePath(sshExePath))
+ {
+ std::vector hostNames;
+
+ tryGetHostNamesFromConfigFile(SSH_SYSTEMCONFIG_PATH, hostNames);
+ tryGetHostNamesFromConfigFile(SSH_USERCONFIG_PATH, hostNames);
+
+ for (const auto& hostName : hostNames)
+ {
+ profiles.emplace_back(makeProfile(sshExePath, hostName));
+ }
+ }
+}
diff --git a/src/cascadia/TerminalSettingsModel/SshHostGenerator.h b/src/cascadia/TerminalSettingsModel/SshHostGenerator.h
new file mode 100644
index 00000000000..f7ed9682158
--- /dev/null
+++ b/src/cascadia/TerminalSettingsModel/SshHostGenerator.h
@@ -0,0 +1,29 @@
+/*++
+Copyright (c) Microsoft Corporation
+Licensed under the MIT license.
+
+Module Name:
+- SshHostGenerator
+
+Abstract:
+- This is the dynamic profile generator for SSH connections. Enumerates all the
+ SSH hosts to create profiles for them.
+
+Author(s):
+- Jon Thysell - September 2022
+
+--*/
+
+#pragma once
+
+#include "IDynamicProfileGenerator.h"
+
+namespace winrt::Microsoft::Terminal::Settings::Model
+{
+ class SshHostGenerator final : public IDynamicProfileGenerator
+ {
+ public:
+ std::wstring_view GetNamespace() const noexcept override;
+ void GenerateProfiles(std::vector>& profiles) const override;
+ };
+};