Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev

This commit is contained in:
Kvarkas
2026-02-20 14:29:33 +00:00
10 changed files with 247 additions and 181 deletions

View File

@@ -19,7 +19,7 @@
<PackageVersion Include="Gherkin" Version="38.0.0" />
<PackageVersion Include="Google.Protobuf" Version="3.33.5" />
<PackageVersion Include="LiteDB" Version="5.0.21" />
<PackageVersion Include="log4net" Version="3.2.0" />
<PackageVersion Include="log4net" Version="3.3.0" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.1.4" />
<PackageVersion Include="Microsoft.Data.SqlClient.SNI" Version="6.0.2" />
<PackageVersion Include="Microsoft.Data.SqlClient.SNI.runtime" Version="6.0.2" />
@@ -62,6 +62,7 @@
<PackageVersion Include="System.Collections.Immutable" Version="10.0.3" />
<PackageVersion Include="System.Console" Version="4.3.1" />
<PackageVersion Include="System.Data.Common" Version="4.3.0" />
<PackageVersion Include="System.Data.Odbc" Version="9.0.4" />
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.3" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.3" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.3" />
@@ -95,7 +96,7 @@
<PackageVersion Include="System.Text.Json" Version="10.0.3" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="System.ValueTuple" Version="4.6.1" />
<PackageVersion Include="System.ValueTuple" Version="4.6.2" />
<PackageVersion Include="System.Windows.Extensions" Version="10.0.3" />
<PackageVersion Include="System.Xml.ReaderWriter" Version="4.3.1" />
<PackageVersion Include="VaultSharp" Version="1.17.5.1" />

View File

@@ -1,149 +1,38 @@
using System;
using Microsoft.Data.SqlClient;
using System;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using LiteDB;
using mRemoteNG.Resources.Language;
namespace mRemoteNG.Config.DatabaseConnectors
{
//[SupportedOSPlatform("windows")]
/// <summary>
/// A helper class for testing database connectivity
/// A helper class for testing database connectivity.
/// </summary>
///
using System;
using System.Data.SqlClient;
[SupportedOSPlatform("windows")]
public class DatabaseConnectionTester
{
public async Task<ConnectionTestResult> TestConnectivity(string type, string server, string database, string username, string password)
{
try
{
// Build the connection string based on the provided parameters
string connectionString = $"Data Source={server};Initial Catalog={database};User ID={username};Password={password}";
// Attempt to open a connection to the database
using (SqlConnection connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
}
using IDatabaseConnector dbConnector = DatabaseConnectorFactory.DatabaseConnector(type, server, database, username, password);
await dbConnector.ConnectAsync();
return ConnectionTestResult.ConnectionSucceded;
}
catch (SqlException ex)
catch (Exception ex)
{
// Handle specific SQL exceptions
switch (ex.Number)
{
case 4060: // Invalid Database
return ConnectionTestResult.UnknownDatabase;
case 18456: // Login Failed
return ConnectionTestResult.CredentialsRejected;
case -1: // Server not accessible
return ConnectionTestResult.ServerNotAccessible;
default:
return ConnectionTestResult.UnknownError;
}
}
catch
{
// Handle any other exceptions
string message = ex.Message;
if (message.Contains("server was not found", StringComparison.OrdinalIgnoreCase)
|| message.Contains("network-related", StringComparison.OrdinalIgnoreCase)
|| message.Contains("instance-specific", StringComparison.OrdinalIgnoreCase))
return ConnectionTestResult.ServerNotAccessible;
if (message.Contains("Cannot open database", StringComparison.OrdinalIgnoreCase)
|| message.Contains("Unknown database", StringComparison.OrdinalIgnoreCase))
return ConnectionTestResult.UnknownDatabase;
if (message.Contains("Login failed", StringComparison.OrdinalIgnoreCase)
|| message.Contains("Access denied", StringComparison.OrdinalIgnoreCase))
return ConnectionTestResult.CredentialsRejected;
return ConnectionTestResult.UnknownError;
}
}
}
//public class DatabaseConnectionTester
//{
//public async Task<ConnectionTestResult> TestConnectivity(string type, string server, string database, string username, string password)
//{
//using IDatabaseConnector dbConnector = DatabaseConnectorFactory.DatabaseConnector(type, server, database, username, password);
//try
//{
// Validate architecture compatibility
//if (!Environment.Is64BitProcess)
//{
// throw new PlatformNotSupportedException("The application must run in a 64-bit process to use this database connector.");
// }
// Attempt to connect
//using (SqlConnection connection = new SqlConnection("Data Source=172.22.155.100,1433;Initial Catalog=Demo;Integrated Security=False;User ID=sa;Password=London123;Multiple Active Result Sets=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=True;Application Name=mRemoteNG;Application Intent=ReadOnly"))
//{
// connection.Open();
// Console.WriteLine("Connection successful!");
//}
//Console.WriteLine($"{RuntimeInformation.OSArchitecture}");
//Console.WriteLine($"{RuntimeInformation.ProcessArchitecture}");
//try
//{
// using (SqlConnection connection = new SqlConnection("Data Source=172.22.155.100,1433;Initial Catalog=Demo;Integrated Security=False;User ID=sa;Password=London123;Multiple Active Result Sets=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=True;Application Name=mRemoteNG;Application Intent=ReadOnly"))
// {
// connection.Open();
// Console.WriteLine("Connection successful!");
// }
//}
//catch (Exception ex)
//{
// Console.WriteLine($"Connection failed: {ex.Message}");
//}
//}
/*
try
{
using (SqlConnection connection = new SqlConnection("Data Source=172.22.155.100,1433;Initial Catalog=Demo;Integrated Security=False;User ID=sa;Password=London123;Multiple Active Result Sets=True;Connect Timeout=30;Encrypt=True;Trust Server Certificate=True;Application Name=mRemoteNG;Application Intent=ReadOnly"))
{
connection.Open();
}
}
catch (TypeInitializationException ex)
{
Console.WriteLine($"Type initialization error: {ex.InnerException?.Message}");
}
//await dbConnector.ConnectAsync();
return ConnectionTestResult.ConnectionSucceded;
}
catch (PlatformNotSupportedException ex)
{
// Log or handle architecture mismatch
Console.WriteLine(string.Format(Language.ErrorPlatformNotSupported, ex.Message));
return ConnectionTestResult.UnknownError;
}
catch (DllNotFoundException ex)
{
// Handle missing native dependencies
Console.WriteLine(string.Format(Language.ErrorMissingDependency, ex.Message));
return ConnectionTestResult.UnknownError;
}
catch (BadImageFormatException ex)
{
// Handle architecture mismatch in native libraries
Console.WriteLine(string.Format(Language.ErrorArchitectureMismatch, ex.Message));
return ConnectionTestResult.UnknownError;
}
catch (SqlException sqlException)
{
if (sqlException.Message.Contains("The server was not found"))
return ConnectionTestResult.ServerNotAccessible;
if (sqlException.Message.Contains("Cannot open database"))
return ConnectionTestResult.UnknownDatabase;
if (sqlException.Message.Contains("Login failed for user"))
return ConnectionTestResult.CredentialsRejected;
return ConnectionTestResult.UnknownError;
}
catch (Exception ex)
{
// Log unexpected errors
Console.WriteLine($"Unexpected error: {ex.Message}");
return ConnectionTestResult.UnknownError;
}
*/
// }
// }
}
}

View File

@@ -26,6 +26,8 @@ namespace mRemoteNG.Config.DatabaseConnectors
{
case "mysql":
return new MySqlDatabaseConnector(server, database, username, password);
case "odbc":
throw new NotSupportedException("ODBC database connections are not supported for schema initialization. Please use a supported database backend.");
case "mssql":
default:
return new MSSqlDatabaseConnector(server, database, username, password);

View File

@@ -0,0 +1,106 @@
using System.Data;
using System.Data.Common;
using System.Data.Odbc;
using System.Threading.Tasks;
// ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.Config.DatabaseConnectors
{
public class OdbcDatabaseConnector : IDatabaseConnector
{
private DbConnection _dbConnection { get; set; } = default(OdbcConnection);
private string _dbConnectionString = "";
private readonly string _connectionString;
private readonly string _dbUsername;
private readonly string _dbPassword;
public DbConnection DbConnection()
{
return _dbConnection;
}
public DbCommand DbCommand(string dbCommand)
{
return new OdbcCommand(dbCommand, (OdbcConnection) _dbConnection);
}
public bool IsConnected => (_dbConnection.State == ConnectionState.Open);
/// <summary>
/// Creates an ODBC database connector.
/// </summary>
/// <param name="connectionString">
/// An ODBC connection string or DSN name. If a plain DSN name is provided (no '=' character),
/// it is automatically wrapped as "DSN=&lt;name&gt;".
/// </param>
/// <param name="username">Optional user name appended to the connection string as UID.</param>
/// <param name="password">Optional password appended to the connection string as PWD.</param>
public OdbcDatabaseConnector(string connectionString, string username, string password)
{
_connectionString = connectionString;
_dbUsername = username;
_dbPassword = password;
Initialize();
}
private void Initialize()
{
BuildConnectionString();
_dbConnection = new OdbcConnection(_dbConnectionString);
}
private void BuildConnectionString()
{
// If no '=' present in the provided string it is a plain DSN name.
string baseString = _connectionString.Contains('=')
? _connectionString
: $"DSN={_connectionString}";
OdbcConnectionStringBuilder builder = new()
{
ConnectionString = baseString
};
if (!string.IsNullOrEmpty(_dbUsername))
builder["UID"] = _dbUsername;
if (!string.IsNullOrEmpty(_dbPassword))
builder["PWD"] = _dbPassword;
_dbConnectionString = builder.ConnectionString;
}
public void Connect()
{
_dbConnection.Open();
}
public async Task ConnectAsync()
{
await _dbConnection.OpenAsync();
}
public void Disconnect()
{
_dbConnection.Close();
}
public void AssociateItemToThisConnector(DbCommand dbCommand)
{
dbCommand.Connection = (OdbcConnection) _dbConnection;
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool itIsSafeToFreeManagedObjects)
{
if (!itIsSafeToFreeManagedObjects) return;
_dbConnection.Close();
_dbConnection.Dispose();
}
}
}

View File

@@ -138,6 +138,7 @@
<PackageReference Include="Microsoft.Web.WebView2" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" />
<PackageReference Include="MySql.Data" />
<PackageReference Include="System.Data.Odbc" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Newtonsoft.Json.Schema" />
<PackageReference Include="OpenCover" />

View File

@@ -5,6 +5,10 @@ Common External Tool Configurations
The list below of various examples is by no means a full list of ways to use
**External Tools** but gives you a idea of how it can be used in different ways.
.. note::
These examples use variables like %HOSTNAME%, %USERNAME%, etc.
For a complete list of available variables, see the :ref:`variables_reference` page.
Ping
====
Ping a server via cmdline.

View File

@@ -23,6 +23,8 @@ What we are going to use is the following for our entry:
- Protocol - sftp://
- Input Parameters (variables) - %HOSTNAME%, %USERNAME%,%PASSWORD% and %PORT%
For a complete list of available variables, see the :ref:`variables_reference` page.
All of the variables are parsed from mRemoteNG connection item to the filezilla command line.
So lets build this entry up in **External Tools** where we add all these items.

View File

@@ -39,6 +39,7 @@ Welcome to mRemoteNG's documentation!
:maxdepth: 2
:caption: Miscellaneous
variables_reference.rst
external_tools_cheat_sheet.rst
migrate.rst
Contribute <https://github.com/mRemoteNG/mRemoteNG/wiki>

View File

@@ -100,7 +100,9 @@ Variables
Variables and arguments can be used to tell the external tool what to do.
This is the list of variables supported by mRemoteNG:
For a complete list of available variables and their usage, see the :ref:`variables_reference` page.
Quick Reference - Available Variables:
- %NAME%
- %HOSTNAME%
@@ -112,53 +114,9 @@ This is the list of variables supported by mRemoteNG:
- %MACADDRESS%
- %USERFIELD%
mRemoteNG will also expand environment variables such as %PATH% and %USERPROFILE%. If you need to use an environment
variable with the same name as an mRemoteNG variable, use \\% instead of %. The most common use of this is for the
USERNAME environment variable. %USERNAME% will be expanded to the username set in the currently selected connection.
\\%USERNAME\\% will be expanded to the value set in the USERNAME environment variable.
See the :ref:`variables_reference` page for detailed information about:
If you need to send a variable name to a program without mRemoteNG expanding it, use ^% instead of %.
mRemoteNG will remove the caret (^) and leave the rest unchanged.
For example, ^%USERNAME^% will be sent to the program as %USERNAME% and will not be expanded.
Rules for variables
-------------------
- Variables always refer to the currently selected connection.
- Variable names are case-insensitive.
- Variables can be used in both the Filename and Arguments fields.
Special Character Escaping
==========================
Expanded variables will be escaped using the rules below. There are two levels of escaping that are done.
1. Is escaping for standard argument splitting (C/C++ argv, CommandLineToArgvW, etc)
2. Is escaping shell metacharacters for ShellExecute.
Argument splitting escaping
---------------------------
- Each quotation mark will be escaped by a backslash
- One or more backslashes (\\) followed by a quotation mark ("):
- Each backslash will be escaped by another backslash
- The quotation mark will be escaped by a backslash
- If the connection's user field contains ``"This"`` is a ``\"test\"``
- Then %USERFIELD% is replaced with ``\"This\"`` is a ``\\\"test\\\"``
- A variable name followed by a quotation mark (for example, %USERFIELD%") with a value ending in one or more backslashes:
- Each backslash will be escaped by another backslash
- Example:
- If the connection's user field contains ``c:\Example\``
- Then "%USERFIELD%" is replaced with ``"c:\Example\\"``
To disable argument splitting escaping for a variable, precede its name with a minus (-) sign. For example: %-USERFIELD%
Shell metacharacter escaping
----------------------------
- The shell metacharacters are ( ) % ! ^ " < > & |
- Each shell metacharacter will be escaped by a caret (^)
To disable both argument splitting and shell metacharacter escaping for a variable, precede its name with an exclamation point (!).
For example, %!USERFIELD%. This is not recommended and may cause unexpected results.
Only variables that have been expanded will be escaped. It is up to you to escape the rest of the arguments.
- How to use environment variables
- How to prevent variable expansion
- Rules for using variables
- Special character escaping

View File

@@ -0,0 +1,102 @@
.. _variables_reference:
*******************
Variables Reference
*******************
Variables (also called parameters) can be used with External Tools to dynamically insert connection properties into commands and arguments.
Available Variables
===================
mRemoteNG supports the following variables:
%NAME%
The display name of the connection.
%HOSTNAME%
The hostname or IP address of the connection.
%PORT%
The port number for the connection.
%USERNAME%
The username configured for the connection.
%PASSWORD%
The password configured for the connection.
%DOMAIN%
The domain configured for the connection.
%DESCRIPTION%
The description text of the connection.
%MACADDRESS%
The MAC address configured for the connection.
%USERFIELD%
The custom user field configured for the connection.
Environment Variables
======================
mRemoteNG will also expand environment variables such as %PATH% and %USERPROFILE%. If you need to use an environment
variable with the same name as an mRemoteNG variable, use \\% instead of %. The most common use of this is for the
USERNAME environment variable. %USERNAME% will be expanded to the username set in the currently selected connection.
\\%USERNAME\\% will be expanded to the value set in the USERNAME environment variable.
Preventing Variable Expansion
==============================
If you need to send a variable name to a program without mRemoteNG expanding it, use ^% instead of %.
mRemoteNG will remove the caret (^) and leave the rest unchanged.
For example, ^%USERNAME^% will be sent to the program as %USERNAME% and will not be expanded.
Rules for Variables
===================
- Variables always refer to the currently selected connection.
- Variable names are case-insensitive.
- Variables can be used in both the Filename and Arguments fields in External Tools.
Special Character Escaping
===========================
Expanded variables will be escaped using the rules below. There are two levels of escaping that are done:
1. Escaping for standard argument splitting (C/C++ argv, CommandLineToArgvW, etc)
2. Escaping shell metacharacters for ShellExecute.
Argument Splitting Escaping
----------------------------
- Each quotation mark will be escaped by a backslash
- One or more backslashes (\\) followed by a quotation mark ("):
- Each backslash will be escaped by another backslash
- The quotation mark will be escaped by a backslash
- If the connection's user field contains ``"This"`` is a ``\"test\"``
- Then %USERFIELD% is replaced with ``\"This\"`` is a ``\\\"test\\\"``
- A variable name followed by a quotation mark (for example, %USERFIELD%") with a value ending in one or more backslashes:
- Each backslash will be escaped by another backslash
- Example:
- If the connection's user field contains ``c:\Example\``
- Then "%USERFIELD%" is replaced with ``"c:\Example\\"``
To disable argument splitting escaping for a variable, precede its name with a minus (-) sign. For example: %-USERFIELD%
Shell Metacharacter Escaping
-----------------------------
- The shell metacharacters are ( ) % ! ^ " < > & |
- Each shell metacharacter will be escaped by a caret (^)
To disable both argument splitting and shell metacharacter escaping for a variable, precede its name with an exclamation point (!).
For example, %!USERFIELD%. This is not recommended and may cause unexpected results.
Only variables that have been expanded will be escaped. It is up to you to escape the rest of the arguments.
See Also
========
- :ref:`external_tools` - For information on using variables with External Tools