Compare commits

..

126 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
f882aaf4fc Implement dynamic tab naming for SSH/Telnet PuTTY connections
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-20 13:51:36 +00:00
copilot-swe-agent[bot]
d39f2b7f3c Initial plan 2026-02-20 13:42:49 +00:00
Dimitrij
8a3688c852 Merge pull request #3149 from mRemoteNG/copilot/fix-search-results-documentation
Add dedicated Variables Reference page to fix search discoverability
2026-02-20 13:33:12 +00:00
Dimitrij
be7e5a6308 Merge pull request #3152 from mRemoteNG/renovate/log4net-3.x
Update dependency log4net to 3.3.0
2026-02-20 13:32:48 +00:00
renovate[bot]
1a0de7cb2d Update dependency log4net to 3.3.0 2026-02-20 13:32:08 +00:00
Dimitrij
7bc0ea29b1 Merge pull request #3150 from mRemoteNG/renovate/system.valuetuple-4.x
Update dependency System.ValueTuple to 4.6.2
2026-02-20 13:31:19 +00:00
renovate[bot]
4328d36a1d Update dependency System.ValueTuple to 4.6.2 2026-02-20 12:39:08 +00:00
Dimitrij
3a2576384b Merge pull request #3151 from mRemoteNG/renovate/nunit-4.x
Update dependency NUnit to 4.5.0
2026-02-18 20:24:06 +00:00
renovate[bot]
6d35121a85 Update dependency NUnit to 4.5.0 2026-02-18 19:07:42 +00:00
copilot-swe-agent[bot]
b127968a6c Add cross-references to variables page from howtos and cheat sheet
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-17 22:42:26 +00:00
copilot-swe-agent[bot]
27dd79c940 Add 'parameters' keyword to improve search discoverability
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-17 22:41:55 +00:00
copilot-swe-agent[bot]
cb5d63224a Add dedicated variables reference page to documentation
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-17 22:40:48 +00:00
copilot-swe-agent[bot]
2a55fbf1d4 Initial plan 2026-02-17 22:37:27 +00:00
Dimitrij
44651c9d4c Merge pull request #3148 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.76
2026-02-17 22:33:15 +00:00
renovate[bot]
1222c87760 Update dependency AWSSDK.EC2 to 4.0.76 2026-02-17 22:18:37 +00:00
Dimitrij
acfb035027 Merge pull request #3147 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.75.1
2026-02-17 22:17:41 +00:00
renovate[bot]
93f6d017e6 Update dependency AWSSDK.EC2 to 4.0.75.1 2026-02-17 01:48:38 +00:00
Dimitrij
455d897b9f Merge pull request #3146 from mRemoteNG/renovate/microsoft.web.webview2-1.x
Update dependency Microsoft.Web.WebView2 to 1.0.3800.47
2026-02-16 12:13:11 +00:00
renovate[bot]
3d4d1136f0 Update dependency Microsoft.Web.WebView2 to 1.0.3800.47 2026-02-16 10:47:26 +00:00
Dimitrij
598136f089 Update MSBuild version to '18.0' 2026-02-15 14:45:44 +00:00
Dimitrij
865bddfb04 Merge pull request #3145 from mRemoteNG/renovate/coverlet.collector-8.x
Update dependency coverlet.collector to v8
2026-02-15 14:40:49 +00:00
Dimitrij
3b2bc028cc Merge pull request #3144 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.75
2026-02-15 14:40:29 +00:00
Dimitrij
71c746f801 Update MSBuild version range in workflow
vsVersion: 18.0
2026-02-15 14:38:43 +00:00
renovate[bot]
079814751e Update dependency coverlet.collector to v8 2026-02-14 23:51:10 +00:00
Dimitrij
1de9ce6ef5 Merge pull request #3143 from jcefoli/v1.78.2-dev
AlwaysShowPanelTabs Setting Not Respected at Application…
2026-02-14 23:50:44 +00:00
renovate[bot]
01df0e295e Update dependency AWSSDK.EC2 to 4.0.75 2026-02-14 00:58:14 +00:00
Joe Cefoli
2631926eda Fixes #3142: AlwaysShowPanelTabs Setting Not Respected at Application Launch 2026-02-13 13:54:13 -05:00
Dimitrij
db1496d4a2 Merge pull request #3140 from mRemoteNG/renovate/actions-setup-dotnet-5.x
Update actions/setup-dotnet action to v5
2026-02-13 10:29:59 +00:00
renovate[bot]
a8b12c9ba1 Update actions/setup-dotnet action to v5 2026-02-12 23:12:53 +00:00
Dimitrij
a2b408e537 Update Build_mR-NB.yml
add .net 10 install
2026-02-12 23:12:17 +00:00
Dimitrij
a871624ab7 Update MSBuild version range in workflow
set ver for lower
2026-02-12 23:03:10 +00:00
Dimitrij
e40a800bc4 Update Build_mR-NB.yml
fix build tools ver
2026-02-12 22:58:46 +00:00
Dimitrij
48cb1ce770 Merge pull request #3139 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.74
2026-02-12 22:56:04 +00:00
Kvarkas
6733d758aa upd action 2026-02-12 22:55:25 +00:00
Dimitrij
f78b9bf51c Refactor self-contained build process in workflow
fix msbuild
2026-02-12 21:53:24 +00:00
renovate[bot]
213ea6a4d3 Update dependency AWSSDK.EC2 to 4.0.74 2026-02-12 21:17:33 +00:00
Kvarkas
ac3d7e6366 added new option - build Self-Contained, this allow to run mR without installing .NET 2026-02-12 21:16:31 +00:00
Dimitrij
9fae2e066e Merge pull request #3138 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2026-02-12 10:30:06 +00:00
renovate[bot]
cf66e84d31 Update aws-sdk-net monorepo 2026-02-11 22:12:21 +00:00
Dimitrij
f7326aff62 Merge pull request #3137 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.Core to 4.0.3.13
2026-02-11 09:22:09 +00:00
Kvarkas
d4bca6b03d small fixes 2026-02-10 22:03:02 +00:00
renovate[bot]
1d86015f9d Update dependency AWSSDK.Core to 4.0.3.13 2026-02-10 21:19:54 +00:00
Dimitrij
5efcc653eb Merge pull request #3038 from mRemoteNG/copilot/fix-command-injection-vulnerability
Fix command injection vulnerabilities in Process.Start calls
2026-02-10 21:18:59 +00:00
Kvarkas
333588e101 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2026-02-10 21:18:29 +00:00
Kvarkas
4046681fc5 remove dependency what are now included with .Net 10
small fixes
2026-02-10 21:18:17 +00:00
Dimitrij
91c7df22b2 Update mRemoteNGTests/Connection/Protocol/ProtocolAnydeskTests.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-10 21:10:35 +00:00
Dimitrij
173b208eb1 Update mRemoteNG/UI/Forms/FrmAbout.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-10 21:10:12 +00:00
Dimitrij
50de37c3a4 Update mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-10 21:09:56 +00:00
Dimitrij
380e91de07 Update mRemoteNG/Connection/Protocol/AnyDesk/ProtocolAnyDesk.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-10 21:09:06 +00:00
Dimitrij
ba97933f33 Update mRemoteNG/UI/Forms/OptionsPages/NotificationsPage.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-10 21:08:46 +00:00
Dimitrij
539b761199 Merge pull request #3136 from mRemoteNG/renovate/major-github-artifact-actions
Update GitHub Artifact Actions (major)
2026-02-10 19:58:22 +00:00
renovate[bot]
aaff6e4548 Update GitHub Artifact Actions 2026-02-10 18:31:58 +00:00
Kvarkas
a1e3b34580 do release 2026-02-10 18:30:52 +00:00
Kvarkas
276585e379 upgrade so release include self contained version 2026-02-10 18:27:03 +00:00
Dimitrij
9242dc0faf Merge pull request #3135 from mRemoteNG/renovate/dotnet-monorepo
Update dotnet monorepo to 10.0.3
2026-02-10 18:13:22 +00:00
renovate[bot]
14d08d8d62 Update dotnet monorepo to 10.0.3 2026-02-10 17:56:13 +00:00
Dimitrij
421d8eb581 Merge pull request #3132 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.72
2026-02-10 11:37:36 +00:00
renovate[bot]
aed0006f1d Update dependency AWSSDK.EC2 to 4.0.72 2026-02-10 01:15:15 +00:00
Dimitrij
86d986a633 Merge pull request #3101 from mRemoteNG/copilot/fix-new-connection-error
Show error popup when connecting without hostname/IP
2026-02-01 20:18:51 +00:00
Dimitrij
5163aeb4d2 Change error message to warning for blank hostname 2026-02-01 20:18:11 +00:00
copilot-swe-agent[bot]
a103939c64 Address code review: fix typos, improve test robustness, use Language resources
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-01 19:59:39 +00:00
copilot-swe-agent[bot]
bcb8e05698 Update documentation for new popup error default setting
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-01 19:58:03 +00:00
copilot-swe-agent[bot]
2e74313f07 Change hostname validation to ErrorMsg and enable popup errors by default
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-01 19:56:53 +00:00
copilot-swe-agent[bot]
c9791454ec Initial plan 2026-02-01 19:52:25 +00:00
Dimitrij
b1c1696acb Merge pull request #3099 from jafin/fix/frmInputBox-autosize
Fix FrmInputBox to use dynamic sizing instead of fixed dimensions
2026-02-01 19:24:31 +00:00
Dimitrij
ab668ac677 Merge pull request #3097 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-x64-144.x
Update dependency chromiumembeddedframework.runtime.win-x64 to v144
2026-02-01 19:23:58 +00:00
Dimitrij
1777c4840a Merge pull request #3096 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-arm64-144.x
Update dependency chromiumembeddedframework.runtime.win-arm64 to v144
2026-02-01 19:23:44 +00:00
Dimitrij
90fcd672d8 Merge pull request #3095 from mRemoteNG/renovate/protobuf-monorepo
Update dependency Google.Protobuf to 3.33.5
2026-02-01 19:23:27 +00:00
Dimitrij
4226396cbf Merge pull request #3094 from mRemoteNG/renovate/microsoft.web.webview2-1.x
Update dependency Microsoft.Web.WebView2 to 1.0.3719.77
2026-02-01 19:23:11 +00:00
Dimitrij
3591ca0f4c Merge pull request #3093 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2026-02-01 19:22:55 +00:00
Jason Finch
09cbcccf30 Fix FrmInputBox to use dynamic sizing instead of fixed dimensions 2026-02-01 13:32:00 +10:00
renovate[bot]
4e36b5666e Update dependency chromiumembeddedframework.runtime.win-x64 to v144 2026-01-31 05:48:20 +00:00
renovate[bot]
308253a325 Update dependency chromiumembeddedframework.runtime.win-arm64 to v144 2026-01-31 05:48:13 +00:00
renovate[bot]
30bb4016b4 Update dependency Google.Protobuf to 3.33.5 2026-01-30 00:59:36 +00:00
renovate[bot]
e616ae16e1 Update aws-sdk-net monorepo 2026-01-30 00:59:30 +00:00
renovate[bot]
469528a07a Update dependency Microsoft.Web.WebView2 to 1.0.3719.77 2026-01-27 12:45:38 +00:00
Dimitrij
c7f831e9f9 Merge pull request #3089 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.67
2026-01-26 12:51:28 +00:00
Dimitrij
dd9922be45 Merge pull request #3087 from mRemoteNG/renovate/mysql.data-9.x
Update dependency MySql.Data to 9.6.0
2026-01-26 12:51:11 +00:00
Dimitrij
d0a468c22b Merge pull request #3090 from mRemoteNG/renovate/cucumber.messages-32.x
Update dependency Cucumber.Messages to v32
2026-01-26 12:50:56 +00:00
Dimitrij
ff5dbc88fe Merge pull request #3091 from mRemoteNG/renovate/gherkin-38.x
Update dependency Gherkin to v38
2026-01-26 12:50:42 +00:00
renovate[bot]
3a4ae9b098 Update dependency Cucumber.Messages to v32 2026-01-25 13:44:37 +00:00
renovate[bot]
2de24e534c Update dependency AWSSDK.EC2 to 4.0.67 2026-01-22 21:05:19 +00:00
renovate[bot]
59412a65e1 Update dependency Gherkin to v38 2026-01-22 02:30:57 +00:00
renovate[bot]
adedb6962f Update dependency MySql.Data to 9.6.0 2026-01-21 10:47:43 +00:00
Dimitrij
d3fa608ae9 Merge pull request #3086 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2026-01-21 10:47:03 +00:00
renovate[bot]
3159903875 Update aws-sdk-net monorepo 2026-01-21 01:59:43 +00:00
Dimitrij
31c28c4917 Merge pull request #3084 from mRemoteNG/renovate/microsoft.data.sqlclient-6.x
Update dependency Microsoft.Data.SqlClient to 6.1.4
2026-01-16 09:59:33 +00:00
Dimitrij
2c13f7c3a7 Merge pull request #3085 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2026-01-16 09:59:18 +00:00
renovate[bot]
725ee92147 Update aws-sdk-net monorepo 2026-01-16 01:27:24 +00:00
renovate[bot]
14406f79a2 Update dependency Microsoft.Data.SqlClient to 6.1.4 2026-01-15 18:42:41 +00:00
Kvarkas
3e202c3a19 lib update 2026-01-14 10:26:47 +00:00
Dimitrij
412c727e4c Merge pull request #3081 from mRemoteNG/renovate/dotnet-monorepo
Update dotnet monorepo to 10.0.2
2026-01-13 19:19:31 +00:00
renovate[bot]
31e7b9e443 Update dotnet monorepo to 10.0.2 2026-01-13 18:40:14 +00:00
Dimitrij
a18e292765 Merge pull request #3078 from mRemoteNG/renovate/protobuf-monorepo
Update dependency Google.Protobuf to 3.33.4
2026-01-13 11:23:46 +00:00
Dimitrij
258ea87f90 Merge pull request #3079 from mRemoteNG/renovate/cucumber.messages-31.x
Update dependency Cucumber.Messages to 31.2.0
2026-01-13 10:38:07 +00:00
renovate[bot]
fd9eabe1e6 Update dependency Google.Protobuf to 3.33.4 2026-01-12 21:55:32 +00:00
renovate[bot]
a9dd06df45 Update dependency Cucumber.Messages to 31.2.0 2026-01-12 00:24:29 +00:00
Dimitrij
dfc24b0cb2 Merge pull request #3076 from mRemoteNG/renovate/nunit3testadapter-6.x
Update dependency NUnit3TestAdapter to 6.1.0
2026-01-08 09:46:33 +00:00
Dimitrij
63f5325f29 Merge pull request #3075 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2026-01-08 09:46:04 +00:00
renovate[bot]
6f7949214b Update dependency NUnit3TestAdapter to 6.1.0 2026-01-07 21:23:22 +00:00
renovate[bot]
6cfec060a0 Update aws-sdk-net monorepo 2026-01-07 21:23:16 +00:00
Dimitrij
db733424ca Merge pull request #3074 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.EC2 to 4.0.65.1
2026-01-06 10:49:59 +00:00
renovate[bot]
49eab4d377 Update dependency AWSSDK.EC2 to 4.0.65.1 2026-01-06 01:42:04 +00:00
Dimitrij
53e5396031 Merge pull request #3073 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update dependency AWSSDK.Core to 4.0.3.7
2026-01-05 21:17:14 +00:00
renovate[bot]
99d01130bf Update dependency AWSSDK.Core to 4.0.3.7 2026-01-05 21:04:09 +00:00
Dimitrij
62ce6cd6e7 Merge pull request #3071 from mRemoteNG/renovate/nunit.consolerunner-3.x
Update dependency NUnit.ConsoleRunner to 3.22.0
2026-01-04 17:53:30 +00:00
renovate[bot]
35c66b0e4a Update dependency NUnit.ConsoleRunner to 3.22.0 2026-01-04 17:53:20 +00:00
Dimitrij
dafc05dc42 Merge pull request #3072 from mRemoteNG/renovate/zstdsharp.port-0.x
Update dependency ZstdSharp.Port to 0.8.7
2026-01-04 17:53:13 +00:00
Dimitrij
458a05ea5f Merge pull request #3070 from mRemoteNG/renovate/nunit.console-3.x
Update dependency NUnit.Console to 3.22.0
2026-01-04 17:52:43 +00:00
renovate[bot]
38acc1e960 Update dependency ZstdSharp.Port to 0.8.7 2026-01-03 17:58:37 +00:00
renovate[bot]
f83209a2b9 Update dependency NUnit.Console to 3.22.0 2026-01-03 01:59:37 +00:00
Dimitrij
9d1546c8b7 Merge pull request #3068 from mRemoteNG/copilot/fix-sql-injection-issue
Fix WQL injection vulnerability in InstalledWindowsUpdateChecker
2025-12-30 12:30:53 +00:00
copilot-swe-agent[bot]
dca2517cf0 Normalize digit-only KB IDs to include KB prefix
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-30 12:19:35 +00:00
copilot-swe-agent[bot]
ff54ca9015 Fix inaccurate comment in SanitizeKbId method
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-30 12:17:52 +00:00
copilot-swe-agent[bot]
76cb0a1e0b Address code review feedback - optimize regex and use Array.Empty
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-30 12:16:52 +00:00
copilot-swe-agent[bot]
c683854678 Add WQL injection prevention via KB ID sanitization
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-30 12:15:31 +00:00
copilot-swe-agent[bot]
e9d0a8aa69 Initial plan 2025-12-30 12:11:00 +00:00
Dimitrij
30cb1de711 Merge pull request #3064 from mRemoteNG/copilot/fix-ldap-query-injection
Fix LDAP injection vulnerability in Active Directory integration
2025-12-30 12:06:35 +00:00
Dimitrij
699b93e175 Merge pull request #3066 from mRemoteNG/copilot/setup-copilot-instructions
Add GitHub Copilot instructions for repository
2025-12-30 12:05:30 +00:00
copilot-swe-agent[bot]
c7df6f3715 Add comprehensive GitHub Copilot instructions
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-29 13:17:42 +00:00
copilot-swe-agent[bot]
f1d1a19779 Initial plan 2025-12-29 13:13:24 +00:00
copilot-swe-agent[bot]
469ca48592 Fix compilation issues and improve code review feedback
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-08 13:12:15 +00:00
copilot-swe-agent[bot]
208ce663b2 Address code review feedback - improve security validations
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-08 13:08:50 +00:00
copilot-swe-agent[bot]
843243c75e Add comprehensive tests for AnyDesk ID validation
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-08 13:06:16 +00:00
copilot-swe-agent[bot]
b7ed5a300d Fix command injection vulnerabilities in Process.Start calls
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2025-12-08 13:03:54 +00:00
copilot-swe-agent[bot]
415a649a76 Initial plan 2025-12-08 12:55:04 +00:00
50 changed files with 1725 additions and 319 deletions

242
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,242 @@
# GitHub Copilot Instructions for mRemoteNG
## Project Overview
mRemoteNG is an open-source, multi-protocol, tabbed remote connections manager for Windows. It's a fork of mRemote that allows users to view and manage all their remote connections (RDP, VNC, SSH, Telnet, HTTP/HTTPS, rlogin, Raw Socket, PowerShell remoting, AnyDesk) in a simple yet powerful interface.
## Technology Stack
- **Language**: C# with latest language version
- **Framework**: .NET 10.0 for Windows (net10.0-windows10.0.26100.0)
- **UI Framework**: Windows Forms with WPF support
- **Target Platforms**: x64 and ARM64
- **Testing Framework**: NUnit with NSubstitute for mocking
- **Build System**: MSBuild with .NET SDK-style projects
## Building the Project
### Prerequisites
- Visual Studio 2022 (version 17.14.12 or later)
- .NET 10.0 Desktop Runtime
- Windows 10/11 or Windows Server 2016+
### Build Commands
```powershell
# Restore NuGet packages
dotnet restore
# Build the solution
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=x64
# Or for ARM64
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=arm64
```
### Running Tests
The project uses NUnit for testing. Test projects are in `mRemoteNGTests/` directory.
```powershell
dotnet test mRemoteNGTests/mRemoteNGTests.csproj
```
## Code Organization
### Main Projects
- **mRemoteNG**: Main application project
- **mRemoteNGTests**: Unit and integration tests
- **mRemoteNGSpecs**: Specification tests
- **ObjectListView.NetCore**: Custom list view control
- **ExternalConnectors**: External protocol connector implementations
- **mRemoteNGDocumentation**: reStructuredText documentation
### Key Directories in mRemoteNG Project
- `App/`: Application startup and initialization
- `Connection/`: Connection models, protocols, and management
- `Config/`: Configuration and serialization (XML, CSV)
- `Container/`: Container/folder node implementations
- `Credential/`: Credential storage and repositories
- `Language/`: Localization resource files (.resx)
- `Security/`: Authentication, encryption, and password management
- `Tree/`: Connection tree UI and management
- `UI/`: User interface components and forms
- `Tools/`: Utility classes and helpers
## Code Style and Conventions
### EditorConfig Settings
The project uses EditorConfig (`.editorconfig` in `mRemoteNG/` directory) with these key rules:
- **Indentation**: 4 spaces (no tabs)
- **Line Endings**: CRLF (Windows-style)
- **Charset**: UTF-8 with BOM for C# files
- **New Lines**: Opening braces on new lines (Allman style)
- **Using Directives**: Sort System directives first
- **this. Qualifier**: Do not use `this.` prefix unless necessary
### Naming Conventions
- **Classes/Interfaces**: PascalCase (e.g., `ConnectionInfo`, `IInheritable`)
- **Methods**: PascalCase (e.g., `GetConnection`, `SaveToXml`)
- **Properties**: PascalCase (e.g., `ConnectionName`, `Port`)
- **Fields**: Use camelCase for private fields, consider `_` prefix for backing fields
- **Constants**: PascalCase
### Code Patterns
#### Inheritance System
mRemoteNG has a sophisticated property inheritance system for connection settings:
1. Properties can be inherited from parent containers/folders
2. Each inheritable property has a corresponding `Inherit<PropertyName>` boolean in `ConnectionInfoInheritance` class
3. Use `IInheritable` interface for objects that support inheritance
4. Example: `ConnectionFrameColor` property has `InheritConnectionFrameColor` boolean
#### Serialization
The project supports multiple serialization formats:
**XML Serialization** (`Config/Serializers/ConnectionSerializers/Xml/`):
- Node-based serializers (e.g., `XmlConnectionNodeSerializer28.cs`)
- Deserializers handle backward compatibility
- Attributes for properties and inheritance flags
- Always maintain backward compatibility - old files must still load
**CSV Serialization** (`Config/Serializers/ConnectionSerializers/Csv/`):
- Export connections to CSV format
- Include both value and inheritance columns
- Maintain column order consistency
#### Localization
All user-facing strings must be localized:
1. Add entries to `Language/Language.resx` (English base)
2. Use `Language.ResourceName` to access strings in code
3. Resource naming:
- Properties: `PropertyName` (e.g., `ConnectionFrameColor`)
- Descriptions: `PropertyDescription<PropertyName>`
- Enum values: `<EnumName><Value>` (e.g., `FrameColorRed`)
4. Provide clear, concise descriptions for PropertyGrid tooltips
### Common Patterns
#### Adding a New Connection Property
1. **Add enum** (if needed) in `Connection/` directory
2. **Add property** to `AbstractConnectionRecord.cs` or `ConnectionInfo.cs`
- Add `[Category("CategoryName")]` attribute for PropertyGrid grouping
- Add `[Description("Language.PropertyDescription<Name>")]` for tooltip
- Use appropriate TypeConverter if needed
3. **Add inheritance support** in `ConnectionInfoInheritance.cs`
4. **Update serializers**:
- XML: Update latest `XmlConnectionNodeSerializer*.cs` and `XmlConnectionsDeserializer.cs`
- CSV: Update `CsvConnectionsSerializerMremotengFormat.cs`
5. **Add localization** in `Language/Language.resx`
6. **Write tests** in `mRemoteNGTests/Connection/`
7. **Update documentation** in `mRemoteNGDocumentation/` if user-facing
#### UI Controls
- Prefer existing mRemoteNG patterns for UI controls
- Connection panels use `InterfaceControl` base class
- Custom painting: Override `OnPaint` or handle `Paint` event
- Follow Windows Forms best practices
## Testing Guidelines
### Test Structure
- Use NUnit's `[Test]` attribute for test methods
- Use `[SetUp]` and `[TearDown]` for test initialization/cleanup
- Use `[TestCase]` for parameterized tests
- Use NSubstitute for mocking: `Substitute.For<IInterface>()`
### Test Naming
- Use descriptive names: `MethodName_Scenario_ExpectedBehavior`
- Example: `ConnectionInfo_SetPassword_EncryptsValue`
### Test Organization
Mirror the main project structure in `mRemoteNGTests/`:
- `Connection/` for connection tests
- `Security/` for security tests
- `Config/` for configuration/serialization tests
## Important Notes and Pitfalls
### Backward Compatibility
- **Critical**: Never break loading of old connection files
- Always provide default values for new properties
- Test that files without new attributes still load correctly
### Security
- Sensitive data (passwords, credentials) must be encrypted
- Use existing security providers in `Security/` namespace
- Never log or expose credentials in plain text
### Performance
- Connection tree can contain thousands of nodes
- Optimize for large connection files
- Avoid unnecessary UI refreshes
- Use async/await for I/O operations
### Platform-Specific Code
- The project targets both x64 and ARM64
- Avoid platform-specific code unless absolutely necessary
- Test on both platforms when possible
### External Dependencies
- PuTTY for SSH/Telnet (bundled)
- Terminal Service Client for RDP
- Various protocol-specific libraries (see CREDITS.md)
## Documentation
### User Documentation
- Located in `mRemoteNGDocumentation/`
- Written in reStructuredText (.rst)
- Follows ReadTheDocs format
- Include screenshots and examples for new features
### Code Documentation
- Use XML documentation comments for public APIs
- Document complex algorithms and business logic
- Keep implementation notes in `IMPLEMENTATION_NOTES.md` for significant features
## Common Tasks
### Adding a Protocol
1. Create protocol implementation in `Connection/Protocol/`
2. Add protocol enum value to protocol types
3. Implement connection logic
4. Add UI controls if needed
5. Update documentation
### Adding a Theme
1. Add theme files to `Themes/` directory
2. Update theme manager
3. Add documentation in `mRemoteNGDocumentation/themes/`
### Updating Dependencies
- Dependencies are centrally managed in `Directory.Packages.props`
- Use `dotnet restore` after updating
- Test thoroughly after dependency updates
## Version and Release
- Current development version: 1.78.2-dev
- Version is set in `mRemoteNG.csproj`
- Builds are automated via GitHub Actions (`.github/workflows/`)
- Changelog maintained in `CHANGELOG.md`
## Getting Help
- Project website: https://mremoteng.org
- Documentation: https://mremoteng.readthedocs.io
- GitHub Issues: Report bugs and feature requests
- Community: Reddit r/mRemoteNG, Matrix chat
## Summary
When contributing to mRemoteNG:
1. Follow the existing code style (EditorConfig)
2. Maintain backward compatibility with old connection files
3. Localize all user-facing strings
4. Write tests for new functionality
5. Update documentation for user-facing changes
6. Test on both x64 and ARM64 if possible
7. Keep security and performance in mind

View File

@@ -1,4 +1,4 @@
name: Build_and_Release_mR-NB
name: Build_and_Release_mR-NB_MultiDeploy
on:
push:
branches:
@@ -19,12 +19,28 @@ jobs:
strategy:
matrix:
include:
# Framework-Dependent builds (requires .NET runtime on user machine)
- runner: windows-latest
platform: x64
arch: x64
deployment: framework-dependent
deploy_suffix: FD
- runner: windows-11-arm
platform: ARM64
arch: arm64
deployment: framework-dependent
deploy_suffix: FD
# Self-Contained builds (includes .NET runtime)
- runner: windows-latest
platform: x64
arch: x64
deployment: self-contained
deploy_suffix: SC
- runner: windows-11-arm
platform: ARM64
arch: arm64
deployment: self-contained
deploy_suffix: SC
runs-on: ${{ matrix.runner }}
# Only run if:
# - manual dispatch, OR
@@ -36,13 +52,18 @@ jobs:
steps:
- name: (01) Checkout Repository
uses: actions/checkout@v6
- name: (02) Setup MSBuild
- name: (02) Install .NET 10 SDK
uses: actions/setup-dotnet@v5
with:
dotnet-version: '10.0.x'
- name: (03) Setup MSBuild
uses: microsoft/setup-msbuild@v2
with:
vs-version: '17.14.12'
vs-version: '18.0'
- name: (03) Install and run dotnet-t4 to transform T4 templates
- name: (04) Install and run dotnet-t4 to transform T4 templates
shell: pwsh
run: |
dotnet tool install --global dotnet-t4
@@ -56,24 +77,32 @@ jobs:
env:
PLATFORM: '${{ matrix.platform }}'
- name: (04) Cache NuGet Packages
- name: (05) Cache NuGet Packages
uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-${{ matrix.arch }}-nuget-${{ hashFiles('**/*.csproj') }}
key: ${{ runner.os }}-${{ matrix.arch }}-${{ matrix.deployment }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-${{ matrix.arch }}-${{ matrix.deployment }}-nuget-
${{ runner.os }}-${{ matrix.arch }}-nuget-
- name: (05) Restore NuGet Packages
- name: (06) Restore NuGet Packages
shell: pwsh
run: dotnet restore
- name: (06) Build Release
- name: (07) Build Framework-Dependent Release
if: matrix.deployment == 'framework-dependent'
shell: pwsh
run: |
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" -p:Configuration="Release" -p:Platform=${{ matrix.platform }} /verbosity:minimal
- name: (07) Release Information
- name: (08) Build Self-Contained Release
if: matrix.deployment == 'self-contained'
shell: pwsh
run: |
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" /t:Publish /p:Configuration="Release Self-Contained" -p:Platform=${{ matrix.platform }} /verbosity:minimal /p:SelfContained=true /p:PublishDir="bin\${{ matrix.platform }}\Release"
- name: (09) Release Information
id: version
shell: pwsh
run: |
@@ -86,7 +115,7 @@ jobs:
throw "Could not extract version and build number"
}
$date = Get-Date -Format "yyyyMMdd"
$zipName = "mRemoteNG-$date-v$version-NB-$build-${{ matrix.arch }}.zip"
$zipName = "mRemoteNG-$date-v$version-NB-$build-${{ matrix.arch }}-${{ matrix.deploy_suffix }}.zip"
$tag = "$date-v$version-NB-($build)"
$message = git log -1 --pretty=%B
echo "message=$message" >> $env:GITHUB_OUTPUT
@@ -94,9 +123,9 @@ jobs:
echo "version=$version" >> $env:GITHUB_OUTPUT
echo "build=$build" >> $env:GITHUB_OUTPUT
echo "tag=$tag" >> $env:GITHUB_OUTPUT
$version = "${{ steps.version.outputs.version }}"
echo "deployment=${{ matrix.deployment }}" >> $env:GITHUB_OUTPUT
- name: (08) Extract Changelog Section
- name: (10) Extract Changelog Section
id: changelog
shell: pwsh
run: |
@@ -127,30 +156,128 @@ jobs:
echo "log<<EOF" >> $env:GITHUB_OUTPUT
echo $joined >> $env:GITHUB_OUTPUT
echo "EOF" >> $env:GITHUB_OUTPUT
echo "log=$escaped"
- name: (09) Create Zip File
- name: (11) Create Zip File
shell: pwsh
run: |
$sourceDir = "$Env:GITHUB_WORKSPACE\mRemoteNG\bin\${{ matrix.platform }}\Release"
Compress-Archive -Path "$sourceDir\*" -DestinationPath ${{ steps.version.outputs.zipname }}
echo "File: ${{ steps.version.outputs.zipname }}"
# Show file size
$fileSize = (Get-Item ${{ steps.version.outputs.zipname }}).Length / 1MB
echo "Size: $([math]::Round($fileSize, 2)) MB"
- name: (10) Create release
id: create_release
- name: (10) Upload artifacts for combination
uses: actions/upload-artifact@v6
with:
name: release-${{ matrix.arch }}-${{ matrix.deploy_suffix }}
path: ${{ steps.version.outputs.zipname }}
retention-days: 1
Create-Combined-Release:
needs: NB-Build-and-Release
runs-on: windows-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Download all artifacts
uses: actions/download-artifact@v7
with:
path: artifacts
- name: Extract version info
id: version
shell: pwsh
run: |
$assemblyInfoPath = "${{ github.workspace }}\mRemoteNG\Properties\AssemblyInfo.cs"
$line = Get-Content $assemblyInfoPath | Where-Object { $_ -match 'AssemblyVersion\("(.+?)"\)' }
if ($line -match 'AssemblyVersion\("(?<ver>\d+\.\d+\.\d+)\.(?<build>\d+)"\)') {
$version = $matches['ver']
$build = $matches['build']
} else {
throw "Could not extract version and build number"
}
$date = Get-Date -Format "yyyyMMdd"
$tag = "$date-v$version-NB-($build)"
$message = git log -1 --pretty=%B
echo "version=$version" >> $env:GITHUB_OUTPUT
echo "build=$build" >> $env:GITHUB_OUTPUT
echo "tag=$tag" >> $env:GITHUB_OUTPUT
echo "message=$message" >> $env:GITHUB_OUTPUT
- name: Extract Changelog
id: changelog
shell: pwsh
run: |
$changelogPath = "$env:GITHUB_WORKSPACE\CHANGELOG.md"
$lines = Get-Content $changelogPath
$startIndex = -1
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match '^## \[') {
$startIndex = $i
break
}
}
if ($startIndex -eq -1) {
throw "No version header found in CHANGELOG.md"
}
$section = @()
for ($i = $startIndex + 1; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match '^## ') {
break
}
$section += $lines[$i]
}
$joined = $section -join "`n"
echo "log<<EOF" >> $env:GITHUB_OUTPUT
echo $joined >> $env:GITHUB_OUTPUT
echo "EOF" >> $env:GITHUB_OUTPUT
- name: Collect all release files
shell: pwsh
run: |
Get-ChildItem -Path artifacts -Recurse -Filter "*.zip" | ForEach-Object {
Copy-Item $_.FullName -Destination .
echo "Found: $($_.Name)"
}
- name: Create combined release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.version.outputs.tag }}
tag_name: ${{ steps.version.outputs.tag }}
name: "mRemoteNG ${{ steps.version.outputs.version }} NB ${{ steps.version.outputs.build }}"
files: ${{ steps.version.outputs.zipname }}
files: '*.zip'
body: |
Changes in this Release:
## mRemoteNG ${{ steps.version.outputs.version }} NB Build ${{ steps.version.outputs.build }}
### 📦 Available Downloads
**Framework-Dependent (FD)** - Requires .NET 10 Runtime installed:
- Smaller download size (~15-25 MB)
- Requires .NET 10 Desktop Runtime on user machine
- Files: `*-FD.zip`
**Self-Contained (SC)** - Portable, no installation required:
- Larger download size (~80-150 MB)
- Includes .NET 10 runtime - no installation needed
- Files: `*-SC.zip`
Both versions are available for **x64** and **ARM64** architectures.
---
### 📝 Changes in this Release
${{ steps.changelog.outputs.log }}
Last Commit Message:
### 💬 Last Commit Message
${{ steps.version.outputs.message }}
draft: false

167
DEPLOYMENT_OPTIONS.md Normal file
View File

@@ -0,0 +1,167 @@
# mRemoteNG Deployment Options
This document explains the two deployment options for mRemoteNG and how to build each version.
## Deployment Types
### 1. Framework-Dependent (FD)
**File suffix: `-FD.zip`**
- **Size**: ~15-25 MB
- **Requirements**: User must have .NET 10 Desktop Runtime installed
- **Startup**: Application checks for .NET runtime and prompts user to download if missing
- **Use case**: Standard release for users comfortable installing prerequisites
### 2. Self-Contained (SC)
**File suffix: `-SC.zip`**
- **Size**: ~80-150 MB
- **Requirements**: None - includes .NET 10 runtime
- **Startup**: No runtime checks performed (runtime is bundled)
- **Use case**: Portable version for users who want zero installation/configuration
## Building Locally
### Framework-Dependent Build
```powershell
# x64
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=x64
# ARM64
msbuild mRemoteNG.sln -p:Configuration=Release -p:Platform=ARM64
```
### Self-Contained Build
```powershell
# x64
dotnet publish mRemoteNG\mRemoteNG.csproj `
--configuration Release `
--runtime win-x64 `
--self-contained true `
-p:Platform=x64 `
-p:PublishSingleFile=false `
-p:PublishReadyToRun=true `
-p:DefineConstants="SELF_CONTAINED"
# ARM64
dotnet publish mRemoteNG\mRemoteNG.csproj `
--configuration Release `
--runtime win-arm64 `
--self-contained true `
-p:Platform=ARM64 `
-p:PublishSingleFile=false `
-p:PublishReadyToRun=true `
-p:DefineConstants="SELF_CONTAINED"
```
## GitHub Actions Workflow
The new workflow file `Build_and_Release_mR-NB-MultiDeploy.yml` automatically builds all four variants:
1. **x64 Framework-Dependent** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-x64-FD.zip`
2. **x64 Self-Contained** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-x64-SC.zip`
3. **ARM64 Framework-Dependent** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-arm64-FD.zip`
4. **ARM64 Self-Contained** - `mRemoteNG-YYYYMMDD-vX.X.X-NB-XXXX-arm64-SC.zip`
### Workflow Triggers
The workflow runs when:
- You push to `v1.78.2-dev` branch with commit message containing "NB release"
- You manually trigger via workflow_dispatch
### Release Output
All four zip files are uploaded to a single GitHub Release with clear descriptions:
- Framework-Dependent versions are marked as requiring .NET 10 Runtime
- Self-Contained versions are marked as portable/no installation needed
## Code Changes
### ProgramRoot.cs
The `MainAsync` method now uses conditional compilation:
```csharp
#if !SELF_CONTAINED
// Runtime check code only included in Framework-Dependent builds
// Checks for .NET Runtime and Visual C++ Redistributable
#endif
```
When building with `-p:DefineConstants="SELF_CONTAINED"`, the runtime checks are completely excluded from the compiled binary.
## Recommendations
### For Users
**Choose Framework-Dependent (FD) if:**
- You don't mind installing .NET 10 Desktop Runtime once
- You want smaller download size
- You're using multiple .NET applications (runtime is shared)
**Choose Self-Contained (SC) if:**
- You want zero installation/setup
- You need portable deployment (USB drive, restricted environments)
- You don't want to deal with prerequisites
### For Distribution
Consider offering both options:
- Make Framework-Dependent the **default/recommended** option (smaller, faster updates)
- Offer Self-Contained as **portable alternative** for special use cases
## Technical Details
### Compilation Symbols
- Framework-Dependent builds: No special symbols
- Self-Contained builds: `SELF_CONTAINED` symbol defined
### Runtime Identifiers
- x64: `win-x64`
- ARM64: `win-arm64`
### Publish Options
Self-contained builds use these optimizations:
- `PublishReadyToRun=true` - AOT compilation for faster startup
- `IncludeNativeLibrariesForSelfExtract=true` - Bundle native dependencies
- `PublishSingleFile=false` - Keep files separate for better compatibility with mRemoteNG's plugin system
## File Size Comparison
Typical build sizes:
| Version | Framework-Dependent | Self-Contained |
|---------|---------------------|----------------|
| x64 | ~18 MB | ~95 MB |
| ARM64 | ~18 MB | ~95 MB |
*Note: Self-contained includes entire .NET 10 runtime (~80 MB overhead)*
## Testing
### Framework-Dependent Build
1. Uninstall .NET 10 Runtime (if installed)
2. Run mRemoteNG.exe
3. Should prompt to download .NET 10 Runtime
4. Install runtime and verify app launches
### Self-Contained Build
1. Uninstall .NET 10 Runtime (if installed)
2. Run mRemoteNG.exe
3. Should launch immediately without runtime check
4. Verify full functionality
## Migration from Old Workflow
The original workflow `Build_and_Release_mR-NB.yml` is preserved. To migrate:
1. Rename or remove old workflow: `Build_and_Release_mR-NB.yml`
2. Rename new workflow: `Build_and_Release_mR-NB-MultiDeploy.yml``Build_and_Release_mR-NB.yml`
3. Commit and push with "NB release" in message

View File

@@ -2,47 +2,47 @@
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
<NoWarn>$(NoWarn);NU1507</NoWarn>
<NoWarn>$(NoWarn);NU1507;NU1701</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="AWSSDK.Core" Version="4.0.3.6" />
<PackageVersion Include="AWSSDK.EC2" Version="4.0.65" />
<PackageVersion Include="AWSSDK.Core" Version="4.0.3.14" />
<PackageVersion Include="AWSSDK.EC2" Version="4.0.76" />
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.6.2" />
<PackageVersion Include="Castle.Core" Version="5.2.1" />
<PackageVersion Include="ConsoleControl" Version="1.3.0" />
<PackageVersion Include="ConsoleControlAPI" Version="1.3.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="Cucumber.Messages" Version="31.1.0" />
<PackageVersion Include="coverlet.collector" Version="8.0.0" />
<PackageVersion Include="Cucumber.Messages" Version="32.0.1" />
<PackageVersion Include="DockPanelSuite" Version="3.1.1" />
<PackageVersion Include="DockPanelSuite.ThemeVS2015" Version="3.1.1" />
<PackageVersion Include="envdte" Version="17.14.40260" />
<PackageVersion Include="Gherkin" Version="37.0.1" />
<PackageVersion Include="Google.Protobuf" Version="3.33.2" />
<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="Microsoft.Data.SqlClient" Version="6.1.3" />
<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" />
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="10.0.3" />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="Microsoft.NETCore.Platforms" Version="7.0.4" />
<PackageVersion Include="Microsoft.NETCore.Targets" Version="5.0.0" />
<PackageVersion Include="Microsoft.VisualStudio.TextTemplating.VSHost" Version="17.14.40265" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3650.58" />
<PackageVersion Include="Microsoft.Web.WebView2" Version="1.0.3800.47" />
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
<PackageVersion Include="Microsoft-WindowsAPICodePack-Shell" Version="1.1.5" />
<PackageVersion Include="MySql.Data" Version="9.5.0" />
<PackageVersion Include="MySql.Data" Version="9.6.0" />
<PackageVersion Include="NETStandard.Library" Version="2.0.3" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="Newtonsoft.Json.Schema" Version="4.0.1" />
<PackageVersion Include="NSubstitute" Version="5.3.0" />
<PackageVersion Include="NUnit" Version="4.4.0" />
<PackageVersion Include="NUnit.Console" Version="3.21.1" />
<PackageVersion Include="NUnit.ConsoleRunner" Version="3.21.1" />
<PackageVersion Include="NUnit" Version="4.5.0" />
<PackageVersion Include="NUnit.Console" Version="3.22.0" />
<PackageVersion Include="NUnit.ConsoleRunner" Version="3.22.0" />
<PackageVersion Include="NUnit.Extension.TeamCityEventListener" Version="1.0.10" />
<PackageVersion Include="NUnit.Runners" Version="3.12.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="6.0.1" />
<PackageVersion Include="NUnit3TestAdapter" Version="6.1.0" />
<PackageVersion Include="OpenCover" Version="4.7.1221" />
<PackageVersion Include="Renci.SshNet.Async" Version="1.4.0" />
<PackageVersion Include="ReportGenerator" Version="5.5.1" />
@@ -58,18 +58,18 @@
<PackageVersion Include="runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl" Version="4.3.3" />
<PackageVersion Include="SSH.NET" Version="2025.1.0" />
<PackageVersion Include="System.Buffers" Version="4.6.1" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.1" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.1" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="10.0.3" />
<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.Diagnostics.DiagnosticSource" Version="10.0.1" />
<PackageVersion Include="System.Drawing.Common" Version="10.0.1" />
<PackageVersion Include="System.Diagnostics.EventLog" Version="10.0.1" />
<PackageVersion Include="System.DirectoryServices" Version="10.0.1" />
<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" />
<PackageVersion Include="System.DirectoryServices" Version="10.0.3" />
<PackageVersion Include="System.Dynamic.Runtime" Version="4.3.0" />
<PackageVersion Include="System.IO.Pipelines" Version="10.0.1" />
<PackageVersion Include="System.Formats.Asn1" Version="10.0.1" />
<PackageVersion Include="System.Management" Version="10.0.1" />
<PackageVersion Include="System.IO.Pipelines" Version="10.0.3" />
<PackageVersion Include="System.Formats.Asn1" Version="10.0.3" />
<PackageVersion Include="System.Management" Version="10.0.3" />
<PackageVersion Include="System.Memory" Version="4.6.3" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Net.Primitives" Version="4.3.1" />
@@ -77,7 +77,7 @@
<PackageVersion Include="System.Reflection.Emit" Version="4.7.0" />
<PackageVersion Include="System.Reflection.Emit.ILGeneration" Version="4.7.0" />
<PackageVersion Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
<PackageVersion Include="System.Reflection.Metadata" Version="10.0.1" />
<PackageVersion Include="System.Reflection.Metadata" Version="10.0.3" />
<PackageVersion Include="System.Reflection.TypeExtensions" Version="4.7.0" />
<PackageVersion Include="System.Resources.ResourceManager" Version="4.3.0" />
<PackageVersion Include="System.Runtime" Version="4.3.1" />
@@ -87,19 +87,19 @@
<PackageVersion Include="System.Security.Cryptography.Algorithms" Version="4.3.1" />
<PackageVersion Include="System.Security.Cryptography.Cng" Version="5.0.0" />
<PackageVersion Include="System.Security.Cryptography.OpenSsl" Version="5.0.0" />
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="10.0.1" />
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="10.0.3" />
<PackageVersion Include="System.Security.Cryptography.X509Certificates" Version="4.3.2" />
<PackageVersion Include="System.Security.Permissions" Version="10.0.1" />
<PackageVersion Include="System.Security.Permissions" Version="10.0.3" />
<PackageVersion Include="System.Security.Principal.Windows" Version="5.0.0" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.1" />
<PackageVersion Include="System.Text.Json" Version="10.0.1" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="10.0.3" />
<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.Windows.Extensions" Version="10.0.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" />
<PackageVersion Include="VncSharpCore" Version="1.2.1" />
<PackageVersion Include="ZstdSharp.Port" Version="0.8.6" />
<PackageVersion Include="ZstdSharp.Port" Version="0.8.7" />
</ItemGroup>
</Project>

View File

@@ -1,18 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<UseWindowsForms>True</UseWindowsForms>
<Platforms>x64;arm64</Platforms>
<Configurations>Debug;Release;Debug Portable;Release Portable;Deploy to github</Configurations>
<SupportedOSPlatformVersion>10.0.26100.0</SupportedOSPlatformVersion>
<Configurations>Debug;Release;Debug Portable;Release Self-Contained;Deploy to github</Configurations>
<SupportedOSPlatformVersion>10.0.22621.0</SupportedOSPlatformVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Portable|x64'">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Self-Contained|x64'">
<Optimize>True</Optimize>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Portable|arm64'">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Self-Contained|arm64'">
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
<Deterministic>false</Deterministic>
<RootNamespace>BrightIdeasSoftware</RootNamespace>
<AssemblyName>ObjectListView</AssemblyName>
@@ -35,8 +35,4 @@
<Folder Include="Resources\" />
<Folder Include="Rendering\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" />
</ItemGroup>
</Project>

View File

@@ -1906,7 +1906,7 @@ namespace BrightIdeasSoftware
[Category("ObjectListView"),
Description("The image list from which group header will take their images"),
DefaultValue(null)]
public ImageList GroupImageList
public new ImageList GroupImageList
{
get { return this.groupImageList; }
set

View File

@@ -645,7 +645,6 @@ namespace BrightIdeasSoftware
/// Mess with the basic message pump of the tooltip
/// </summary>
/// <param name="msg"></param>
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
override protected void WndProc(ref Message msg) {
//System.Diagnostics.Trace.WriteLine(String.Format("xx {0:x}", msg.Msg));
switch (msg.Msg) {
@@ -697,5 +696,4 @@ namespace BrightIdeasSoftware
#endregion
}
}

View File

@@ -101,7 +101,7 @@ namespace BrightIdeasSoftware
/// <para>
/// Although it isn't documented, .NET virtual lists cannot have checkboxes. This class codes around this limitation,
/// but you must use the functions provided by ObjectListView: CheckedObjects, CheckObject(), UncheckObject() and their friends.
/// If you use the normal check box properties (CheckedItems or CheckedIndicies), they will throw an exception, since the
/// If you use the normal check box properties (CheckedItems or CheckedIndicie), they will throw an exception, since the
/// list is in virtual mode, and .NET "knows" it can't handle checkboxes in virtual mode.
/// </para>
/// <para>Due to the limits of the underlying Windows control, virtual lists do not trigger ItemCheck/ItemChecked events.
@@ -155,7 +155,7 @@ namespace BrightIdeasSoftware
/// <para>
/// This property returns a simple collection. Changes made to the returned
/// collection do NOT affect the list. This is different to the behaviour of
/// CheckedIndicies collection.
/// CheckedIndicie collection.
/// </para>
/// <para>
/// When getting CheckedObjects, the performance of this method is O(n) where n is the number of checked objects.
@@ -405,8 +405,6 @@ namespace BrightIdeasSoftware
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_virtualListSize")]
private static extern ref int GetVirtualListSizeField(ListView listView);
static private FieldInfo virtualListSizeFieldInfo;
#endregion
#region OLV accessing

View File

@@ -1,4 +1,4 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31912.275
@@ -13,8 +13,8 @@ Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|arm64 = Debug|arm64
Debug|x64 = Debug|x64
Release Installer and Portable|arm64 = Release Installer and Portable|arm64
Release Installer and Portable|x64 = Release Installer and Portable|x64
Release Self-Contained|arm64 = Release Self-Contained|arm64
Release Self-Contained|x64 = Release Self-Contained|x64
Release|arm64 = Release|arm64
Release|x64 = Release|x64
EndGlobalSection
@@ -23,10 +23,10 @@ Global
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|arm64.Build.0 = Debug|arm64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|x64.ActiveCfg = Debug|x64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|x64.Build.0 = Debug|x64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer and Portable|arm64.ActiveCfg = Release Portable|arm64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer and Portable|arm64.Build.0 = Release Portable|arm64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer and Portable|x64.ActiveCfg = Release Portable|x64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer and Portable|x64.Build.0 = Release Portable|x64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Self-Contained|arm64.ActiveCfg = Release Self-Contained|arm64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Self-Contained|arm64.Build.0 = Release Self-Contained|arm64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Self-Contained|x64.ActiveCfg = Release Self-Contained|x64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Self-Contained|x64.Build.0 = Release Self-Contained|x64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|arm64.ActiveCfg = Release|arm64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|arm64.Build.0 = Release|arm64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|x64.ActiveCfg = Release|x64
@@ -35,10 +35,10 @@ Global
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Debug|arm64.Build.0 = Debug|arm64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Debug|x64.ActiveCfg = Debug|x64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Debug|x64.Build.0 = Debug|x64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Installer and Portable|arm64.ActiveCfg = Release Portable|arm64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Installer and Portable|arm64.Build.0 = Release Portable|arm64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Installer and Portable|x64.ActiveCfg = Release Portable|x64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Installer and Portable|x64.Build.0 = Release Portable|x64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Self-Contained|arm64.ActiveCfg = Release Self-Contained|arm64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Self-Contained|arm64.Build.0 = Release Self-Contained|arm64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Self-Contained|x64.ActiveCfg = Release Self-Contained|x64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Self-Contained|x64.Build.0 = Release Self-Contained|x64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release|arm64.ActiveCfg = Release|arm64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release|arm64.Build.0 = Release|arm64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release|x64.ActiveCfg = Release|x64
@@ -47,10 +47,10 @@ Global
{5718734C-03AC-4954-89B1-1723CF03AF10}.Debug|arm64.Build.0 = Debug|Any CPU
{5718734C-03AC-4954-89B1-1723CF03AF10}.Debug|x64.ActiveCfg = Debug|Any CPU
{5718734C-03AC-4954-89B1-1723CF03AF10}.Debug|x64.Build.0 = Debug|Any CPU
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Installer and Portable|arm64.ActiveCfg = Release|Any CPU
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Installer and Portable|arm64.Build.0 = Release|Any CPU
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Installer and Portable|x64.ActiveCfg = Release|Any CPU
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Installer and Portable|x64.Build.0 = Release|Any CPU
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Self-Contained|arm64.ActiveCfg = Release|Any CPU
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Self-Contained|arm64.Build.0 = Release|Any CPU
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Self-Contained|x64.ActiveCfg = Release|Any CPU
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release Self-Contained|x64.Build.0 = Release|Any CPU
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release|arm64.ActiveCfg = Release|Any CPU
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release|arm64.Build.0 = Release|Any CPU
{5718734C-03AC-4954-89B1-1723CF03AF10}.Release|x64.ActiveCfg = Release|Any CPU

View File

@@ -24,7 +24,6 @@ namespace mRemoteNG.App
public static class ProgramRoot
{
private static Mutex? _mutex;
private static FrmSplashScreenNew _frmSplashScreen = null;
private static string customResourcePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Languages");
private static System.Threading.Thread? _wpfSplashThread;
@@ -41,6 +40,9 @@ namespace mRemoteNG.App
{
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
#if !SELF_CONTAINED
// Runtime checks only needed for framework-dependent deployments
// Self-contained builds include the runtime, so no check is needed
string? installedVersion = DotNetRuntimeCheck.GetLatestDotNetRuntimeVersion();
//installedVersion = ""; // Force check for testing purposes
@@ -106,6 +108,7 @@ namespace mRemoteNG.App
{
Environment.Exit(0);
}
#endif
Lazy<bool> singleInstanceOption = new(() => Properties.OptionsStartupExitPage.Default.SingleInstance);
if (singleInstanceOption.Value)

View File

@@ -121,7 +121,11 @@ namespace mRemoteNG.App
private static void RunUpdateFile()
{
if (UpdatePending)
{
// Validate the update file path to prevent command injection
Tools.PathValidator.ValidateExecutablePathOrThrow(_updateFilePath, nameof(_updateFilePath));
Process.Start(new ProcessStartInfo(_updateFilePath) { UseShellExecute = true });
}
}
}
}

View File

@@ -55,7 +55,6 @@ namespace mRemoteNG.Config.Settings
SetShowSystemTrayIcon();
SetAutoSave();
LoadExternalAppsFromXml();
SetAlwaysShowPanelTabs();
if (Properties.App.Default.ResetToolbars)
SetToolbarsDefault();
@@ -68,12 +67,6 @@ namespace mRemoteNG.Config.Settings
}
}
private static void SetAlwaysShowPanelTabs()
{
if (Properties.OptionsTabsPanelsPage.Default.AlwaysShowPanelTabs)
FrmMain.Default.pnlDock.DocumentStyle = DocumentStyle.DockingWindow;
}
private void SetSupportedCulture()
{

View File

@@ -348,6 +348,7 @@ namespace mRemoteNG.Connection
private static void SetConnectionFormEventHandlers(ProtocolBase newProtocol, Form connectionForm)
{
newProtocol.Closed += ((ConnectionWindow)connectionForm).Prot_Event_Closed;
newProtocol.TitleChanged += ((ConnectionWindow)connectionForm).Prot_Event_TitleChanged;
}
private void SetConnectionEventHandlers(ProtocolBase newProtocol)
@@ -465,4 +466,4 @@ namespace mRemoteNG.Connection
#endregion
}
}
}

View File

@@ -211,7 +211,17 @@ namespace mRemoteNG.Connection.Protocol.AnyDesk
// Username field is optional and not used in the CLI (reserved for future use)
// Password field is piped via stdin when --with-password flag is used
string anydeskId = _connectionInfo.Hostname.Trim();
string arguments = $"{anydeskId}";
// Validate AnyDesk ID to prevent command injection
// AnyDesk IDs are numeric or alphanumeric with @ and - characters for aliases
if (!IsValidAnydeskId(anydeskId))
{
Runtime.MessageCollector?.AddMessage(MessageClass.ErrorMsg,
"Invalid AnyDesk ID format. Only alphanumeric characters, @, -, _, and . are allowed.", true);
return false;
}
string arguments = anydeskId;
// Add --with-password flag if password is provided
bool hasPassword = !string.IsNullOrEmpty(_connectionInfo.Password);
@@ -242,27 +252,45 @@ namespace mRemoteNG.Connection.Protocol.AnyDesk
return false;
}
}
private bool IsValidAnydeskId(string anydeskId)
{
if (string.IsNullOrWhiteSpace(anydeskId))
return false;
// AnyDesk IDs can be:
// - Pure numeric (e.g., 123456789)
// - Alphanumeric with @ for aliases (e.g., alias@ad)
// - May contain hyphens and underscores in aliases
// Reject any characters that could be used for command injection
foreach (char c in anydeskId)
{
if (!char.IsLetterOrDigit(c) && c != '@' && c != '-' && c != '_' && c != '.')
{
return false;
}
}
return true;
}
private bool StartAnydeskWithPassword(string anydeskPath, string arguments)
{
try
{
// Use PowerShell to pipe the password to AnyDesk
// This is the recommended way according to AnyDesk documentation
string escapedPassword = _connectionInfo.Password.Replace("'", "''");
string powershellCommand = $"echo '{escapedPassword}' | & '{anydeskPath}' {arguments}";
// Start AnyDesk and pipe the password directly to stdin
// This avoids command injection vulnerabilities from using PowerShell
_process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "powershell.exe",
Arguments = $"-WindowStyle Hidden -Command \"{powershellCommand}\"",
FileName = anydeskPath,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true,
CreateNoWindow = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
RedirectStandardInput = false
RedirectStandardInput = true
},
EnableRaisingEvents = true
};
@@ -270,8 +298,23 @@ namespace mRemoteNG.Connection.Protocol.AnyDesk
_process.Exited += ProcessExited;
_process.Start();
// Write the password to stdin and close the stream
// AnyDesk expects the password on stdin when --with-password is used
try
{
if (_process.StandardInput != null)
{
_process.StandardInput.WriteLine(_connectionInfo.Password);
_process.StandardInput.Close();
}
}
catch (Exception ex)
{
Runtime.MessageCollector?.AddMessage(MessageClass.WarningMsg,
$"Failed to send password to AnyDesk: {ex.Message}", true);
}
// Wait for the AnyDesk window to appear
// Note: The window belongs to the AnyDesk process, not PowerShell
if (!WaitForAnydeskWindow())
{
Runtime.MessageCollector?.AddMessage(MessageClass.WarningMsg,

View File

@@ -305,6 +305,15 @@ namespace mRemoteNG.Connection.Protocol
remove => ClosedEvent = (ClosedEventHandler)Delegate.Remove(ClosedEvent, value);
}
public delegate void TitleChangedEventHandler(object sender, string newTitle);
private TitleChangedEventHandler TitleChangedEvent;
public event TitleChangedEventHandler TitleChanged
{
add => TitleChangedEvent = (TitleChangedEventHandler)Delegate.Combine(TitleChangedEvent, value);
remove => TitleChangedEvent = (TitleChangedEventHandler)Delegate.Remove(TitleChangedEvent, value);
}
public void Event_Closing(object sender)
{
@@ -336,6 +345,11 @@ namespace mRemoteNG.Connection.Protocol
ErrorOccuredEvent?.Invoke(sender, errorMsg, errorCode);
}
protected void Event_TitleChanged(object sender, string newTitle)
{
TitleChangedEvent?.Invoke(sender, newTitle);
}
protected void Event_ReconnectGroupCloseClicked()
{
Close();

View File

@@ -28,8 +28,11 @@ namespace mRemoteNG.Connection.Protocol
public class PuttyBase : ProtocolBase
{
private const int IDM_RECONF = 0x50; // PuTTY Settings Menu ID
private const int TitleMonitorIntervalMs = 500;
private bool _isPuttyNg;
private readonly DisplayProperties _display = new();
private System.Threading.Timer _titleMonitorTimer;
private string _lastWindowTitle;
#region Public Properties
@@ -333,6 +336,11 @@ namespace mRemoteNG.Connection.Protocol
Resize(this, new EventArgs());
base.Connect();
// Start monitoring PuTTY window title for dynamic tab naming
_lastWindowTitle = PuttyProcess.MainWindowTitle;
_titleMonitorTimer = new System.Threading.Timer(MonitorPuttyTitle, null, TitleMonitorIntervalMs, TitleMonitorIntervalMs);
return true;
}
catch (Exception ex)
@@ -363,6 +371,32 @@ namespace mRemoteNG.Connection.Protocol
}
}
private void MonitorPuttyTitle(object state)
{
try
{
if (PuttyProcess == null || PuttyProcess.HasExited)
{
_titleMonitorTimer?.Dispose();
return;
}
PuttyProcess.Refresh();
string currentTitle = PuttyProcess.MainWindowTitle;
if (currentTitle != _lastWindowTitle)
{
_lastWindowTitle = currentTitle;
Event_TitleChanged(this, currentTitle);
}
}
catch (Exception ex)
{
_titleMonitorTimer?.Dispose();
Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg,
"PuTTY title monitoring stopped: " + ex.Message, true);
}
}
protected override void Resize(object sender, EventArgs e)
{
try
@@ -403,6 +437,9 @@ namespace mRemoteNG.Connection.Protocol
public override void Close()
{
_titleMonitorTimer?.Dispose();
_titleMonitorTimer = null;
try
{
if (PuttyProcess.HasExited == false)

View File

@@ -92,7 +92,7 @@ namespace mRemoteNG.Connection
#region IComponent
[Browsable(false)]
public ISite Site
public ISite? Site
{
get => new PropertyGridCommandSite(this);
set => throw (new NotImplementedException());
@@ -103,7 +103,7 @@ namespace mRemoteNG.Connection
Disposed?.Invoke(this, EventArgs.Empty);
}
public event EventHandler Disposed;
public event EventHandler? Disposed;
#endregion
}

View File

@@ -6117,6 +6117,15 @@ namespace mRemoteNG.Resources.Language {
}
}
/// <summary>
/// Looks up a localized string similar to Use terminal title for tab names (SSH/Telnet).
/// </summary>
internal static string UseTerminalTitleForTabs {
get {
return ResourceManager.GetString("UseTerminalTitleForTabs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Show Text.
/// </summary>

View File

@@ -1600,6 +1600,9 @@ If you run into such an error, please create a new connection file!</value>
<data name="ShowProtocolOnTabs" xml:space="preserve">
<value>Show protocols on tab names</value>
</data>
<data name="UseTerminalTitleForTabs" xml:space="preserve">
<value>Use terminal title for tab names (SSH/Telnet)</value>
</data>
<data name="SingleClickOnConnectionOpensIt" xml:space="preserve">
<value>Single click on connection opens it</value>
</data>

View File

@@ -145,7 +145,7 @@ namespace mRemoteNG.Properties {
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
[global::System.Configuration.DefaultSettingValueAttribute("True")]
public bool PopupMessageWriterWriteErrorMsgs {
get {
return ((bool)(this["PopupMessageWriterWriteErrorMsgs"]));

View File

@@ -33,7 +33,7 @@
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="PopupMessageWriterWriteErrorMsgs" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
<Value Profile="(Default)">True</Value>
</Setting>
<Setting Name="LogToApplicationDirectory" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">True</Value>

View File

@@ -166,5 +166,17 @@ namespace mRemoteNG.Properties {
this["BindConnectionsAndConfigPanels"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool UseTerminalTitleForTabs {
get {
return ((bool)(this["UseTerminalTitleForTabs"]));
}
set {
this["UseTerminalTitleForTabs"] = value;
}
}
}
}

View File

@@ -38,5 +38,8 @@
<Setting Name="BindConnectionsAndConfigPanels" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
<Setting Name="UseTerminalTitleForTabs" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
</Settings>
</SettingsFile>

View File

@@ -46,17 +46,19 @@ namespace mRemoteNG.Tools
return Status;
}
//X509Certificate2 certificate2 = new(X509Certificate.CreateFromSignedFile(FilePath));
#pragma warning disable SYSLIB0057
using X509Certificate cert = X509Certificate.CreateFromSignedFile(FilePath);
byte[] certData = cert.GetRawCertData();
#pragma warning restore SYSLIB0057
X509Certificate2 certificate2 = X509CertificateLoader.LoadCertificate(certData);
_thumbprint = certificate2.Thumbprint;
X509Certificate2 certificate2 = new(X509Certificate.CreateFromSignedFile(FilePath));
_thumbprint = certificate2.Thumbprint;
if (_thumbprint != ThumbprintToMatch)
{
Status = StatusValue.ThumbprintNotMatch;
return Status;
}
}
if (_thumbprint != ThumbprintToMatch)
{
Status = StatusValue.ThumbprintNotMatch;
return Status;
}
}
NativeMethods.WINTRUST_FILE_INFO trustFileInfo = new() { pcwszFilePath = FilePath};
trustFileInfoPointer = Marshal.AllocCoTaskMem(Marshal.SizeOf(trustFileInfo));

View File

@@ -42,17 +42,17 @@ namespace mRemoteNG.Tools
return result;
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext? context)
{
return new StandardValuesCollection(SshTunnels);
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context)
{
return true;
}
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context)
{
return true;
}

View File

@@ -27,7 +27,7 @@ namespace mRemoteNG.UI.Controls
private MrngLabel label2;
private MrngLabel label3;
private ToolTip toolTip1;
private System.ComponentModel.IContainer components;
private System.ComponentModel.Container components;
/* Sets and Gets the tooltiptext on toolTip1 */
public string ToolTipText
@@ -46,7 +46,7 @@ namespace mRemoteNG.UI.Controls
}
/* Set or Get the string that represents the value in the box */
public override string? Text
public override string Text
{
get => (Octet1.Text ?? string.Empty) + @"." + (Octet2.Text ?? string.Empty) + @"." + (Octet3.Text ?? string.Empty) + @"." + (Octet4.Text ?? string.Empty);
set
@@ -119,9 +119,7 @@ namespace mRemoteNG.UI.Controls
{
if (disposing)
{
// ReSharper disable once UseNullPropagation
if (components != null)
components.Dispose();
components?.Dispose();
}
base.Dispose(disposing);

View File

@@ -1,4 +1,5 @@
using System.Diagnostics;
using System;
using System.Diagnostics;
using System.Windows.Forms;
using mRemoteNG.App.Info;
using mRemoteNG.Themes;
@@ -8,6 +9,8 @@ using mRemoteNG.Properties;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using mRemoteNG.UI.Window;
using mRemoteNG.App;
using mRemoteNG.Messages;
namespace mRemoteNG.UI.Forms
{
@@ -41,7 +44,7 @@ namespace mRemoteNG.UI.Forms
[Conditional("PORTABLE")]
private void AddPortableString() => lblTitle.Text += $@" {Language.PortableEdition}";
private void ApplyTheme()
private new void ApplyTheme()
{
if (!ThemeManager.getInstance().ThemingActive) return;
if (!ThemeManager.getInstance().ActiveAndExtended) return;
@@ -59,47 +62,110 @@ namespace mRemoteNG.UI.Forms
private void llLicense_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
OpenUrl("https://raw.githubusercontent.com/mRemoteNG/mRemoteNG/v" + Assembly.GetExecutingAssembly().GetName().Version.ToString().Substring(0, Assembly.GetExecutingAssembly().GetName().Version.ToString().Length - 2) + "-" + Properties.OptionsUpdatesPage.Default.CurrentUpdateChannelType + "/COPYING.txt");
var version = Assembly.GetExecutingAssembly().GetName().Version;
var updateChannel = Properties.OptionsUpdatesPage.Default.CurrentUpdateChannelType;
if (version != null && updateChannel != null)
{
var versionString = version.ToString();
OpenUrl("https://raw.githubusercontent.com/mRemoteNG/mRemoteNG/v" + versionString[..^2] + "-" + updateChannel + "/COPYING.txt");
}
Close();
}
private void llChangelog_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
OpenUrl("https://raw.githubusercontent.com/mRemoteNG/mRemoteNG/v" + Assembly.GetExecutingAssembly().GetName().Version.ToString().Substring(0, Assembly.GetExecutingAssembly().GetName().Version.ToString().Length - 2) + "-" + Properties.OptionsUpdatesPage.Default.CurrentUpdateChannelType + "/CHANGELOG.md");
var version = Assembly.GetExecutingAssembly().GetName().Version;
var updateChannel = Properties.OptionsUpdatesPage.Default.CurrentUpdateChannelType;
if (version != null && updateChannel != null)
{
var versionString = version.ToString();
OpenUrl("https://raw.githubusercontent.com/mRemoteNG/mRemoteNG/v" + versionString[..^2] + "-" + updateChannel + "/CHANGELOG.md");
}
Close();
}
private void llCredits_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
OpenUrl("https://raw.githubusercontent.com/mRemoteNG/mRemoteNG/v" + Assembly.GetExecutingAssembly().GetName().Version.ToString().Substring(0, Assembly.GetExecutingAssembly().GetName().Version.ToString().Length - 2) + "-" + Properties.OptionsUpdatesPage.Default.CurrentUpdateChannelType + "/CREDITS.md");
var version = Assembly.GetExecutingAssembly().GetName().Version;
var updateChannel = Properties.OptionsUpdatesPage.Default.CurrentUpdateChannelType;
if (version != null && updateChannel != null)
{
var versionString = version.ToString();
OpenUrl("https://raw.githubusercontent.com/mRemoteNG/mRemoteNG/v" + versionString[..^2] + "-" + updateChannel + "/CREDITS.md");
}
Close();
}
private void OpenUrl(string url)
private static void OpenUrl(string url)
{
// Validate URL format to prevent injection
if (string.IsNullOrWhiteSpace(url))
return;
// Basic URL validation - ensure it starts with http:// or https://
if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{
// Invalid URL format - don't try to open it
return;
}
try
{
Process.Start(url);
// Use the standard .NET approach for opening URLs securely
// UseShellExecute=true delegates to the OS default handler
var startInfo = new ProcessStartInfo
{
FileName = url,
UseShellExecute = true
};
Process.Start(startInfo);
}
catch
{
// hack because of this: https://github.com/dotnet/corefx/issues/10361
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
// Fallback for older .NET Core versions with bug: https://github.com/dotnet/corefx/issues/10361
// Use platform-specific URL launchers
try
{
url = url.Replace("&", "^&");
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Use rundll32 with url.dll as fallback
var startInfo = new ProcessStartInfo
{
FileName = "rundll32.exe",
UseShellExecute = false,
CreateNoWindow = true
};
startInfo.ArgumentList.Add("url.dll,FileProtocolHandler");
startInfo.ArgumentList.Add(url);
Process.Start(startInfo);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
var startInfo = new ProcessStartInfo
{
FileName = "xdg-open",
UseShellExecute = false
};
startInfo.ArgumentList.Add(url);
Process.Start(startInfo);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
var startInfo = new ProcessStartInfo
{
FileName = "open",
UseShellExecute = false
};
startInfo.ArgumentList.Add(url);
Process.Start(startInfo);
}
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
catch
{
Process.Start("xdg-open", url);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", url);
}
else
{
throw;
// Unable to open URL - notify the user
Runtime.MessageCollector?.AddMessage(MessageClass.WarningMsg,
"Unable to open URL in browser. Please open manually: " + url, true);
}
}
}

View File

@@ -44,6 +44,8 @@ namespace mRemoteNG.UI.Forms
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel1.AutoSize = true;
this.tableLayoutPanel1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.tableLayoutPanel1.Controls.Add(this._Ok, 1, 2);
this.tableLayoutPanel1.Controls.Add(this.buttonCancel, 2, 2);
this.tableLayoutPanel1.Controls.Add(this.textBox, 0, 1);
@@ -51,11 +53,12 @@ namespace mRemoteNG.UI.Forms
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.Padding = new System.Windows.Forms.Padding(8);
this.tableLayoutPanel1.RowCount = 3;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 24F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(284, 81);
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.AutoSize));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 35F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(284, 90);
this.tableLayoutPanel1.TabIndex = 0;
//
// _Ok
@@ -103,12 +106,14 @@ namespace mRemoteNG.UI.Forms
this.label.TabIndex = 3;
this.label.Text = "Label";
this.label.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
//
// FrmInputBox
//
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.ClientSize = new System.Drawing.Size(284, 81);
this.AutoSize = true;
this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.ClientSize = new System.Drawing.Size(300, 95);
this.ControlBox = false;
this.Controls.Add(this.tableLayoutPanel1);
this.DoubleBuffered = true;
@@ -116,6 +121,7 @@ namespace mRemoteNG.UI.Forms
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.MinimumSize = new System.Drawing.Size(300, 95);
this.Name = "FrmInputBox";
this.ShowIcon = false;
this.ShowInTaskbar = false;

View File

@@ -91,7 +91,12 @@ namespace mRemoteNG.UI.Forms
private void buttonCreateBug_Click(object sender, EventArgs e)
{
Process.Start(GeneralAppInfo.UrlBugs);
var startInfo = new ProcessStartInfo
{
FileName = GeneralAppInfo.UrlBugs,
UseShellExecute = true
};
Process.Start(startInfo);
}
}
}

View File

@@ -7,6 +7,7 @@ using mRemoteNG.App;
using mRemoteNG.Config.Settings.Registry;
using mRemoteNG.Properties;
using mRemoteNG.Resources.Language;
using mRemoteNG.Tools;
namespace mRemoteNG.UI.Forms.OptionsPages
{
@@ -342,8 +343,17 @@ namespace mRemoteNG.UI.Forms.OptionsPages
{
try
{
// Validate path to prevent command injection
PathValidator.ValidatePathOrThrow(path, nameof(path));
// Open the file using the default application associated with its file type based on the user's preference
Process.Start(path);
// Use ProcessStartInfo with UseShellExecute for better control
var startInfo = new ProcessStartInfo
{
FileName = path,
UseShellExecute = true
};
Process.Start(startInfo);
return true;
}
catch
@@ -362,9 +372,19 @@ namespace mRemoteNG.UI.Forms.OptionsPages
{
try
{
// Validate path to prevent command injection
PathValidator.ValidatePathOrThrow(path, nameof(path));
// Open it in "Notepad" (Windows default editor).
// Usually available on all Windows systems
Process.Start("notepad.exe", path);
// Use ProcessStartInfo with ArgumentList for better security
var startInfo = new ProcessStartInfo
{
FileName = "notepad.exe",
UseShellExecute = false
};
startInfo.ArgumentList.Add(path);
Process.Start(startInfo);
return true;
}
catch
@@ -383,11 +403,22 @@ namespace mRemoteNG.UI.Forms.OptionsPages
{
try
{
// Validate path to prevent command injection
PathValidator.ValidatePathOrThrow(path, nameof(path));
// when all fails open filelocation to logfile...
// Open Windows Explorer to the directory containing the file
Process.Start("explorer.exe", $"/select,\"{path}\"");
return true;
}
// Explorer expects /select,"path" as a single argument
var startInfo = new ProcessStartInfo
{
FileName = "explorer.exe",
UseShellExecute = false
};
startInfo.ArgumentList.Add("/select,");
startInfo.ArgumentList.Add(path);
Process.Start(startInfo);
return true;
}
catch
{
// If necessary, the error can be logged here.

View File

@@ -41,6 +41,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkShowLogonInfoOnTabs = new MrngCheckBox();
chkDoubleClickClosesTab = new MrngCheckBox();
chkShowProtocolOnTabs = new MrngCheckBox();
chkUseTerminalTitleForTabs = new MrngCheckBox();
chkCreateEmptyPanelOnStart = new MrngCheckBox();
chkBindConnectionsAndConfigPanels = new MrngCheckBox();
txtBoxPanelName = new MrngTextBox();
@@ -80,7 +81,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkIdentifyQuickConnectTabs._mice = MrngCheckBox.MouseState.OUT;
chkIdentifyQuickConnectTabs.AutoSize = true;
chkIdentifyQuickConnectTabs.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
chkIdentifyQuickConnectTabs.Location = new System.Drawing.Point(3, 72);
chkIdentifyQuickConnectTabs.Location = new System.Drawing.Point(3, 95);
chkIdentifyQuickConnectTabs.Name = "chkIdentifyQuickConnectTabs";
chkIdentifyQuickConnectTabs.Size = new System.Drawing.Size(315, 17);
chkIdentifyQuickConnectTabs.TabIndex = 4;
@@ -104,7 +105,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkAlwaysShowPanelSelectionDlg._mice = MrngCheckBox.MouseState.OUT;
chkAlwaysShowPanelSelectionDlg.AutoSize = true;
chkAlwaysShowPanelSelectionDlg.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
chkAlwaysShowPanelSelectionDlg.Location = new System.Drawing.Point(3, 118);
chkAlwaysShowPanelSelectionDlg.Location = new System.Drawing.Point(3, 141);
chkAlwaysShowPanelSelectionDlg.Name = "chkAlwaysShowPanelSelectionDlg";
chkAlwaysShowPanelSelectionDlg.Size = new System.Drawing.Size(347, 17);
chkAlwaysShowPanelSelectionDlg.TabIndex = 6;
@@ -128,7 +129,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkDoubleClickClosesTab._mice = MrngCheckBox.MouseState.OUT;
chkDoubleClickClosesTab.AutoSize = true;
chkDoubleClickClosesTab.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
chkDoubleClickClosesTab.Location = new System.Drawing.Point(3, 95);
chkDoubleClickClosesTab.Location = new System.Drawing.Point(3, 118);
chkDoubleClickClosesTab.Name = "chkDoubleClickClosesTab";
chkDoubleClickClosesTab.Size = new System.Drawing.Size(170, 17);
chkDoubleClickClosesTab.TabIndex = 5;
@@ -147,12 +148,24 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkShowProtocolOnTabs.Text = "Show protocols on tab names";
chkShowProtocolOnTabs.UseVisualStyleBackColor = true;
//
// chkUseTerminalTitleForTabs
//
chkUseTerminalTitleForTabs._mice = MrngCheckBox.MouseState.OUT;
chkUseTerminalTitleForTabs.AutoSize = true;
chkUseTerminalTitleForTabs.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
chkUseTerminalTitleForTabs.Location = new System.Drawing.Point(3, 72);
chkUseTerminalTitleForTabs.Name = "chkUseTerminalTitleForTabs";
chkUseTerminalTitleForTabs.Size = new System.Drawing.Size(270, 17);
chkUseTerminalTitleForTabs.TabIndex = 10;
chkUseTerminalTitleForTabs.Text = "Use terminal title for tab names (SSH/Telnet)";
chkUseTerminalTitleForTabs.UseVisualStyleBackColor = true;
//
// chkCreateEmptyPanelOnStart
//
chkCreateEmptyPanelOnStart._mice = MrngCheckBox.MouseState.OUT;
chkCreateEmptyPanelOnStart.AutoSize = true;
chkCreateEmptyPanelOnStart.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
chkCreateEmptyPanelOnStart.Location = new System.Drawing.Point(3, 141);
chkCreateEmptyPanelOnStart.Location = new System.Drawing.Point(3, 164);
chkCreateEmptyPanelOnStart.Name = "chkCreateEmptyPanelOnStart";
chkCreateEmptyPanelOnStart.Size = new System.Drawing.Size(271, 17);
chkCreateEmptyPanelOnStart.TabIndex = 7;
@@ -165,7 +178,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkBindConnectionsAndConfigPanels._mice = MrngCheckBox.MouseState.OUT;
chkBindConnectionsAndConfigPanels.AutoSize = true;
chkBindConnectionsAndConfigPanels.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
chkBindConnectionsAndConfigPanels.Location = new System.Drawing.Point(3, 210);
chkBindConnectionsAndConfigPanels.Location = new System.Drawing.Point(3, 233);
chkBindConnectionsAndConfigPanels.Name = "chkBindConnectionsAndConfigPanels";
chkBindConnectionsAndConfigPanels.Size = new System.Drawing.Size(350, 17);
chkBindConnectionsAndConfigPanels.TabIndex = 9;
@@ -175,7 +188,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
// txtBoxPanelName
//
txtBoxPanelName.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
txtBoxPanelName.Location = new System.Drawing.Point(35, 177);
txtBoxPanelName.Location = new System.Drawing.Point(35, 200);
txtBoxPanelName.Name = "txtBoxPanelName";
txtBoxPanelName.Size = new System.Drawing.Size(213, 22);
txtBoxPanelName.TabIndex = 8;
@@ -183,7 +196,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
// lblPanelName
//
lblPanelName.AutoSize = true;
lblPanelName.Location = new System.Drawing.Point(32, 161);
lblPanelName.Location = new System.Drawing.Point(32, 184);
lblPanelName.Name = "lblPanelName";
lblPanelName.Size = new System.Drawing.Size(69, 13);
lblPanelName.TabIndex = 9;
@@ -194,6 +207,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
pnlOptions.Controls.Add(chkAlwaysShowPanelTabs);
pnlOptions.Controls.Add(lblPanelName);
pnlOptions.Controls.Add(chkShowProtocolOnTabs);
pnlOptions.Controls.Add(chkUseTerminalTitleForTabs);
pnlOptions.Controls.Add(txtBoxPanelName);
pnlOptions.Controls.Add(chkDoubleClickClosesTab);
pnlOptions.Controls.Add(chkCreateEmptyPanelOnStart);
@@ -206,7 +220,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
pnlOptions.Dock = System.Windows.Forms.DockStyle.Top;
pnlOptions.Location = new System.Drawing.Point(0, 30);
pnlOptions.Name = "pnlOptions";
pnlOptions.Size = new System.Drawing.Size(610, 240);
pnlOptions.Size = new System.Drawing.Size(610, 263);
pnlOptions.TabIndex = 10;
//
// lblRegistrySettingsUsedInfo
@@ -243,6 +257,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
internal MrngCheckBox chkShowLogonInfoOnTabs;
internal MrngCheckBox chkDoubleClickClosesTab;
internal MrngCheckBox chkShowProtocolOnTabs;
internal MrngCheckBox chkUseTerminalTitleForTabs;
private MrngCheckBox chkCreateEmptyPanelOnStart;
private MrngCheckBox chkBindConnectionsAndConfigPanels;
private Controls.MrngTextBox txtBoxPanelName;

View File

@@ -42,6 +42,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkOpenNewTabRightOfSelected.Text = Language.OpenNewTabRight;
chkShowLogonInfoOnTabs.Text = Language.ShowLogonInfoOnTabs;
chkShowProtocolOnTabs.Text = Language.ShowProtocolOnTabs;
chkUseTerminalTitleForTabs.Text = Language.UseTerminalTitleForTabs;
chkIdentifyQuickConnectTabs.Text = Language.IdentifyQuickConnectTabs;
chkDoubleClickClosesTab.Text = Language.DoubleClickTabClosesIt;
chkAlwaysShowPanelSelectionDlg.Text = Language.AlwaysShowPanelSelection;
@@ -73,6 +74,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
chkShowLogonInfoOnTabs.Checked = Properties.OptionsTabsPanelsPage.Default.ShowLogonInfoOnTabs;
chkShowProtocolOnTabs.Checked = Properties.OptionsTabsPanelsPage.Default.ShowProtocolOnTabs;
chkUseTerminalTitleForTabs.Checked = Properties.OptionsTabsPanelsPage.Default.UseTerminalTitleForTabs;
chkIdentifyQuickConnectTabs.Checked = Properties.OptionsTabsPanelsPage.Default.IdentifyQuickConnectTabs;
chkDoubleClickClosesTab.Checked = Properties.OptionsTabsPanelsPage.Default.DoubleClickOnTabClosesIt;
chkAlwaysShowPanelSelectionDlg.Checked = Properties.OptionsTabsPanelsPage.Default.AlwaysShowPanelSelectionDlg;
@@ -105,6 +107,7 @@ namespace mRemoteNG.UI.Forms.OptionsPages
Properties.OptionsTabsPanelsPage.Default.ShowLogonInfoOnTabs = chkShowLogonInfoOnTabs.Checked;
Properties.OptionsTabsPanelsPage.Default.ShowProtocolOnTabs = chkShowProtocolOnTabs.Checked;
Properties.OptionsTabsPanelsPage.Default.UseTerminalTitleForTabs = chkUseTerminalTitleForTabs.Checked;
Properties.OptionsTabsPanelsPage.Default.IdentifyQuickConnectTabs = chkIdentifyQuickConnectTabs.Checked;
Properties.OptionsTabsPanelsPage.Default.DoubleClickOnTabClosesIt = chkDoubleClickClosesTab.Checked;
Properties.OptionsTabsPanelsPage.Default.AlwaysShowPanelSelectionDlg = chkAlwaysShowPanelSelectionDlg.Checked;

View File

@@ -234,6 +234,8 @@ namespace mRemoteNG.UI.Forms
else
SetLayout();
ShowHidePanelTabs();
Runtime.ConnectionsService.ConnectionsLoaded += ConnectionsServiceOnConnectionsLoaded;
Runtime.ConnectionsService.ConnectionsSaved += ConnectionsServiceOnConnectionsSaved;
@@ -398,6 +400,11 @@ namespace mRemoteNG.UI.Forms
private async void FrmMain_Shown(object sender, EventArgs e)
{
// Bring the main window to the front after splash screen closes
Activate();
BringToFront();
NativeMethods.SetForegroundWindow(Handle);
PromptForUpdatesPreference();
await CheckForUpdates();
}

View File

@@ -188,19 +188,29 @@ namespace mRemoteNG.UI.Menu
}
}
private void mMenInfoHelp_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlDocumentation);
private void mMenInfoHelp_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlDocumentation);
private void mMenInfoForum_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlForum);
private void mMenInfoForum_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlForum);
private void mMenInfoChat_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlChat);
private void mMenInfoChat_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlChat);
private void mMenInfoCommunity_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlCommunity);
private void mMenInfoCommunity_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlCommunity);
private void mMenInfoBug_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlBugs);
private void mMenInfoBug_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlBugs);
private void mMenInfoWebsite_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlHome);
private void mMenInfoWebsite_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlHome);
private void mMenInfoDonate_Click(object? sender, EventArgs e) => Process.Start("explorer.exe", GeneralAppInfo.UrlDonate);
private void mMenInfoDonate_Click(object? sender, EventArgs e) => OpenUrl(GeneralAppInfo.UrlDonate);
private static void OpenUrl(string url)
{
var startInfo = new ProcessStartInfo
{
FileName = url,
UseShellExecute = true
};
Process.Start(startInfo);
}
private void mMenInfoAbout_Click(object? sender, EventArgs e)
{

View File

@@ -40,7 +40,7 @@ namespace mRemoteNG.UI.TaskDialog
//--------------------------------------------------------------------------------
// Override this to make sure the control is invalidated (repainted) when 'Text' is changed
public override string Text
public override string? Text
{
get => base.Text;
set
@@ -109,13 +109,16 @@ namespace mRemoteNG.UI.TaskDialog
//--------------------------------------------------------------------------------
string GetLargeText()
{
if (string.IsNullOrEmpty(Text))
return string.Empty;
string[] lines = Text.Split('\n');
return lines[0];
}
string GetSmallText()
{
if (Text.IndexOf('\n') < 0)
if (string.IsNullOrEmpty(Text) || Text.IndexOf('\n') < 0)
return "";
string s = Text;

View File

@@ -832,6 +832,26 @@ namespace mRemoteNG.UI.Window
Invoke(new Action(() => tabPage.Close()));
}
public void Prot_Event_TitleChanged(object sender, string newTitle)
{
if (!Properties.OptionsTabsPanelsPage.Default.UseTerminalTitleForTabs) return;
ProtocolBase protocolBase = sender as ProtocolBase;
if (!(protocolBase?.InterfaceControl?.Parent is ConnectionTab tabPage)) return;
if (tabPage.Disposing || tabPage.IsDisposed) return;
if (IsDisposed || Disposing) return;
string connectionName = protocolBase.InterfaceControl.Info?.Name ?? "";
string tabText = string.IsNullOrEmpty(newTitle)
? connectionName
: $"{newTitle} ({connectionName})";
tabText = tabText.Replace("&", "&&");
if (tabPage.InvokeRequired)
tabPage.Invoke(new Action(() => tabPage.TabText = tabText));
else
tabPage.TabText = tabText;
}
#endregion
}
}

View File

@@ -99,7 +99,12 @@ namespace mRemoteNG.UI.Window
return;
}
Process.Start(linkUri.ToString());
var startInfo = new ProcessStartInfo
{
FileName = linkUri.ToString(),
UseShellExecute = true
};
Process.Start(startInfo);
}
#endregion

View File

@@ -3,6 +3,7 @@
<PropertyGroup>
<Platforms>x64;arm64</Platforms>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
<NoWarn>$(NoWarn);NU1701</NoWarn>
</PropertyGroup>
<PropertyGroup>
<OutputType>WinExe</OutputType>
@@ -15,13 +16,13 @@
<PackageLicenseFile>COPYING.TXT</PackageLicenseFile>
<PackageProjectUrl>https://mremoteng.org/</PackageProjectUrl>
<Platforms>x64;arm64</Platforms>
<Configurations>Debug;Release;Debug Portable;Release Portable;Release Installer;Deploy to github</Configurations>
<Configurations>Debug;Release;Release Self-Contained;Deploy to github</Configurations>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<LangVersion>latest</LangVersion>
<PackageIcon>Header_dark.png</PackageIcon>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<UseWPF>True</UseWPF>
<SupportedOSPlatformVersion>10.0.26100.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion>
<SignAssembly>False</SignAssembly>
<Title>Multi-Remote Next Generation Connection Manager</Title>
<RepositoryUrl>https://github.com/mRemoteNG/mRemoteNG.git</RepositoryUrl>
@@ -69,55 +70,40 @@
<WarningLevel>8</WarningLevel>
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug Portable|x64'">
<DefineConstants>DEBUG;PORTABLE</DefineConstants>
<Optimize>False</Optimize>
<OutputPath>bin\x64\Debug Portable\</OutputPath>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningsAsErrors />
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<WarningLevel>8</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug Portable|arm64'">
<DefineConstants>DEBUG;PORTABLE</DefineConstants>
<Optimize>False</Optimize>
<OutputPath>bin\arm64\Debug Portable\</OutputPath>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningsAsErrors />
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<WarningLevel>8</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Portable|x64'">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Self-Contained|x64'">
<DefineConstants>PORTABLE</DefineConstants>
<Optimize>True</Optimize>
<OutputPath>bin\x64\Release Portable\</OutputPath>
<PublishDir>bin\x64\Publish Self-Contained\</PublishDir>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningsAsErrors />
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<WarningLevel>8</WarningLevel>
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishReadyToRun>true</PublishReadyToRun>
<CopyBuildOutputToPublishDirectory>true</CopyBuildOutputToPublishDirectory>
<CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory>
<RunCommand></RunCommand>
<RunArguments></RunArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Portable|arm64'">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Self-Contained|arm64'">
<DefineConstants>PORTABLE</DefineConstants>
<Optimize>True</Optimize>
<OutputPath>bin\arm64\Release Portable\</OutputPath>
<OutputPath>bin\arm64\Release Self-Contained\</OutputPath>
<PublishDir>bin\arm64\Publish Self-Contained\</PublishDir>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<WarningsAsErrors />
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<WarningLevel>8</WarningLevel>
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Installer|x64'">
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<Optimize>True</Optimize>
<WarningLevel>8</WarningLevel>
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Installer|arm64'">
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
<Optimize>True</Optimize>
<WarningLevel>8</WarningLevel>
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-arm64</RuntimeIdentifier>
<PublishReadyToRun>true</PublishReadyToRun>
<CopyBuildOutputToPublishDirectory>true</CopyBuildOutputToPublishDirectory>
<CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory>
<RunCommand></RunCommand>
<RunArguments></RunArguments>
</PropertyGroup>
<ItemGroup>
<None Remove="buildenv.tmp" />
@@ -149,59 +135,16 @@
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
<PackageReference Include="Microsoft.NETCore.Platforms" />
<PackageReference Include="Microsoft.NETCore.Targets" />
<PackageReference Include="Microsoft.VisualStudio.TextTemplating.VSHost" />
<PackageReference Include="Microsoft.Web.WebView2" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" />
<PackageReference Include="MySql.Data" />
<PackageReference Include="NETStandard.Library" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="Newtonsoft.Json.Schema" />
<PackageReference Include="OpenCover" />
<PackageReference Include="Renci.SshNet.Async" />
<PackageReference Include="ReportGenerator" />
<PackageReference Include="runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl" />
<PackageReference Include="runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl" />
<PackageReference Include="runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl" />
<PackageReference Include="runtime.native.System" />
<PackageReference Include="runtime.native.System.IO.Compression" />
<PackageReference Include="runtime.native.System.Net.Http" />
<PackageReference Include="runtime.native.System.Security.Cryptography.OpenSsl" />
<PackageReference Include="runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl" />
<PackageReference Include="runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl" />
<PackageReference Include="runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl" />
<PackageReference Include="SSH.NET" />
<PackageReference Include="System.Collections.Immutable" />
<PackageReference Include="System.Configuration.ConfigurationManager" />
<PackageReference Include="System.Console" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" />
<PackageReference Include="System.Diagnostics.EventLog" />
<PackageReference Include="System.DirectoryServices" />
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="System.Formats.Asn1" />
<PackageReference Include="System.IO.Pipelines" />
<PackageReference Include="System.Management" />
<PackageReference Include="System.Net.Primitives" />
<PackageReference Include="System.Net.Sockets" />
<PackageReference Include="System.Reflection.Emit" />
<PackageReference Include="System.Reflection.Emit.ILGeneration" />
<PackageReference Include="System.Reflection.Emit.Lightweight" />
<PackageReference Include="System.Reflection.Metadata" />
<PackageReference Include="System.Resources.ResourceManager" />
<PackageReference Include="System.Runtime.Extensions" />
<PackageReference Include="System.Security.AccessControl" />
<PackageReference Include="System.Security.Cryptography.Algorithms" />
<PackageReference Include="System.Security.Cryptography.Cng" />
<PackageReference Include="System.Security.Cryptography.OpenSsl" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" />
<PackageReference Include="System.Security.Cryptography.X509Certificates" />
<PackageReference Include="System.Security.Permissions" />
<PackageReference Include="System.Security.Principal.Windows" />
<PackageReference Include="System.Text.Encoding.CodePages" />
<PackageReference Include="System.Text.Json" />
<PackageReference Include="System.Text.RegularExpressions" />
<PackageReference Include="System.Threading.Tasks.Extensions" />
<PackageReference Include="System.Windows.Extensions" />
<PackageReference Include="System.Xml.ReaderWriter" />
<PackageReference Include="VncSharpCore" />
<PackageReference Include="ZstdSharp.Port" />
</ItemGroup>
@@ -601,10 +544,10 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Update="chromiumembeddedframework.runtime.win-x64" Version="143.0.9" />
<PackageReference Update="chromiumembeddedframework.runtime.win-x64" Version="144.0.12" />
</ItemGroup>
<ItemGroup Condition="'$(Platform)'=='arm64'">
<PackageReference Update="chromiumembeddedframework.runtime.win-arm64" Version="143.0.9" />
<PackageReference Update="chromiumembeddedframework.runtime.win-arm64" Version="144.0.12" />
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
@@ -634,4 +577,14 @@
<!-- Disable CET compatibility check in .NET 9 AppHost -->
<CETCompat>false</CETCompat>
</PropertyGroup>
<Target Name="PublishAfterBuild" AfterTargets="Build" Condition="'$(Configuration)' == 'Release Self-Contained'">
<Message Text="Publishing self-contained application for $(Platform)..." Importance="high" />
<MSBuild Projects="$(MSBuildProjectFile)" Targets="Publish" Properties="Configuration=$(Configuration);Platform=$(Platform);NoBuild=true;NoRestore=true" />
<PropertyGroup>
<TempBuildDir>$([System.IO.Path]::GetFullPath('$(OutputPath)'))</TempBuildDir>
</PropertyGroup>
<Message Text="Cleaning up temporary build directory: $(TempBuildDir)" Importance="high" />
<RemoveDir Directories="$(TempBuildDir)" />
<RemoveDir Directories="$(ProjectDir)bin\x64\Release Self-Contained" />
</Target>
</Project>

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

@@ -37,7 +37,7 @@ Popups settings
.. figure:: /images/notifications_popup.png
When items are selected here you will recieve a popup on the error that occurrs
When items are selected here you will receive a popup on the error that occurs
based on level chosen in settings here.
This can be useful if you do not want to use the notification area
and only get a popup if error occurs. (**default**: all off)
and only get a popup if error occurs. (**default**: Errors enabled, Debug/Info/Warning disabled)

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

View File

@@ -2,12 +2,14 @@
using System.Management;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace CustomActions
{
public class InstalledWindowsUpdateChecker
{
private readonly ManagementScope _managementScope;
private static readonly Regex KbPattern = new Regex(@"^(KB)?\d+$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public InstalledWindowsUpdateChecker()
{
@@ -61,12 +63,45 @@ namespace CustomActions
var counter = 0;
foreach (var kb in kbList)
{
var sanitizedKb = SanitizeKbId(kb);
if (string.IsNullOrEmpty(sanitizedKb))
continue; // Skip invalid KB IDs
if (counter > 0)
whereClause += " OR ";
whereClause += $"HotFixID='{kb}'";
whereClause += $"HotFixID='{sanitizedKb}'";
counter++;
}
return whereClause;
}
/// <summary>
/// Sanitizes a KB ID to prevent WQL injection attacks.
/// KB IDs must match the pattern: optional "KB" prefix followed by digits,
/// or just digits. Any other characters are rejected.
/// </summary>
/// <param name="kbId">The KB ID to sanitize</param>
/// <returns>The sanitized KB ID, or empty string if invalid</returns>
private string SanitizeKbId(string kbId)
{
if (string.IsNullOrWhiteSpace(kbId))
return string.Empty;
// KB IDs should match the pattern: optional KB prefix followed by digits
// (e.g., KB1234567 or 1234567)
// Trim whitespace and check if it matches the expected pattern
var trimmedKb = kbId.Trim();
if (!KbPattern.IsMatch(trimmedKb))
return string.Empty;
// Normalize to uppercase
var normalizedKb = trimmedKb.ToUpperInvariant();
// Ensure KB prefix is present (Win32_QuickFixEngineering always uses the KB prefix)
if (!normalizedKb.StartsWith("KB"))
normalizedKb = "KB" + normalizedKb;
return normalizedKb;
}
}
}

View File

@@ -0,0 +1,117 @@
using System;
using System.Linq;
using mRemoteNG.App;
using mRemoteNG.Connection;
using mRemoteNG.Connection.Protocol;
using mRemoteNG.Messages;
using mRemoteNG.Resources.Language;
using NUnit.Framework;
namespace mRemoteNGTests.Connection
{
[TestFixture]
public class ConnectionInitiatorTests
{
private ConnectionInitiator _connectionInitiator;
private MessageCollector _messageCollector;
[SetUp]
public void Setup()
{
_connectionInitiator = new ConnectionInitiator();
_messageCollector = Runtime.MessageCollector;
_messageCollector.ClearMessages();
}
[TearDown]
public void Teardown()
{
_messageCollector?.ClearMessages();
}
[Test]
public void OpenConnection_WithEmptyHostname_AddsErrorMessage()
{
// Arrange
var connectionInfo = new ConnectionInfo
{
Name = "Test Connection",
Hostname = "", // Empty hostname
Protocol = ProtocolType.RDP // RDP doesn't support blank hostname
};
// Act
_connectionInitiator.OpenConnection(connectionInfo);
// Assert - poll for message with timeout
var foundMessage = WaitForMessage(MessageClass.ErrorMsg, timeoutMs: 1000);
Assert.That(foundMessage, Is.Not.Null, "Expected an error message to be added");
Assert.That(foundMessage.Text, Is.EqualTo(Language.ConnectionOpenFailedNoHostname));
}
[Test]
public void OpenConnection_WithNullHostname_AddsErrorMessage()
{
// Arrange
var connectionInfo = new ConnectionInfo
{
Name = "Test Connection",
Hostname = null, // Null hostname
Protocol = ProtocolType.SSH2 // SSH doesn't support blank hostname
};
// Act
_connectionInitiator.OpenConnection(connectionInfo);
// Assert - poll for message with timeout
var foundMessage = WaitForMessage(MessageClass.ErrorMsg, timeoutMs: 1000);
Assert.That(foundMessage, Is.Not.Null, "Expected an error message to be added");
Assert.That(foundMessage.Text, Is.EqualTo(Language.ConnectionOpenFailedNoHostname));
}
[Test]
public void OpenConnection_WithValidHostname_DoesNotAddHostnameError()
{
// Arrange
var connectionInfo = new ConnectionInfo
{
Name = "Test Connection",
Hostname = "192.168.1.1", // Valid hostname
Protocol = ProtocolType.RDP
};
// Act
_connectionInitiator.OpenConnection(connectionInfo);
// Give a moment for any potential async operations
System.Threading.Thread.Sleep(200);
// Assert
var hostnameErrors = _messageCollector.Messages
.Where(m => m.Text == Language.ConnectionOpenFailedNoHostname)
.ToList();
Assert.That(hostnameErrors, Is.Empty,
"Should not have hostname error when hostname is provided");
}
/// <summary>
/// Polls the message collector for a message of the specified class
/// </summary>
private IMessage WaitForMessage(MessageClass messageClass, int timeoutMs = 1000)
{
var startTime = DateTime.Now;
while ((DateTime.Now - startTime).TotalMilliseconds < timeoutMs)
{
var message = _messageCollector.Messages
.FirstOrDefault(m => m.Class == messageClass);
if (message != null)
return message;
System.Threading.Thread.Sleep(50); // Poll every 50ms
}
return null;
}
}
}

View File

@@ -0,0 +1,204 @@
using System;
using System.Reflection;
using mRemoteNG.Connection;
using mRemoteNG.Connection.Protocol.AnyDesk;
using NUnit.Framework;
namespace mRemoteNGTests.Connection.Protocol;
public class ProtocolAnyDeskTests
{
private ProtocolAnyDesk _protocolAnyDesk;
private ConnectionInfo _connectionInfo;
[SetUp]
public void Setup()
{
_connectionInfo = new ConnectionInfo();
_protocolAnyDesk = new ProtocolAnyDesk(_connectionInfo);
}
[TearDown]
public void Teardown()
{
_protocolAnyDesk?.Close();
_protocolAnyDesk = null;
_connectionInfo = null;
}
#region IsValidAnydeskId Tests
[Test]
public void IsValidAnydeskId_NumericId_ReturnsTrue()
{
// Valid numeric AnyDesk ID
bool result = InvokeIsValidAnydeskId("123456789");
Assert.That(result, Is.True);
}
[Test]
public void IsValidAnydeskId_AlphanumericWithAt_ReturnsTrue()
{
// Valid alias format: alias@ad
bool result = InvokeIsValidAnydeskId("myalias@ad");
Assert.That(result, Is.True);
}
[Test]
public void IsValidAnydeskId_WithHyphen_ReturnsTrue()
{
// Valid ID with hyphen
bool result = InvokeIsValidAnydeskId("my-alias@ad");
Assert.That(result, Is.True);
}
[Test]
public void IsValidAnydeskId_WithUnderscore_ReturnsTrue()
{
// Valid ID with underscore
bool result = InvokeIsValidAnydeskId("my_alias@ad");
Assert.That(result, Is.True);
}
[Test]
public void IsValidAnydeskId_WithDot_ReturnsTrue()
{
// Valid ID with dot
bool result = InvokeIsValidAnydeskId("alias.name@ad");
Assert.That(result, Is.True);
}
[Test]
public void IsValidAnydeskId_WithSemicolon_ReturnsFalse()
{
// Command injection attempt with semicolon
bool result = InvokeIsValidAnydeskId("123456789; calc.exe");
Assert.That(result, Is.False);
}
[Test]
public void IsValidAnydeskId_WithAmpersand_ReturnsFalse()
{
// Command injection attempt with ampersand
bool result = InvokeIsValidAnydeskId("123456789 & calc.exe");
Assert.That(result, Is.False);
}
[Test]
public void IsValidAnydeskId_WithPipe_ReturnsFalse()
{
// Command injection attempt with pipe
bool result = InvokeIsValidAnydeskId("123456789 | calc.exe");
Assert.That(result, Is.False);
}
[Test]
public void IsValidAnydeskId_WithRedirection_ReturnsFalse()
{
// Command injection attempt with redirection
bool result = InvokeIsValidAnydeskId("123456789 > output.txt");
Assert.That(result, Is.False);
}
[Test]
public void IsValidAnydeskId_WithBacktick_ReturnsFalse()
{
// PowerShell escape character
bool result = InvokeIsValidAnydeskId("123456789`calc");
Assert.That(result, Is.False);
}
[Test]
public void IsValidAnydeskId_WithDollarSign_ReturnsFalse()
{
// PowerShell variable indicator
bool result = InvokeIsValidAnydeskId("123456789$var");
Assert.That(result, Is.False);
}
[Test]
public void IsValidAnydeskId_WithParentheses_ReturnsFalse()
{
// Command substitution
bool result = InvokeIsValidAnydeskId("123456789(calc)");
Assert.That(result, Is.False);
}
[Test]
public void IsValidAnydeskId_WithNewline_ReturnsFalse()
{
// Newline injection
bool result = InvokeIsValidAnydeskId("123456789\ncalc");
Assert.That(result, Is.False);
}
[Test]
public void IsValidAnydeskId_WithCarriageReturn_ReturnsFalse()
{
// Carriage return injection
bool result = InvokeIsValidAnydeskId("123456789\rcalc");
Assert.That(result, Is.False);
}
[Test]
public void IsValidAnydeskId_WithQuotes_ReturnsFalse()
{
// Quote escape attempt
bool result = InvokeIsValidAnydeskId("123456789\"calc\"");
Assert.That(result, Is.False);
}
[Test]
public void IsValidAnydeskId_WithSingleQuotes_ReturnsFalse()
{
// Single quote escape attempt
bool result = InvokeIsValidAnydeskId("123456789'calc'");
Assert.That(result, Is.False);
}
[Test]
public void IsValidAnydeskId_EmptyString_ReturnsFalse()
{
// Empty string
bool result = InvokeIsValidAnydeskId("");
Assert.That(result, Is.False);
}
[Test]
public void IsValidAnydeskId_Whitespace_ReturnsFalse()
{
// Only whitespace
bool result = InvokeIsValidAnydeskId(" ");
Assert.That(result, Is.False);
}
[Test]
public void IsValidAnydeskId_Null_ReturnsFalse()
{
// Null string
bool result = InvokeIsValidAnydeskId(null);
Assert.That(result, Is.False);
}
#endregion
#region Helper Methods
/// <summary>
/// Uses reflection to invoke the private IsValidAnydeskId method
/// </summary>
private bool InvokeIsValidAnydeskId(string anydeskId)
{
var method = typeof(ProtocolAnyDesk).GetMethod("IsValidAnydeskId",
BindingFlags.NonPublic | BindingFlags.Instance);
if (method == null)
{
throw new Exception("IsValidAnydeskId method not found. The method may have been renamed or removed.");
}
return (bool)method.Invoke(_protocolAnydesk, new object[] { anydeskId });
}
#endregion
}

View File

@@ -0,0 +1,208 @@
using NUnit.Framework;
using System;
using System.Reflection;
namespace mRemoteNGTests.Installer
{
[TestFixture]
public class InstalledWindowsUpdateCheckerTests
{
private CustomActions.InstalledWindowsUpdateChecker _checker;
private MethodInfo _sanitizeKbIdMethod;
private MethodInfo _buildWhereClauseMethod;
[SetUp]
public void Setup()
{
_checker = new CustomActions.InstalledWindowsUpdateChecker();
// Use reflection to access private methods for testing
var type = typeof(CustomActions.InstalledWindowsUpdateChecker);
_sanitizeKbIdMethod = type.GetMethod("SanitizeKbId", BindingFlags.NonPublic | BindingFlags.Instance);
_buildWhereClauseMethod = type.GetMethod("BuildWhereClauseFromKbList", BindingFlags.NonPublic | BindingFlags.Instance);
}
#region SanitizeKbId Tests
[Test]
public void SanitizeKbId_ValidKbWithPrefix_ReturnsUppercased()
{
var result = InvokeSanitizeKbId("KB1234567");
Assert.That(result, Is.EqualTo("KB1234567"));
}
[Test]
public void SanitizeKbId_ValidKbLowercase_ReturnsUppercased()
{
var result = InvokeSanitizeKbId("kb1234567");
Assert.That(result, Is.EqualTo("KB1234567"));
}
[Test]
public void SanitizeKbId_ValidKbMixedCase_ReturnsUppercased()
{
var result = InvokeSanitizeKbId("Kb1234567");
Assert.That(result, Is.EqualTo("KB1234567"));
}
[Test]
public void SanitizeKbId_ValidNumberOnly_ReturnsWithKbPrefix()
{
var result = InvokeSanitizeKbId("1234567");
Assert.That(result, Is.EqualTo("KB1234567"));
}
[Test]
public void SanitizeKbId_WithWhitespace_ReturnsTrimmedAndUppercased()
{
var result = InvokeSanitizeKbId(" KB1234567 ");
Assert.That(result, Is.EqualTo("KB1234567"));
}
[Test]
public void SanitizeKbId_SqlInjectionAttempt_ReturnsEmpty()
{
var result = InvokeSanitizeKbId("KB1234' OR '1'='1");
Assert.That(result, Is.Empty);
}
[Test]
public void SanitizeKbId_WqlInjectionWithSemicolon_ReturnsEmpty()
{
var result = InvokeSanitizeKbId("KB1234; DROP TABLE");
Assert.That(result, Is.Empty);
}
[Test]
public void SanitizeKbId_WithSpecialCharacters_ReturnsEmpty()
{
var result = InvokeSanitizeKbId("KB1234@#$");
Assert.That(result, Is.Empty);
}
[Test]
public void SanitizeKbId_NullInput_ReturnsEmpty()
{
var result = InvokeSanitizeKbId(null);
Assert.That(result, Is.Empty);
}
[Test]
public void SanitizeKbId_EmptyString_ReturnsEmpty()
{
var result = InvokeSanitizeKbId("");
Assert.That(result, Is.Empty);
}
[Test]
public void SanitizeKbId_WhitespaceOnly_ReturnsEmpty()
{
var result = InvokeSanitizeKbId(" ");
Assert.That(result, Is.Empty);
}
[Test]
public void SanitizeKbId_OnlyKbPrefix_ReturnsEmpty()
{
var result = InvokeSanitizeKbId("KB");
Assert.That(result, Is.Empty);
}
[Test]
public void SanitizeKbId_WithDashes_ReturnsEmpty()
{
var result = InvokeSanitizeKbId("KB-1234567");
Assert.That(result, Is.Empty);
}
[Test]
public void SanitizeKbId_WithUnderscores_ReturnsEmpty()
{
var result = InvokeSanitizeKbId("KB_1234567");
Assert.That(result, Is.Empty);
}
#endregion
#region BuildWhereClauseFromKbList Tests
[Test]
public void BuildWhereClause_SingleValidKb_ReturnsCorrectClause()
{
var result = InvokeBuildWhereClause(new[] { "KB1234567" });
Assert.That(result, Is.EqualTo("HotFixID='KB1234567'"));
}
[Test]
public void BuildWhereClause_MultipleValidKbs_ReturnsOrClause()
{
var result = InvokeBuildWhereClause(new[] { "KB1234567", "KB7654321" });
Assert.That(result, Is.EqualTo("HotFixID='KB1234567' OR HotFixID='KB7654321'"));
}
[Test]
public void BuildWhereClause_InvalidKb_SkipsInvalid()
{
var result = InvokeBuildWhereClause(new[] { "KB1234567", "KB1234'; DROP--", "KB7654321" });
Assert.That(result, Is.EqualTo("HotFixID='KB1234567' OR HotFixID='KB7654321'"));
}
[Test]
public void BuildWhereClause_AllInvalidKbs_ReturnsEmpty()
{
var result = InvokeBuildWhereClause(new[] { "'; DROP TABLE", "OR 1=1--" });
Assert.That(result, Is.Empty);
}
[Test]
public void BuildWhereClause_EmptyList_ReturnsEmpty()
{
var result = InvokeBuildWhereClause(Array.Empty<string>());
Assert.That(result, Is.Empty);
}
[Test]
public void BuildWhereClause_NullValues_SkipsNulls()
{
var result = InvokeBuildWhereClause(new[] { "KB1234567", null, "KB7654321" });
Assert.That(result, Is.EqualTo("HotFixID='KB1234567' OR HotFixID='KB7654321'"));
}
[Test]
public void BuildWhereClause_MixedCaseKbs_NormalizesToUppercase()
{
var result = InvokeBuildWhereClause(new[] { "kb1234567", "KB7654321" });
Assert.That(result, Is.EqualTo("HotFixID='KB1234567' OR HotFixID='KB7654321'"));
}
[Test]
public void BuildWhereClause_DigitOnlyKb_AddsKbPrefix()
{
var result = InvokeBuildWhereClause(new[] { "1234567" });
Assert.That(result, Is.EqualTo("HotFixID='KB1234567'"));
}
[Test]
public void BuildWhereClause_MixedPrefixedAndDigitOnly_NormalizesAll()
{
var result = InvokeBuildWhereClause(new[] { "1234567", "KB7654321", "kb9999999" });
Assert.That(result, Is.EqualTo("HotFixID='KB1234567' OR HotFixID='KB7654321' OR HotFixID='KB9999999'"));
}
#endregion
#region Helper Methods
private string InvokeSanitizeKbId(string input)
{
return (string)_sanitizeKbIdMethod.Invoke(_checker, new object[] { input });
}
private string InvokeBuildWhereClause(string[] kbList)
{
return (string)_buildWhereClauseMethod.Invoke(_checker, new object[] { kbList });
}
#endregion
}
}

View File

@@ -69,6 +69,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\mRemoteNG\mRemoteNG.csproj" />
<ProjectReference Include="..\mRemoteNGInstaller\CustomActions\CustomActions.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">