Compare commits

..

172 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
dbb15207d6 Fix RDP tab wrong resolution after jump-host reconnect
When the outer RDP session (jump-host) reconnects with a different viewport
resolution, Windows fires SystemEvents.DisplaySettingsChanged. Previously,
DoResizeClient() was only called on window-state changes (Maximize/Restore),
so if the window stayed in Normal state across the reconnect, the inner RDP
session resolution was never updated.

Changes:
- Subscribe to SystemEvents.DisplaySettingsChanged in RdpProtocol8 constructor
  to schedule a debounced resize when the outer RDP reconnects
- Fix thread-safety: ResizeDebounceTimer_Elapsed now marshals DoResizeClient()
  to the UI thread via InterfaceControl.BeginInvoke (timer runs on ThreadPool)
- Unsubscribe from SystemEvents.DisplaySettingsChanged in Close() to prevent
  memory leaks

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-24 17:46:53 +00:00
copilot-swe-agent[bot]
95514bc5ea Initial plan 2026-02-24 17:32:42 +00:00
Dimitrij
43735b1d04 Merge pull request #3169 from mRemoteNG/renovate/vstest-monorepo
Update dependency Microsoft.NET.Test.Sdk to 18.3.0
2026-02-24 17:25:53 +00:00
renovate[bot]
002f6cb290 Update dependency Microsoft.NET.Test.Sdk to 18.3.0 2026-02-24 13:30:48 +00:00
Dimitrij
92c617d442 Merge pull request #3168 from mRemoteNG/renovate/aws-sdk-net-monorepo
Update aws-sdk-net monorepo
2026-02-24 10:20:28 +00:00
renovate[bot]
764b96f864 Update aws-sdk-net monorepo 2026-02-23 21:57:17 +00:00
Dimitrij
e30a8ad3f0 Merge pull request #3164 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-arm64-145.x
Update dependency chromiumembeddedframework.runtime.win-arm64 to v145
2026-02-22 16:26:38 +00:00
Dimitrij
1e85969e3a Merge pull request #3165 from mRemoteNG/renovate/chromiumembeddedframework.runtime.win-x64-145.x
Update dependency chromiumembeddedframework.runtime.win-x64 to v145
2026-02-22 16:26:24 +00:00
renovate[bot]
1a47bba982 Update dependency chromiumembeddedframework.runtime.win-x64 to v145 2026-02-22 16:24:37 +00:00
renovate[bot]
1cc5f05bf5 Update dependency chromiumembeddedframework.runtime.win-arm64 to v145 2026-02-22 16:24:32 +00:00
Dimitrij
7e0277f85d Update GitHub regex to exclude user-attachments
allow attachments
2026-02-22 16:24:08 +00:00
Kvarkas
ff3da2c7d5 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2026-02-22 00:19:37 +00:00
Kvarkas
c90152c35c small fixes 2026-02-22 00:19:24 +00:00
Dimitrij
95e20afefa Update regex to filter GitHub repo links 2026-02-21 21:37:06 +00:00
Dimitrij
4c7b3a9da5 Censor external GitHub repository links in comments 2026-02-21 21:30:52 +00:00
Dimitrij
14ec6b38e8 Fix regex replacement for issue body sanitization 2026-02-21 21:29:22 +00:00
Dimitrij
f10bcdee20 Censor external GitHub links in comments and issues 2026-02-21 21:22:40 +00:00
Dimitrij
af105a62f1 Refactor GitHub link sanitization in workflow 2026-02-21 21:19:06 +00:00
Dimitrij
5173eec41d Enhance logging in filter-links workflow
Add logging for event name and payload keys in GitHub Actions workflow.
2026-02-21 21:12:12 +00:00
Dimitrij
797a8dff14 Add read permission for contents in workflow 2026-02-21 21:10:27 +00:00
Dimitrij
7dcc43824a Fix typo in console log statement 2026-02-21 21:09:11 +00:00
Dimitrij
9b04d70276 Correct typo in logging comment context
Fix typo in console log statement for comment context.
2026-02-21 21:07:15 +00:00
Dimitrij
21407c0805 Improve comment processing in filter-links workflow
Refactor comment handling and logging for GitHub repo link filtering.
2026-02-21 20:59:36 +00:00
Dimitrij
286eee370a Update workflow to include permissions
Add permissions for issues and pull requests.
2026-02-21 20:53:40 +00:00
Dimitrij
bd4c9fea34 Merge pull request #3159 from mRemoteNG/renovate/actions-github-script-8.x
Update actions/github-script action to v8
2026-02-20 16:03:49 +00:00
renovate[bot]
9ba21e9047 Update actions/github-script action to v8 2026-02-20 15:02:59 +00:00
Dimitrij
2e7ec5a354 Merge pull request #3141 from mRemoteNG/copilot/add-keyboard-shortcuts-sessions
Add keyboard shortcuts for session/tab navigation
2026-02-20 15:02:00 +00:00
copilot-swe-agent[bot]
d83be65e5c Fix menu state management and key calculation
- Initialize all menu items as disabled by default
- Add public UpdateMenuState() method for reliable state updates
- Wire UpdateMenuState to ActiveDocumentChanged event in FrmMain
- Fix key calculation to use explicit cast: (Keys)((int)Keys.D1 + i)

This ensures shortcuts are only active when appropriate sessions exist,
preventing global interception in text boxes and other contexts.

Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-20 14:54:22 +00:00
Dimitrij
fec445d3a5 Merge pull request #3158 from mRemoteNG/copilot/fix-remote-code-execution-issue
Remove EnableUnsafeBinaryFormatterSerialization from ObjectListView
2026-02-20 14:51:46 +00:00
Kvarkas
87fb45c115 odbc fixes 2026-02-20 14:50:39 +00:00
copilot-swe-agent[bot]
779b541702 Remove EnableUnsafeBinaryFormatterSerialization from ObjectListView project
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-20 14:48:48 +00:00
copilot-swe-agent[bot]
24d2a0b407 Initial plan 2026-02-20 14:40:56 +00:00
Kvarkas
da10db53ee Add links filtering flow 2026-02-20 14:39:48 +00:00
Kvarkas
4487545445 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2026-02-20 14:34:18 +00:00
Dimitrij
afbec5fae0 Merge pull request #3157 from mRemoteNG/renovate/major-dotnet-monorepo
Update dependency System.Data.Odbc to v10
2026-02-20 14:33:55 +00:00
Dimitrij
6c20941430 Merge branch 'v1.78.2-dev' into renovate/major-dotnet-monorepo 2026-02-20 14:33:46 +00:00
Dimitrij
8f79b313c5 Merge pull request #3156 from mRemoteNG/renovate/dotnet-monorepo
Update dependency System.Data.Odbc to 9.0.13
2026-02-20 14:33:10 +00:00
Kvarkas
295fcaae12 Merge branch 'v1.78.2-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.78.2-dev 2026-02-20 14:29:33 +00:00
Kvarkas
3ffac2684c upd instructions 2026-02-20 14:29:22 +00:00
renovate[bot]
26978fc09d Update dependency System.Data.Odbc to v10 2026-02-20 14:29:05 +00:00
renovate[bot]
151d82a640 Update dependency System.Data.Odbc to 9.0.13 2026-02-20 14:29:00 +00:00
Dimitrij
12b4afac9a Merge pull request #3155 from mRemoteNG/copilot/add-odbc-support
Add ODBC support as a database backend for connection list storage
2026-02-20 14:28:03 +00:00
Dimitrij
12f2942202 Update mRemoteNG/Config/DatabaseConnectors/DatabaseConnectorFactory.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-20 14:27:04 +00:00
copilot-swe-agent[bot]
0b7fd5b5f5 Add ODBC support for database connection list storage
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-20 14:02:53 +00:00
copilot-swe-agent[bot]
fd41e27d47 Initial plan 2026-02-20 13:54:30 +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
copilot-swe-agent[bot]
9ba3cf0727 Add keyboard shortcuts documentation for session navigation
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-13 10:37:15 +00:00
copilot-swe-agent[bot]
71911bba7b Add Sessions menu with keyboard shortcuts for tab navigation
Co-authored-by: Kvarkas <3611964+Kvarkas@users.noreply.github.com>
2026-02-13 10:36:32 +00:00
copilot-swe-agent[bot]
e9f94cbe31 Initial plan 2026-02-13 10:32:08 +00: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
55 changed files with 2171 additions and 481 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 2026 (version 18.4.0 or later)
- .NET 10.0 Desktop Runtime
- Windows 10/11 or Windows Server 2022+
### 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

63
.github/workflows/filter-links.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: Remove other GitHub repo links
permissions:
contents: read
issues: write
pull-requests: write
on:
issue_comment:
types: [created, edited]
issues:
types: [opened, edited]
jobs:
filter:
runs-on: ubuntu-latest
steps:
- name: Sanitize links
uses: actions/github-script@v8
with:
script: |
const githubRepoRegex =
/\[[^\]]*\]\((?!https?:\/\/github\.com\/user-attachments\/assets\/)[^\)]*https?:\/\/github\.com\/(?!user-attachments\/assets\/)[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+[^\s)]*\)|https?:\/\/github\.com\/(?!user-attachments\/assets\/)[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+[^\s)]*/gi;
// CASE 1: Comment
if (context.payload.comment) {
const comment = context.payload.comment;
console.log("Processing comment:", comment.id);
const matches = comment.body.match(githubRepoRegex);
console.log("Matches:", matches);
if (!matches) return;
const sanitized = comment.body.replace(
githubRepoRegex,
"**[CENSORED!] Links to external GitHub repositories are not allowed**"
);
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: comment.id,
body: sanitized
});
console.log("Comment updated");
return;
}
// CASE 2: Issue body
const issue = context.payload.issue;
if (issue) {
console.log("Processing issue:", issue.number);
const matches = issue.body?.match(githubRepoRegex);
console.log("Matches:", matches);
if (!matches) return;
const sanitized = issue.body.replace(
githubRepoRegex,
"**[CENSORED!] Links to external GitHub repositories are not allowed**"
);
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: sanitized
});
console.log("Issue body updated");
}

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.15" />
<PackageVersion Include="AWSSDK.EC2" Version="4.0.76.1" />
<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.NET.Test.Sdk" Version="18.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.3.0" />
<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,19 @@
<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.Data.Odbc" Version="10.0.3" />
<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 +78,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 +88,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,12 +1,11 @@
<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>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWindowsForms>true</UseWindowsForms>
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
<NoWarn>$(NoWarn);WFO1000</NoWarn>
<EnableWinFormsAnalyzers>false</EnableWinFormsAnalyzers>
</PropertyGroup>
@@ -35,8 +34,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

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

View File

@@ -1,5 +1,6 @@
using mRemoteNG.App;
using mRemoteNG.Security.SymmetricEncryption;
using System;
using System.Runtime.Versioning;
namespace mRemoteNG.Config.DatabaseConnectors
@@ -22,14 +23,13 @@ namespace mRemoteNG.Config.DatabaseConnectors
public static IDatabaseConnector DatabaseConnector(string type, string server, string database, string username, string password)
{
switch (type)
return type switch
{
case "mysql":
return new MySqlDatabaseConnector(server, database, username, password);
case "mssql":
default:
return new MSSqlDatabaseConnector(server, database, username, password);
}
"mysql" => new MySqlDatabaseConnector(server, database, username, password),
"odbc" => throw new NotSupportedException("ODBC database connections are not supported for schema initialization. Please use a supported database backend."),
"mssql" => new MSSqlDatabaseConnector(server, database, username, password),
_ => new MSSqlDatabaseConnector(server, database, username, password)
};
}
}
}

View File

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

View File

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

@@ -465,4 +465,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

@@ -39,9 +39,9 @@ namespace mRemoteNG.Connection.Protocol
public IntPtr PuttyHandle { get; set; }
private Process PuttyProcess { get; set; }
private Process? PuttyProcess { get; set; }
public static string PuttyPath { get; set; }
public static string? PuttyPath { get; set; }
public bool Focused => NativeMethods.GetForegroundWindow() == PuttyHandle;
@@ -60,7 +60,7 @@ namespace mRemoteNG.Connection.Protocol
public bool isRunning()
{
return !PuttyProcess.HasExited;
return PuttyProcess?.HasExited == false;
}
public void CreatePipe(object oData)
@@ -298,6 +298,9 @@ namespace mRemoteNG.Connection.Protocol
while (PuttyHandle.ToInt32() == 0 &
Environment.TickCount < startTicks + Properties.OptionsAdvancedPage.Default.MaxPuttyWaitTime * 1000)
{
if (PuttyProcess.HasExited)
break;
if (_isPuttyNg)
{
PuttyHandle = NativeMethods.FindWindowEx(InterfaceControl.Handle, new IntPtr(0), null, null);
@@ -305,12 +308,28 @@ namespace mRemoteNG.Connection.Protocol
else
{
PuttyProcess.Refresh();
PuttyHandle = PuttyProcess.MainWindowHandle;
IntPtr candidateHandle = PuttyProcess.MainWindowHandle;
if (candidateHandle != IntPtr.Zero)
{
// Check the window class name to distinguish the actual PuTTY
// terminal window ("PuTTY") from popup dialogs like the host key
// verification alert (class "#32770"). Dialogs must remain as
// top-level windows so the user can interact with them.
StringBuilder className = new(256);
NativeMethods.GetClassName(candidateHandle, className, className.Capacity);
string cls = className.ToString();
if (cls.Equals("PuTTY", StringComparison.OrdinalIgnoreCase))
{
PuttyHandle = candidateHandle;
}
}
}
if (PuttyHandle.ToInt32() == 0)
{
Thread.Sleep(0);
Thread.Sleep(100);
}
}
@@ -405,27 +424,23 @@ namespace mRemoteNG.Connection.Protocol
{
try
{
if (PuttyProcess.HasExited == false)
if (PuttyProcess?.HasExited == false)
{
PuttyProcess.Kill();
}
}
catch (Exception ex)
{
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg,
Language.PuttyKillFailed + Environment.NewLine + ex.Message,
true);
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.PuttyKillFailed + Environment.NewLine + ex.Message, true);
}
try
{
PuttyProcess.Dispose();
PuttyProcess?.Dispose();
}
catch (Exception ex)
{
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg,
Language.PuttyDisposeFailed + Environment.NewLine + ex.Message,
true);
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.PuttyDisposeFailed + Environment.NewLine + ex.Message, true);
}
base.Close();
@@ -440,9 +455,7 @@ namespace mRemoteNG.Connection.Protocol
}
catch (Exception ex)
{
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg,
Language.PuttyShowSettingsDialogFailed + Environment.NewLine +
ex.Message, true);
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.PuttyShowSettingsDialogFailed + Environment.NewLine + ex.Message, true);
}
}

View File

@@ -7,6 +7,7 @@ using mRemoteNG.Messages;
using MSTSCLib;
using mRemoteNG.Resources.Language;
using System.Runtime.Versioning;
using Microsoft.Win32;
namespace mRemoteNG.Connection.Protocol.RDP
{
@@ -38,6 +39,10 @@ namespace mRemoteNG.Connection.Protocol.RDP
_resizeDebounceTimer = new System.Timers.Timer(300);
_resizeDebounceTimer.AutoReset = false;
_resizeDebounceTimer.Elapsed += ResizeDebounceTimer_Elapsed;
// Subscribe to display settings changes so the inner RDP session is resized
// when the outer RDP session (jump-host) reconnects with a different viewport.
SystemEvents.DisplaySettingsChanged += OnDisplaySettingsChanged;
}
public override bool Initialize()
@@ -153,8 +158,24 @@ namespace mRemoteNG.Connection.Protocol.RDP
Runtime.MessageCollector?.AddMessage(MessageClass.DebugMsg,
$"Debounce timer fired - executing delayed resize to {_pendingResizeSize.Width}x{_pendingResizeSize.Height}");
// Execute the actual RDP session resize
DoResizeClient();
// Marshal to the UI thread because DoResizeClient() accesses WinForms and COM objects
if (InterfaceControl.InvokeRequired)
{
InterfaceControl.BeginInvoke(new Action(DoResizeClient));
}
else
{
DoResizeClient();
}
}
private void OnDisplaySettingsChanged(object sender, EventArgs e)
{
// When display settings change (e.g., outer RDP session reconnects with a different
// resolution/viewport), schedule a debounced resize so the inner RDP session is
// updated to match the new panel dimensions once the display has settled.
if (!loginComplete) return;
ScheduleDebouncedResize();
}
protected override AxHost CreateActiveXRdpClientControl()
@@ -278,6 +299,9 @@ namespace mRemoteNG.Connection.Protocol.RDP
public override void Close()
{
// Unsubscribe from display settings changes to prevent memory leaks
SystemEvents.DisplaySettingsChanged -= OnDisplaySettingsChanged;
// Clean up debounce timer
if (_resizeDebounceTimer != null)
{

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

@@ -19,7 +19,7 @@ namespace mRemoteNG.Resources.Language {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Language {
@@ -177,6 +177,15 @@ namespace mRemoteNG.Resources.Language {
}
}
/// <summary>
/// Looks up a localized string similar to &amp;Sessions.
/// </summary>
internal static string _Sessions {
get {
return ResourceManager.GetString("_Sessions", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &amp;Stop.
/// </summary>
@@ -2909,6 +2918,15 @@ namespace mRemoteNG.Resources.Language {
}
}
/// <summary>
/// Looks up a localized string similar to Jump to Session {0}.
/// </summary>
internal static string JumpToSession {
get {
return ResourceManager.GetString("JumpToSession", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to (Automatically Detect).
/// </summary>
@@ -3494,6 +3512,15 @@ namespace mRemoteNG.Resources.Language {
}
}
/// <summary>
/// Looks up a localized string similar to Next Session.
/// </summary>
internal static string NextSession {
get {
return ResourceManager.GetString("NextSession", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No.
/// </summary>
@@ -3963,6 +3990,15 @@ namespace mRemoteNG.Resources.Language {
}
}
/// <summary>
/// Looks up a localized string similar to Previous Session.
/// </summary>
internal static string PreviousSession {
get {
return ResourceManager.GetString("PreviousSession", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Printers.
/// </summary>

View File

@@ -120,6 +120,18 @@
<data name="MenuItem_About" xml:space="preserve">
<value>About</value>
</data>
<data name="_Sessions" xml:space="preserve">
<value>&amp;Sessions</value>
</data>
<data name="NextSession" xml:space="preserve">
<value>Next Session</value>
</data>
<data name="PreviousSession" xml:space="preserve">
<value>Previous Session</value>
</data>
<data name="JumpToSession" xml:space="preserve">
<value>Jump to Session {0}</value>
</data>
<data name="ActiveDirectory" xml:space="preserve">
<value>Active Directory</value>
</data>

View File

@@ -1,5 +1,4 @@
//Generated for platform: x64
@@ -11,7 +10,7 @@ using System.Resources;
// Compute version values
//Build nr: 3225
//Build nr: 3405
// General Information
[assembly: AssemblyTitle("mRemoteNG")]
@@ -19,12 +18,12 @@ using System.Resources;
[assembly: AssemblyConfiguration("x64")]
[assembly: AssemblyCompany("Profi-KOM Ltd.")]
[assembly: AssemblyProduct("mRemoteNG Connection Manager")]
[assembly: AssemblyCopyright("(c) 2025 mRemoteNG")]
[assembly: AssemblyCopyright("(c) 2026 mRemoteNG")]
[assembly: AssemblyTrademark("Profi-KOM LTd.")]
[assembly: AssemblyCulture("")]
// Version information
[assembly: AssemblyVersion("1.78.2.3225")]
[assembly: AssemblyFileVersion("1.78.2.3225")]
[assembly: AssemblyVersion("1.78.2.3405")]
[assembly: AssemblyFileVersion("1.78.2.3405")]
[assembly: NeutralResourcesLanguageAttribute("en-US")]
[assembly: AssemblyInformationalVersion("1.78.2 (Nightly Build 3225) x64")]
[assembly: AssemblyInformationalVersion("1.78.2 (Nightly Build 3405) x64")]

View File

@@ -11,14 +11,30 @@
if (File.Exists(dllPath)) {
Assembly.LoadFrom(dllPath);
var sp = Host as IServiceProvider;
var dte = sp?.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
var projectItem = dte?.Solution?.FindProjectItem(Host.TemplateFile);
var project = projectItem?.ContainingProject;
var cfg = project?.ConfigurationManager?.ActiveConfiguration;
platformFromDte = cfg?.PlatformName;
if (sp != null) {
// Use reflection to avoid compile-time dependency on EnvDTE
var envDteAsm = AppDomain.CurrentDomain.GetAssemblies();
Type dteType = null;
foreach (var asm in envDteAsm) {
dteType = asm.GetType("EnvDTE.DTE");
if (dteType != null) break;
}
if (dteType != null) {
var dte = sp.GetService(dteType);
if (dte != null) {
var solution = dteType.GetProperty("Solution")?.GetValue(dte);
var findItem = solution?.GetType().GetMethod("FindProjectItem", new[] { typeof(string) });
var projectItem = findItem?.Invoke(solution, new object[] { Host.TemplateFile });
var project = projectItem?.GetType().GetProperty("ContainingProject")?.GetValue(projectItem);
var cfgMgr = project?.GetType().GetProperty("ConfigurationManager")?.GetValue(project);
var activeCfg = cfgMgr?.GetType().GetProperty("ActiveConfiguration")?.GetValue(cfgMgr);
platformFromDte = activeCfg?.GetType().GetProperty("PlatformName")?.GetValue(activeCfg) as string;
}
}
}
}
} catch {
// Log the exception if necessary
// Fallback to environment variables below
}
platformValue = platformFromDte

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

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

@@ -38,6 +38,7 @@ namespace mRemoteNG.UI.Forms
this.pnlDock = new WeifenLuo.WinFormsUI.Docking.DockPanel();
this.msMain = new System.Windows.Forms.MenuStrip();
this.fileMenu = new mRemoteNG.UI.Menu.FileMenu();
this.sessionsMenu = new mRemoteNG.UI.Menu.SessionsMenu();
this.viewMenu = new mRemoteNG.UI.Menu.ViewMenu();
this.toolsMenu = new mRemoteNG.UI.Menu.ToolsMenu();
this.helpMenu = new mRemoteNG.UI.Menu.HelpMenu();
@@ -75,13 +76,14 @@ namespace mRemoteNG.UI.Forms
this.msMain.GripStyle = System.Windows.Forms.ToolStripGripStyle.Visible;
this.msMain.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.fileMenu,
this.sessionsMenu,
this.viewMenu,
this.toolsMenu,
this.helpMenu});
this.msMain.Location = new System.Drawing.Point(3, 0);
this.msMain.Name = "msMain";
this.msMain.Padding = new System.Windows.Forms.Padding(0, 0, 1, 0);
this.msMain.Size = new System.Drawing.Size(151, 25);
this.msMain.Size = new System.Drawing.Size(212, 25);
this.msMain.Stretch = false;
this.msMain.TabIndex = 0;
this.msMain.Text = "Main Toolbar";
@@ -94,6 +96,13 @@ namespace mRemoteNG.UI.Forms
this.fileMenu.Text = "&File";
this.fileMenu.TreeWindow = null;
//
// sessionsMenu
//
this.sessionsMenu.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3);
this.sessionsMenu.Name = "mMenSessions";
this.sessionsMenu.Size = new System.Drawing.Size(61, 19);
this.sessionsMenu.Text = "&Sessions";
//
// viewMenu
//
this.viewMenu.FullscreenHandler = null;
@@ -253,6 +262,7 @@ namespace mRemoteNG.UI.Forms
internal System.Windows.Forms.ToolStripSeparator mMenSep3;
private System.ComponentModel.IContainer components;
private Menu.FileMenu fileMenu;
private Menu.SessionsMenu sessionsMenu;
private Menu.ViewMenu viewMenu;
private Menu.ToolsMenu toolsMenu;
private Menu.HelpMenu helpMenu;

View File

@@ -234,6 +234,8 @@ namespace mRemoteNG.UI.Forms
else
SetLayout();
ShowHidePanelTabs();
Runtime.ConnectionsService.ConnectionsLoaded += ConnectionsServiceOnConnectionsLoaded;
Runtime.ConnectionsService.ConnectionsSaved += ConnectionsServiceOnConnectionsSaved;
@@ -292,6 +294,7 @@ namespace mRemoteNG.UI.Forms
private void ApplyLanguage()
{
fileMenu.ApplyLanguage();
sessionsMenu.ApplyLanguage();
viewMenu.ApplyLanguage();
toolsMenu.ApplyLanguage();
helpMenu.ApplyLanguage();
@@ -398,6 +401,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();
}
@@ -692,6 +700,7 @@ namespace mRemoteNG.UI.Forms
private void PnlDock_ActiveDocumentChanged(object sender, EventArgs e)
{
ActivateConnection();
sessionsMenu.UpdateMenuState();
}
internal void UpdateWindowTitle()

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

@@ -0,0 +1,161 @@
using System;
using System.Windows.Forms;
using mRemoteNG.UI.Window;
using mRemoteNG.Resources.Language;
using System.Runtime.Versioning;
using mRemoteNG.UI.Forms;
namespace mRemoteNG.UI.Menu
{
[SupportedOSPlatform("windows")]
public class SessionsMenu : ToolStripMenuItem
{
private ToolStripMenuItem _mMenSessionsNextSession;
private ToolStripMenuItem _mMenSessionsPreviousSession;
private ToolStripSeparator _mMenSessionsSep1;
private readonly ToolStripMenuItem[] _sessionNumberItems = new ToolStripMenuItem[9];
public SessionsMenu()
{
Initialize();
}
private void Initialize()
{
_mMenSessionsNextSession = new ToolStripMenuItem();
_mMenSessionsPreviousSession = new ToolStripMenuItem();
_mMenSessionsSep1 = new ToolStripSeparator();
// Initialize session number menu items (Ctrl+1 through Ctrl+9)
for (int i = 0; i < 9; i++)
{
_sessionNumberItems[i] = new ToolStripMenuItem();
}
//
// mMenSessions
//
DropDownItems.Add(_mMenSessionsNextSession);
DropDownItems.Add(_mMenSessionsPreviousSession);
DropDownItems.Add(_mMenSessionsSep1);
for (int i = 0; i < 9; i++)
{
DropDownItems.Add(_sessionNumberItems[i]);
}
Name = "mMenSessions";
Size = new System.Drawing.Size(61, 20);
Text = Language._Sessions;
//
// mMenSessionsNextSession
//
_mMenSessionsNextSession.Name = "mMenSessionsNextSession";
_mMenSessionsNextSession.ShortcutKeys = Keys.Control | Keys.Right;
_mMenSessionsNextSession.Size = new System.Drawing.Size(230, 22);
_mMenSessionsNextSession.Text = Language.NextSession;
_mMenSessionsNextSession.Click += mMenSessionsNextSession_Click;
//
// mMenSessionsPreviousSession
//
_mMenSessionsPreviousSession.Name = "mMenSessionsPreviousSession";
_mMenSessionsPreviousSession.ShortcutKeys = Keys.Control | Keys.Left;
_mMenSessionsPreviousSession.Size = new System.Drawing.Size(230, 22);
_mMenSessionsPreviousSession.Text = Language.PreviousSession;
_mMenSessionsPreviousSession.Click += mMenSessionsPreviousSession_Click;
//
// mMenSessionsSep1
//
_mMenSessionsSep1.Name = "mMenSessionsSep1";
_mMenSessionsSep1.Size = new System.Drawing.Size(227, 6);
// Initialize session number items (Ctrl+1 through Ctrl+9)
for (int i = 0; i < 9; i++)
{
int sessionNumber = i + 1;
_sessionNumberItems[i].Name = $"mMenSessionsSession{sessionNumber}";
_sessionNumberItems[i].ShortcutKeys = Keys.Control | (Keys)((int)Keys.D1 + i);
_sessionNumberItems[i].Size = new System.Drawing.Size(230, 22);
_sessionNumberItems[i].Text = string.Format(Language.JumpToSession.ToString(), sessionNumber);
_sessionNumberItems[i].Enabled = false; // Initialize as disabled
int capturedIndex = i; // Capture the index for the lambda
_sessionNumberItems[i].Click += (s, e) => JumpToSessionNumber(capturedIndex);
}
// Initialize navigation items as disabled
_mMenSessionsNextSession.Enabled = false;
_mMenSessionsPreviousSession.Enabled = false;
// Hook up the dropdown opening event to update enabled state
DropDownOpening += SessionsMenu_DropDownOpening;
}
public void ApplyLanguage()
{
Text = Language._Sessions;
_mMenSessionsNextSession.Text = Language.NextSession;
_mMenSessionsPreviousSession.Text = Language.PreviousSession;
for (int i = 0; i < 9; i++)
{
_sessionNumberItems[i].Text = string.Format(Language.JumpToSession.ToString(), i + 1);
}
}
public void UpdateMenuState()
{
// Update enabled state of menu items based on active sessions
var connectionWindow = GetActiveConnectionWindow();
bool hasMultipleSessions = false;
int sessionCount = 0;
if (connectionWindow != null)
{
var documents = connectionWindow.GetDocuments();
sessionCount = documents.Length;
hasMultipleSessions = sessionCount > 1;
}
_mMenSessionsNextSession.Enabled = hasMultipleSessions;
_mMenSessionsPreviousSession.Enabled = hasMultipleSessions;
// Enable/disable session number items based on session count
for (int i = 0; i < 9; i++)
{
_sessionNumberItems[i].Enabled = (i < sessionCount);
}
}
private void SessionsMenu_DropDownOpening(object sender, EventArgs e)
{
// Update state when menu is opened (for visual feedback)
UpdateMenuState();
}
private void mMenSessionsNextSession_Click(object sender, EventArgs e)
{
var connectionWindow = GetActiveConnectionWindow();
connectionWindow?.NavigateToNextTab();
}
private void mMenSessionsPreviousSession_Click(object sender, EventArgs e)
{
var connectionWindow = GetActiveConnectionWindow();
connectionWindow?.NavigateToPreviousTab();
}
private void JumpToSessionNumber(int index)
{
var connectionWindow = GetActiveConnectionWindow();
connectionWindow?.NavigateToTab(index);
}
private ConnectionWindow GetActiveConnectionWindow()
{
return FrmMain.Default.pnlDock?.ActiveDocument as ConnectionWindow;
}
}
}

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

@@ -387,6 +387,34 @@ namespace mRemoteNG.UI.Window
}
}
internal void NavigateToTab(int index)
{
try
{
var documents = connDock.DocumentsToArray();
if (index < 0 || index >= documents.Length) return;
documents[index].DockHandler.Activate();
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionMessage("NavigateToTab (UI.Window.ConnectionWindow) failed", ex);
}
}
internal IDockContent[] GetDocuments()
{
try
{
return connDock.DocumentsToArray();
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionMessage("GetDocuments (UI.Window.ConnectionWindow) failed", ex);
return Array.Empty<IDockContent>();
}
}
#endregion
#region Events

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,11 +3,15 @@
<PropertyGroup>
<Platforms>x64;arm64</Platforms>
<RuntimeIdentifiers>win-x64;win-arm64</RuntimeIdentifiers>
<NoWarn>$(NoWarn);NU1701</NoWarn>
</PropertyGroup>
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<TransformOnBuild>true</TransformOnBuild>
<TransformOutOfDateOnly>false</TransformOutOfDateOnly>
<OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
<ApplicationIcon>Icons\mRemoteNG.ico</ApplicationIcon>
<Version>1.78.2-dev</Version>
<Description>Multi-protocol remote connections manager</Description>
@@ -15,13 +19,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 +73,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 +138,17 @@
<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="System.Data.Odbc" />
<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>
@@ -334,6 +281,11 @@
<SubType>UserControl</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<T4ParameterValues Include="platformType">
<Value>$(Platform)</Value>
</T4ParameterValues>
</ItemGroup>
<ItemGroup>
<None Update="Properties\AssemblyInfo.tt">
<LastGenOutput>AssemblyInfo.cs</LastGenOutput>
@@ -601,10 +553,10 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Update="chromiumembeddedframework.runtime.win-x64" Version="143.0.9" />
<PackageReference Update="chromiumembeddedframework.runtime.win-x64" Version="145.0.26" />
</ItemGroup>
<ItemGroup Condition="'$(Platform)'=='arm64'">
<PackageReference Update="chromiumembeddedframework.runtime.win-arm64" Version="143.0.9" />
<PackageReference Update="chromiumembeddedframework.runtime.win-arm64" Version="145.0.26" />
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
@@ -634,4 +586,15 @@
<!-- 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>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v18.0\TextTemplating\Microsoft.TextTemplating.targets" />
</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

@@ -73,3 +73,35 @@ Connections
- Move Up
* - Ctrl+Down
- Move Down
Sessions
========
.. list-table::
:widths: 30 70
:header-rows: 1
* - Keybinding
- Action
* - Ctrl+Right
- Next Session/Tab
* - Ctrl+Left
- Previous Session/Tab
* - Ctrl+1
- Jump to Session 1
* - Ctrl+2
- Jump to Session 2
* - Ctrl+3
- Jump to Session 3
* - Ctrl+4
- Jump to Session 4
* - Ctrl+5
- Jump to Session 5
* - Ctrl+6
- Jump to Session 6
* - Ctrl+7
- Jump to Session 7
* - Ctrl+8
- Jump to Session 8
* - Ctrl+9
- Jump to Session 9

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