Compare commits

..

80 Commits

Author SHA1 Message Date
Faryan Rezagholi
8a5f0f248e open credential manager from connection tree window 2021-09-04 00:52:04 +02:00
Faryan Rezagholi
c1931ff4cd hide password/username/domain fields in connection properties 2021-09-04 00:27:42 +02:00
Faryan Rezagholi
12cb7ad7b0 merged from develop 2021-09-01 01:26:21 +02:00
David Sparer
4349b1b65b dont save settings on individual settings pages 2019-05-19 16:23:00 -05:00
David Sparer
bb04605dc3 Merge branch 'develop' into reapply_credential_manager
# Conflicts:
#	mRemoteNGTests/Connection/DefaultConnectionInheritanceTests.cs
#	mRemoteNGTests/TestHelpers/ConnectionInfoHelpers.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Csv/CsvConnectionsDeserializerMremotengFormat.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Csv/CsvConnectionsSerializerMremotengFormat.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/DataTableSerializer.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionNodeSerializer27.cs
#	mRemoteV1/Config/Serializers/MiscSerializers/RemoteDesktopConnectionManagerDeserializer.cs
#	mRemoteV1/Connection/AbstractConnectionRecord.cs
#	mRemoteV1/Connection/ConnectionInitiator.cs
#	mRemoteV1/Connection/Protocol/RDP/RdpProtocol.cs
#	mRemoteV1/Resources/Help/ui_external_tools.htm
#	mRemoteV1/Resources/Language/Language.resx
#	mRemoteV1/UI/Controls/ConnectionTree/ConnectionTree.cs
#	mRemoteV1/UI/Forms/OptionsPages/CredentialsPage.Designer.cs
#	mRemoteV1/UI/Window/ConfigWindow.cs
#	mRemoteV1/UI/Window/ConnectionTreeWindow.Designer.cs
#	mRemoteV1/mRemoteV1.csproj
2019-05-19 16:19:18 -05:00
David Sparer
294bd2f7a4 fixed event invocator warnings 2019-03-19 20:25:59 -05:00
David Sparer
a3aa323cce updated html documentation for #680 2019-03-19 20:15:16 -05:00
David Sparer
355cd63acb Merge branch 'develop' into reapply_credential_manager 2019-03-19 18:01:37 -05:00
David Sparer
93d50b0818 Merge branch 'develop' into reapply_credential_manager
# Conflicts:
#	mRemoteNGTests/Config/Serializers/MiscSerializers/RemoteDesktopConnectionManager27DeserializerTests.cs
#	mRemoteNGTests/packages.config
#	mRemoteV1/Config/Connections/SqlConnectionsSaver.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Csv/CsvConnectionsSerializerMremotengFormat.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionNodeSerializer27.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs
#	mRemoteV1/Config/Serializers/MiscSerializers/RemoteDesktopConnectionManagerDeserializer.cs
#	mRemoteV1/Connection/AbstractConnectionRecord.cs
#	mRemoteV1/Connection/ConnectionInfoInheritance.cs
#	mRemoteV1/Connection/ConnectionsService.cs
#	mRemoteV1/Connection/Protocol/Http/Connection.Protocol.HTTPBase.cs
#	mRemoteV1/Properties/Settings.settings
#	mRemoteV1/Resources/Language/Language.de.resx
#	mRemoteV1/Resources/Language/Language.resx
#	mRemoteV1/Resources/Language/Language.ru.resx
#	mRemoteV1/UI/Controls/ConnectionTree/ConnectionTree.cs
#	mRemoteV1/mRemoteV1.csproj
2019-03-09 13:49:23 -06:00
Faryan Rezagholi
8779776ad5 removed minimize button from credentials manager 2019-02-20 19:45:18 +01:00
Faryan Rezagholi
3bb285d180 removed unused form 2019-02-20 19:42:30 +01:00
Faryan Rezagholi
052796c794 adjusted text box positioning 2019-02-20 19:08:25 +01:00
Faryan Rezagholi
33a6dbb4c3 made more hardcoded strings translateable 2019-02-20 18:32:35 +01:00
Faryan Rezagholi
953ddaa292 merged develop 2019-02-19 22:39:58 +01:00
Faryan Rezagholi
c065b86dbd revised credential manager forms design 2019-02-19 22:38:12 +01:00
David Sparer
95859b96ff Merge branch 'develop' into reapply_credential_manager
# Conflicts:
#	mRemoteV1/App/Export.cs
#	mRemoteV1/App/Initialization/CredsAndConsSetup.cs
#	mRemoteV1/App/Runtime.cs
#	mRemoteV1/Config/Connections/ConnectionsLoadedEventArgs.cs
#	mRemoteV1/Config/Connections/ConnectionsSavedEventArgs.cs
#	mRemoteV1/Config/Connections/CsvConnectionsSaver.cs
#	mRemoteV1/Config/Connections/SaveConnectionsOnEdit.cs
#	mRemoteV1/Config/CredentialHarvester.cs
#	mRemoteV1/Config/CredentialRepositoryListLoader.cs
#	mRemoteV1/Config/CredentialRepositoryListSaver.cs
#	mRemoteV1/Config/DataProviders/FileDataProvider.cs
#	mRemoteV1/Config/Import/MRemoteNGCsvImporter.cs
#	mRemoteV1/Config/Import/MRemoteNGXmlImporter.cs
#	mRemoteV1/Config/Import/RemoteDesktopConnectionImporter.cs
#	mRemoteV1/Config/Putty/PuttySessionsRegistryProvider.cs
#	mRemoteV1/Config/Putty/PuttySessionsXmingProvider.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Csv/CsvConnectionsDeserializerMremotengFormat.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Csv/CsvConnectionsSerializerMremotengFormat.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/DataTableDeserializer.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/MsSql/DataTableSerializer.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionNodeSerializer27.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsSerializer.cs
#	mRemoteV1/Config/Serializers/CredentialProviderSerializer/CredentialRepositoryListDeserializer.cs
#	mRemoteV1/Config/Serializers/MiscSerializers/PuttyConnectionManagerDeserializer.cs
#	mRemoteV1/Config/Serializers/MiscSerializers/RemoteDesktopConnectionDeserializer.cs
#	mRemoteV1/Config/Serializers/MiscSerializers/RemoteDesktopConnectionManagerDeserializer.cs
#	mRemoteV1/Connection/AbstractConnectionRecord.cs
#	mRemoteV1/Connection/ConnectionInfo.cs
#	mRemoteV1/Connection/ConnectionInfoInheritance.cs
#	mRemoteV1/Connection/ConnectionInitiator.cs
#	mRemoteV1/Connection/ConnectionsService.cs
#	mRemoteV1/Connection/DefaultConnectionInfo.cs
#	mRemoteV1/Connection/Protocol/Http/Connection.Protocol.HTTPBase.cs
#	mRemoteV1/Connection/Protocol/ICA/IcaProtocol.cs
#	mRemoteV1/Connection/Protocol/IntegratedProgram.cs
#	mRemoteV1/Connection/Protocol/ProtocolBase.cs
#	mRemoteV1/Connection/Protocol/PuttyBase.cs
#	mRemoteV1/Connection/Protocol/RDP/RdpProtocol.cs
#	mRemoteV1/Connection/Protocol/VNC/Connection.Protocol.VNC.cs
#	mRemoteV1/Connection/PuttySessionInfo.cs
#	mRemoteV1/Credential/CredentialRecordTypeConverter.cs
#	mRemoteV1/Credential/CredentialServiceFacade.cs
#	mRemoteV1/Credential/CredentialServiceFactory.cs
#	mRemoteV1/Credential/Records/UnavailableCredentialRecord.cs
#	mRemoteV1/Credential/Repositories/XmlCredentialRepository.cs
#	mRemoteV1/Credential/Repositories/XmlCredentialRepositoryFactory.cs
#	mRemoteV1/Properties/Settings.settings
#	mRemoteV1/Schemas/mremoteng_confcons_v2_7.xsd
#	mRemoteV1/Tools/CustomCollections/FullyObservableCollection.cs
#	mRemoteV1/Tools/Extensions.cs
#	mRemoteV1/Tools/ExternalTool.cs
#	mRemoteV1/Tools/ExternalToolArgumentParser.cs
#	mRemoteV1/UI/Controls/Base/NGComboBox.cs
#	mRemoteV1/UI/Controls/ConnectionTree/ConnectionTree.cs
#	mRemoteV1/UI/Controls/CredentialRecordListBox.cs
#	mRemoteV1/UI/Controls/QuickConnectToolStrip.cs
#	mRemoteV1/UI/Forms/OptionsPages/CredentialsPage.cs
#	mRemoteV1/UI/Forms/OptionsPages/SqlServerPage.cs
#	mRemoteV1/UI/Forms/frmMain.cs
#	mRemoteV1/UI/Forms/frmOptions.cs
#	mRemoteV1/UI/Menu/ToolsMenu.cs
#	mRemoteV1/UI/Window/ConfigWindow.cs
#	mRemoteV1/UI/Window/ConnectionTreeWindow.Designer.cs
#	mRemoteV1/UI/Window/ConnectionTreeWindow.cs
#	mRemoteV1/UI/Window/ConnectionWindow.cs
#	mRemoteV1/app.config
2019-02-17 11:21:22 -06:00
David Sparer
ba62c17ea2 added some interface documentation 2019-01-27 18:01:34 -06:00
David Sparer
1f296f2f72 slight fix to the optional<T> tests 2019-01-27 16:59:02 -06:00
David Sparer
89bb4d45b3 minor tweaks to the import form 2019-01-27 16:58:39 -06:00
David Sparer
6e417ed47e ensure specified creds can be parsed even if no connectioninfo is selected 2019-01-27 12:33:14 -06:00
David Sparer
f3a7d97016 allow specifying specific credential record in external tool arguments
related to #680
2019-01-27 11:31:41 -06:00
David Sparer
530a4e165d extracted interface from credential service to make testing easier 2019-01-27 09:58:28 -06:00
David Sparer
9e217dba79 began creating cred import form 2019-01-23 22:16:08 -06:00
David Sparer
d524df6315 minor efficiency gain 2019-01-22 13:04:58 -06:00
David Sparer
5e224f5b5b ensure assigned cred id property cannot be null 2019-01-22 12:51:41 -06:00
David Sparer
3649927618 fixed bug with connection tree not appearing 2019-01-22 12:48:12 -06:00
David Sparer
4b736176bf request iconnectiontree interface in more places 2019-01-22 06:01:33 -06:00
David Sparer
f78ca1e9fd some more deserialization refactoring 2019-01-21 17:52:18 -06:00
David Sparer
739112a3ff added tests for SaveConnectionsOnEdit
had to create some new interfaces and refactor a bit to make it testable
2019-01-20 15:45:09 -06:00
David Sparer
923b9efd40 began converting the database serializers to work with cred manager 2019-01-20 14:51:09 -06:00
David Sparer
f0d0b5ae21 reduced duplication in rdcm tests 2019-01-20 13:46:34 -06:00
David Sparer
8906e08704 made the rdcm 2.7 importer compatible with credential management 2019-01-20 12:38:39 -06:00
David Sparer
9ead7e8e16 fixed an issue with putty session importing
cred id was not being set. also added a test for it
2019-01-20 11:18:40 -06:00
David Sparer
788ca79ece fixed rdp file importer to support cred manager 2019-01-20 11:15:33 -06:00
David Sparer
88558a353c fixed putty connection manager credential serialization 2019-01-19 14:22:04 -06:00
David Sparer
7fd9abbbc8 resolved csv deserializer test 2019-01-19 13:19:06 -06:00
David Sparer
b029b35df7 Merge remote-tracking branch 'origin/develop' into reapply_credential_manager
# Conflicts:
#	mRemoteV1/Properties/Settings.Designer.cs
#	mRemoteV1/Properties/Settings.settings
#	mRemoteV1/app.config
2019-01-19 12:51:43 -06:00
David Sparer
187ca5e55b started to redo how cred harvesting works in order to apply it to all importers/deserializers 2019-01-14 16:32:51 -06:00
David Sparer
53c26d8a91 updated tests and references to using cred records rather than user/domain/pass of the connection 2019-01-14 14:02:41 -06:00
David Sparer
159f25b8a1 fixed exttools arg parser tests 2019-01-14 09:54:19 -06:00
David Sparer
f4904f350e minor cleanup 2019-01-14 07:59:09 -06:00
David Sparer
ea59f6ad9d centralized credential lookup logic 2019-01-14 07:56:31 -06:00
David Sparer
4bba05f737 minor cleanup of cred harvester, slightly more efficient 2019-01-14 07:35:37 -06:00
David Sparer
569cf38f24 fixed a cred harvester test 2019-01-14 07:35:13 -06:00
David Sparer
6f8cde4d8e added a method comment 2019-01-13 14:45:07 -06:00
David Sparer
753ea9b421 connections now correctly use default cred if no credential is supplied 2019-01-13 14:42:24 -06:00
David Sparer
a5ea867b6d began hooking up the default credential to the manager 2019-01-13 12:14:28 -06:00
David Sparer
80228966f9 double clicking cred repo tree item opens editor page 2019-01-13 11:27:42 -06:00
David Sparer
b8298ecb14 minor display changes of the repo manager 2019-01-13 11:19:03 -06:00
David Sparer
ee1db6ff8b refactored some methods/fields to be more descriptive 2019-01-13 11:07:58 -06:00
David Sparer
aab1fb1dd2 minor cleanup 2019-01-13 11:05:45 -06:00
David Sparer
fbea9d1ede hooked up remaining cred repo management buttons 2019-01-13 10:57:33 -06:00
David Sparer
75cfc9c75c Merge branch 'develop' into reapply_credential_manager
# Conflicts:
#	mRemoteV1/Properties/Settings.Designer.cs
#	mRemoteV1/Properties/Settings.settings
#	mRemoteV1/UI/Forms/frmMain.cs
#	mRemoteV1/app.config
2019-01-13 08:52:51 -06:00
David Sparer
f60a481902 Merge branch 'develop' into reapply_credential_manager 2019-01-09 10:27:26 -06:00
David Sparer
e89c77a1bc fixed mis-merge 2019-01-09 10:26:40 -06:00
David Sparer
45766b8c12 Merge branch 'develop' into reapply_credential_manager
# Conflicts:
#	mRemoteV1/UI/Forms/OptionsPages/CredentialsPage.Designer.cs
2019-01-09 07:32:09 -06:00
David Sparer
1c44fcb070 Merge branch 'develop' into reapply_credential_manager
# Conflicts:
#	mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs
#	mRemoteV1/UI/Controls/Base/NGComboBox.cs
#	mRemoteV1/UI/Controls/NewPasswordWithVerification.Designer.cs
#	mRemoteV1/mRemoteV1.csproj
2019-01-05 07:48:43 -06:00
David Sparer
25aa815a82 began reworking the repo list management screen 2018-12-31 14:17:31 -06:00
David Sparer
a2542f1b18 rearranged some cred manager UI files 2018-12-31 11:13:48 -06:00
David Sparer
2e0979dc5a simplified screens for creating and editing creds 2018-12-31 11:05:36 -06:00
David Sparer
e2ebf25b7b Merge branch 'develop' into reapply_credential_manager 2018-12-31 09:32:47 -06:00
David Sparer
cc44b830a2 scaled images in the unlocker form 2018-12-30 15:28:47 -06:00
David Sparer
687cb6c7bc cred unlocker wont appear if there are no repos to unlock 2018-12-30 14:42:37 -06:00
David Sparer
15f028157e inserted a new page in the upgrade process to view harvested credentials 2018-12-30 14:33:42 -06:00
David Sparer
63ebef56b0 updated cred manager upgrade text with correct version 2018-12-30 11:32:24 -06:00
David Sparer
258093e52a when harvesting creds, consider Password when determining if a credential set is a duplicate
This prevents issues when the username and domain are the same but passwords are different (such as for local admin accounts)
2018-12-28 16:25:33 -06:00
David Sparer
b09fdcd5f5 credential objects now used when establishing connections 2018-12-28 16:25:33 -06:00
David Sparer
2277e95dd2 fixed tests 2018-12-28 16:25:33 -06:00
David Sparer
e0486bec7d simplified some of the credential management classes 2018-12-28 16:25:33 -06:00
David Sparer
29d44d103d fixed bug preventing FullyObservableCollection from generating events 2018-12-28 16:25:32 -06:00
David Sparer
44aa100566 minor change to chkbox location 2018-12-28 16:25:32 -06:00
David Sparer
23eb9cc44b added option to auto close the cred unlocker form when all repos are unlocked 2018-12-28 16:25:32 -06:00
David Sparer
f1797282d9 readded option to unlock cred repos on startup 2018-12-28 16:25:32 -06:00
David Sparer
0c0740d488 cred upgrade form now only lets you upgrade if all required fields are filled in 2018-12-28 16:25:32 -06:00
David Sparer
a4faaa20c3 cred manager upgrader now correctly uses custom password provided during upgrade 2018-12-28 16:25:29 -06:00
David Sparer
ce03b74d48 serialization is now working as expected 2018-12-28 16:25:29 -06:00
David Sparer
ce8ada05f5 hooked up the credential manager upgrader 2018-12-28 16:25:29 -06:00
David Sparer
b4dfe5beb6 hooked up tools menu button for cred manager 2018-12-28 16:25:29 -06:00
David Sparer
22ecf0d06f readded forms and controls for the credential manager 2018-12-28 16:25:29 -06:00
David Sparer
1e66787422 readded the credential record property to the connection info class 2018-12-28 16:25:28 -06:00
1096 changed files with 180562 additions and 59 deletions

View File

@@ -3,28 +3,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.77.3]
## [Unreleased]
### Added
- # #2123: Thycotic Secret Server - Added 2FA OTP support
## [1.77.2]
### Added
- #2086: Replace WebClient with async HttpClient for updater.
- #1850: Minify config xml
- #1770: Added missing RDP performance settings
- #1516: added API to access credential vault (Thycotic Secret Server) by specifying SSAPI:ID as username
- #1476: Configurable backups. Can now edit/set backup frequency, backup path, and max number of backup files.
- #1427: Fix RDP local desktop scale not taking effect on remote
- #1770: Added missing RDP performance settings
- #1332: Added option to hide menu strip container
- #870: Added option to push inheritance settings to child nodes recursively
- #545: Option to minimize to system tray on closing
- #503: SSH Execute a single command after login
- #420: SSH tunneling implemented
- #327: Added Alternative Shell for RDP settings
- #319: Override quick connect username when using user@domain
- #283: Support for native PowerShell remoting as new protocol
- #xxx: Add external connector to retrieve ip address from Amazon EC2 Instance IDs
- #1850: Minify config xml
### Changed
- #2102: Extended the field RenderingEngine from 10 chars to 16
- #2022: Replaced CefSharp with WebView2
- #2014: Revised icons
- #2013: Removed components check
@@ -35,33 +24,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- #1767: Turned about window into a simple popup form
- #1690: Replaced GeckoFX (Firefox) with CefSharp (Chromium)
- #1325: Language resource files cleanup
- #xxxx: Secret Server connector via new field "API User ID" instead of SSAPI: prefix
### Fixed
- #2125: Fixed string parsing logic for Quick Connect toolbar.
- #2122: Fix to avoid throwing exception incase if not able decrypt connections and ask to open another one or create a new.
- #2117: Fix of broken Links due migration to .NET 6 and branch renaming
- #2098: Fix failed BinaryFileTest
- #2097: Fix failed tests related to mRemoteNGTests.UI.Window.ConfigWindowTests
- #2096: Corrected encryption code of LegacyRijndaelCryptographyProvider
- #2089: Fixed the exception thrown by menu buttons "Documentation" and "Website"
- #2087: Fixed application crash, when the update file is launched from the application
- #2079: Fixed theme files not being copied to output directory
- #2012: Updated PuTTYNG to v0.76
- #1884: Allow setting Port when using MSSQL
- #1783: Added missing inheritance properties to SQL scripts
- #1773: Connection issue with MySql - Missing fields in
- #1773: Connection issue with mysql - Missing fields in
- #1756: Cannot type any character on MultiSSH toolbar
- #1720: Show configuration file name in title of password prompt form
- #1713: Sound redirection does not work if Clipboard redirection is set to No
- #1632: 1.77.1 breaks RDP drive and sound redirection
- #1610: Menu bar changes to English when canceling options form
- #1610: Menu bar changes to english when cancelling options form
- #1595: Unhandled exception when trying to browse through non existent multi ssh history with keyboard key strokes
- #1589: Update SQL tables instead of rewriting them
- #1465: REGRESSION: Smart Cards redirection to Remote Desktop not working
- #1363: Don't show "Disk Usage" button in installer
- #1337: Unhandled exception after closing mRemoteNG
- #359: Making a VNC connection to an unreachable host causes the application to not respond for 20-30 seconds
- #618: Do not break the Windows Clipboard Chain when exiting.
## [1.77.1] - 2019-09-02
### Added

View File

@@ -278,3 +278,62 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@@ -90,10 +90,6 @@ Copyright © 2004 Marc Merritt © 2008 Felix Deimel
# Included Components
**[CefSharp](https://github.com/cefsharp/CefSharp)**
Copyright © The CefSharp Authors
MIT License
**[DockPanel Suite](https://github.com/dockpanelsuite/dockpanelsuite)**
Copyright © 2018 @roken and @lextm (formerly Weifen Luo)
MIT License

View File

@@ -2,7 +2,7 @@
<br/><br/>
<p align="center">
<img width="450" src="https://github.com/mRemoteNG/mRemoteNG/blob/mRemoteNGProjectFiles/Header_dark.png">
<img width="500" src="https://github.com/mRemoteNG/mRemoteNG/blob/develop/mRemoteNGProjectFiles/Header_dark.png">
</p>
<p align="center">
@@ -23,9 +23,7 @@
<a href="https://gitter.im/mRemoteNG/PublicChat">
<img alt="Gitter" src="https://img.shields.io/gitter/room/mRemoteNG/PublicChat?label=Join%20the%20Chat&logo=Gitter&style=flat-square">
</a>
</p>
<p align="center">
<a href="https://www.paypal.com/paypalme/mremoteng">
<a href="https://www.paypal.me/DavidSparer">
<img alt="PayPal" src="https://img.shields.io/badge/%24-PayPal-blue.svg?label=Donate&logo=PayPal&style=flat-square">
</a>
<a href="bitcoin:16fUnHUM3k7W9Fvpc6dug7TAdfeGEcLbSg">
@@ -47,11 +45,11 @@
---
| Channel | Build Status | Downloads |
| Update Channel | Build Status | Downloads |
| ---------------|--------------|-----------|
| Stable | ![Build status](https://ci.appveyor.com/api/projects/status/rqwxjxldail7btcf?svg=true) | [![Github Releases (by Release)](https://img.shields.io/github/downloads/mRemoteNG/mRemoteNG/v1.76.20/total.svg)](https://github.com/mRemoteNG/mRemoteNG/releases/tag/v1.76.20) |
| Preview | ![Build status](https://ci.appveyor.com/api/projects/status/rqwxjxldail7btcf/branch/preview?svg=true) | [![Github Releases (by Release)](https://img.shields.io/github/downloads/mRemoteNG/mRemoteNG/v1.77.1/total.svg)](https://github.com/mRemoteNG/mRemoteNG/releases/tag/v1.77.1) |
| Nightly | ![Build status](https://ci.appveyor.com/api/projects/status/rqwxjxldail7btcf/branch/develop?svg=true) | [![Github Releases (by Release)](https://img.shields.io/github/downloads/mRemoteNG/mRemoteNG/2022.01.07-1.77.2-nb/total.svg)](https://github.com/mRemoteNG/mRemoteNG/releases/tag/2022.01.07-1.77.2-nb) |
| Stable | [![Build status](https://ci.appveyor.com/api/projects/status/k0sdbxmq90fgdmj6/branch/master?svg=true)](https://ci.appveyor.com/project/mremoteng/mremoteng/branch/master) | [![Github Releases (by Release)](https://img.shields.io/github/downloads/mRemoteNG/mRemoteNG/v1.76.20/total.svg)](https://github.com/mRemoteNG/mRemoteNG/releases/tag/v1.76.20) |
| Prerelease | [![Build status](https://ci.appveyor.com/api/projects/status/k0sdbxmq90fgdmj6/branch/develop?svg=true)](https://ci.appveyor.com/project/mremoteng/mremoteng/branch/develop) | [![Github Releases (by Release)](https://img.shields.io/github/downloads/mRemoteNG/mRemoteNG/v1.77.1/total.svg)](https://github.com/mRemoteNG/mRemoteNG/releases/tag/v1.77.1) |
| Nightly build | [![Build status](https://ci.appveyor.com/api/projects/status/k0sdbxmq90fgdmj6/branch/develop?svg=true)](https://ci.appveyor.com/project/mremoteng/mremoteng/branch/develop) | [![Github Releases (by Release)](https://img.shields.io/github/downloads/mRemoteNG/mRemoteNG/v1.77.2-nb/total.svg)](https://github.com/mRemoteNG/mRemoteNG/releases/tag/v1.77.2-nb) |
## Features
@@ -75,11 +73,24 @@ For a detailed feature list and general usage support, refer to the [Documentati
- [Windows 11](https://en.wikipedia.org/wiki/Windows_11)
- [Windows 10](https://en.wikipedia.org/wiki/Windows_10)
- [Windows 8.1](https://en.wikipedia.org/wiki/Windows_8.1)
- [Windows Server 2022](https://en.wikipedia.org/wiki/Windows_Server_2022)
- [Windows Server 2019](https://en.wikipedia.org/wiki/Windows_Server_2019)
- [Windows Server 2016](https://en.wikipedia.org/wiki/Windows_Server_2016)
- [Windows Server 2012 R2](https://en.wikipedia.org/wiki/Windows_Server_2012_R2)
### Packaging
Downloads are provided in three different packages.
#### Binary package
The binary package of mRemoteNG is a compiled version of mRemoteNG which comes in an MSI installer.
This is the most common way to install mRemoteNG and get up and running.
#### Portable package
The portable package contains a modified version of the executable which stores and loads all your settings from files in the application's directory.
This package can be used to run mRemoteNG from a USB stick and preserve your configuration wherever you go.
#### Source package
This contains the source code from which mRemoteNG is build.
@@ -87,8 +98,8 @@ You will need to compile it yourself using Visual Studio.
### Minimum Requirements
* [Microsoft Visual C++ Redistributable for Visual Studio 2015 - 2022](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads)
* [Microsoft .NET 6.0](https://dotnet.microsoft.com/download/dotnet/6.0)
* [Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017 and 2019](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads)
* [Microsoft .NET Framework 4.0](https://www.microsoft.com/en-us/download/details.aspx?id=17851)
* Microsoft Terminal Service Client 6.0 or later
* Needed if you use RDP. mstscax.dll and/or msrdp.ocx be registered.
@@ -102,31 +113,52 @@ mRemoteNG is available as a redistributable MSI package or as a portable ZIP pac
The MSI package of mRemoteNG can be installed using the command line:
`msiexec /i [/qn] C:\Path\To\mRemoteNG-Installer.exe [INSTALLDIR=value] [IGNOREPREREQUISITES=value] [/lv* <log path>]`
`msiexec /i C:\Path\To\mRemoteNG-Installer.exe [INSTALLDIR=value] [IGNOREPREREQUISITES=value]`
| Argument/Property | Value | Description |
| Property | Value | Description |
|-|-|-|
| /qn | `Silent Installation` | Will run the installer silently in the background. |
| /lv* | `Silent Installation` | Will write a logfile to the specified location. (For paths that contain spaces, enclose the path in double quotes) |
| INSTALLDIR | `folder path` | Allows you to set the installation directory from the command line. (For paths that contain spaces, enclose the path in double quotes) |
| INSTALLDIR | `folder path` | This allows you to set the installation directory from the command line. For paths that contain spaces, enclose the path in double quotes (""). This overrides any value found in the registry. |
| IGNOREPREREQUISITES | `0` or `1` | When set to `1`, the installer will not be halted if any prerequisite check is not met. You must still run the installer as administrator. |
## Manual Uninstall
#### Examples
**Install to a custom folder**
`msiexec /i C:\Path\To\mRemoteNG-Installer.msi INSTALLDIR="D:\Work Apps\mRemoteNG"`
**Ignore prerequisites during a normal install**
`msiexec /i C:\Path\To\mRemoteNG-Installer.msi IGNOREPREREQUISITES=1`
**Ignore prerequisites during a silent install**
`msiexec /i C:\Path\To\mRemoteNG-Installer.msi /qn IGNOREPREREQUISITES=1`
### Troubleshooting installation
Turn on verbose logging by using the `/lv* <log path>` argument at the command line.
`msiexec /i C:\Path\To\mRemoteNG-Installer.msi /l*v C:\mremoteng_install.log`
## Uninstall
### Standard Uninstall
mRemoteNG basic binary package can be uninstalled with Windows Control Panel. If for some reason it does not work please
follow information provided below for Manual Uninstall.
### Manual Uninstall
_If you are using the Portable version, simply deleting the folder that contains mRemoteNG should be sufficient. These uninstall instructions are only necessary for the normal binary .MSI installed version of mRemoteNG_
* Delete the folder where mRemoteNG was installed. By default, this is:
`%PROGRAMFILES%\mRemoteNG` (for versions before 1.77 on a x64 Windows its `%programfiles(x86)%\mRemoteNG`)
`%PROGRAMFILES%\mRemoteNG`
* Delete the mRemoteNG install entry from the following location. You may search for "mRemoteNG" in the DisplayName field:
* x86 Windows or mRemoteNG starting with v1.77: `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\`
* x64 Windows and mRemoteNG before 1.77: `HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\`
* Remove the following registry key: `HKLM\SOFTWARE\mRemoteNG` (on x64 Windows with mRemoteNG before 1.77 it's `HKLM\SOFTWARE\WOW6432Node\mRemoteNG`)
* Delete the mRemoteNG install entry from one of the following locations. Search for "mRemoteNG" in the DisplayName field:
* x86: ``HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\``
* x64: ``HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\``
* (Optional) If you would also like to delete user data remove `%LOCALAPPDATA%\mRemoteNG`
* (Optional) If you would also like to remove the connection configuration, delete `%APPDATA%\mRemoteNG`
* (Optional) If no other software uses it, the "Microsoft Windows Desktop Runtime" may be uninstalled too.
## Featured Projects
@@ -145,5 +177,5 @@ Check out the [Wiki page](https://github.com/mRemoteNG/mRemoteNG/wiki) on how to
</br>
<p align="center">
<img alt="Developed with ReSharper" src="https://github.com/mRemoteNG/mRemoteNG/blob/mRemoteNGProjectFiles/icon_ReSharper.png">
<img alt="Developed with ReSharper" src="https://github.com/mRemoteNG/mRemoteNG/blob/develop/mRemoteNGProjectFiles/icon_ReSharper.png">
</p>

View File

@@ -0,0 +1,294 @@
#####################################
# Authors: David Sparer & Jack Denton
# Summary:
# This is intended to be a template for creating connections in bulk. This uses the serializers directly from the mRemoteNG binaries.
# You will still need to create the connection info objects, but the library will handle serialization. It is expected that you
# are familiar with PowerShell. If this is not the case, reach out to the mRemoteNG community for help.
# Usage:
# Replace or modify the examples that are shown toward the end of the script to create your own connection info objects.
#####################################
foreach ($Path in 'HKLM:\SOFTWARE\WOW6432Node\mRemoteNG', 'HKLM:\SOFTWARE\mRemoteNG') {
Try {
$mRNGPath = (Get-ItemProperty -Path $Path -Name InstallDir -ErrorAction Stop).InstallDir
break
}
Catch {
continue
}
}
if (!$mRNGPath) {
Add-Type -AssemblyName System.Windows.Forms
$FolderBrowser = [System.Windows.Forms.FolderBrowserDialog]@{
Description = 'Please select the folder which contains mRemoteNG.exe'
ShowNewFolderButton = $false
}
$Response = $FolderBrowser.ShowDialog()
if ($Response.value__ -eq 1) {
$mRNGPath = $FolderBrowser.SelectedPath
}
elseif ($Response.value__ -eq 2) {
Write-Warning 'A folder containing mRemoteNG.exe has not been selected'
return
}
}
$null = [System.Reflection.Assembly]::LoadFile((Join-Path -Path $mRNGPath -ChildPath "mRemoteNG.exe"))
Add-Type -Path (Join-Path -Path $mRNGPath -ChildPath "BouncyCastle.Crypto.dll")
function ConvertTo-mRNGSerializedXml {
[CmdletBinding()]
Param (
[Parameter(Mandatory)]
[mRemoteNG.Connection.ConnectionInfo[]]
$Xml
)
function Get-ChildNodes {
Param ($Xml)
$Xml
if ($Xml -is [mRemoteNG.Container.ContainerInfo] -and $Xml.HasChildren()) {
foreach ($Node in $Xml.Children) {
Get-ChildNodes -Xml $Node
}
}
}
$AllNodes = Get-ChildNodes -Xml $Xml
if (
$AllNodes.Password -or
$AllNodes.RDGatewayPassword -or
$AllNodes.VNCProxyPassword
) {
$Password = Read-Host -Message 'If you have password protected your ConfCons.xml please enter the password here otherwise just press enter' -AsSecureString
}
else {
$Password = [securestring]::new()
}
$CryptoProvider = [mRemoteNG.Security.SymmetricEncryption.AeadCryptographyProvider]::new()
$SaveFilter = [mRemoteNG.Security.SaveFilter]::new()
$ConnectionNodeSerializer = [mRemoteNG.Config.Serializers.Xml.XmlConnectionNodeSerializer26]::new($CryptoProvider, $Password, $SaveFilter)
$XmlSerializer = [mRemoteNG.Config.Serializers.Xml.XmlConnectionsSerializer]::new($CryptoProvider, $ConnectionNodeSerializer)
$RootNode = [mRemoteNG.Tree.Root.RootNodeInfo]::new('Connection')
foreach ($Node in $Xml) {
$RootNode.AddChild($Node)
}
$XmlSerializer.Serialize($RootNode)
}
function New-mRNGConnection {
[CmdletBinding(DefaultParameterSetName = 'Credential')]
Param (
[Parameter(Mandatory)]
[string]
$Name,
[Parameter(Mandatory)]
[string]
$Hostname,
[Parameter(Mandatory)]
[mRemoteNG.Connection.Protocol.ProtocolType]
$Protocol,
[Parameter(ParameterSetName = 'Credential')]
[pscredential]
$Credential,
[Parameter(ParameterSetName = 'InheritCredential')]
[switch]
$InheritCredential,
[Parameter()]
[mRemoteNG.Container.ContainerInfo]
$ParentContainer,
[Parameter()]
[switch]
$PassThru
)
$Connection = [mRemoteNG.Connection.ConnectionInfo]@{
Name = $Name
Hostname = $Hostname
Protocol = $Protocol
}
if ($Credential) {
$Connection.Username = $Credential.GetNetworkCredential().UserName
$Connection.Domain = $Credential.GetNetworkCredential().Domain
$Connection.Password = $Credential.GetNetworkCredential().Password
}
if ($InheritCredential) {
$Connection.Inheritance.Username = $true
$Connection.Inheritance.Domain = $true
$Connection.Inheritance.Password = $true
}
if ($ParentContainer) {
$ParentContainer.AddChild($Connection)
if ($PSBoundParameters.ContainsKey('PassThru')) {
$Connection
}
}
else {
$Connection
}
}
function New-mRNGContainer {
[CmdletBinding(DefaultParameterSetName = 'Credential')]
Param (
[Parameter(Mandatory)]
[string]
$Name,
[Parameter(ParameterSetName = 'Credential')]
[pscredential]
$Credential,
[Parameter(ParameterSetName = 'InheritCredential')]
[switch]
$InheritCredential,
[Parameter()]
[mRemoteNG.Container.ContainerInfo]
$ParentContainer
)
$Container = [mRemoteNG.Container.ContainerInfo]@{
Name = $Name
}
if ($Credential) {
$Container.Username = $Credential.GetNetworkCredential().UserName
$Container.Domain = $Credential.GetNetworkCredential().Domain
$Container.Password = $Credential.GetNetworkCredential().Password
}
if ($InheritCredential) {
$Container.Inheritance.Username = $true
$Container.Inheritance.Domain = $true
$Container.Inheritance.Password = $true
}
if ($ParentContainer) {
$ParentContainer.AddChild($Container)
}
$Container
}
function Export-mRNGXml {
[CmdletBinding()]
param (
[Parameter()]
[string]
$Path,
[Parameter()]
[string]
$SerializedXml
)
$FilePathProvider = [mRemoteNG.Config.DataProviders.FileDataProvider]::new($Path)
$filePathProvider.Save($SerializedXml)
}
#----------------------------------------------------------------
# Example 1: serialize many connections, no containers
# Here you can define the number of connection info objects to create
# You can also provide a list of desired hostnames and iterate over those
$Connections = foreach ($i in 1..5) {
# Create new connection
$Splat = @{
Name = 'Server-{0:D2}' -f $i
Hostname = 'Server-{0:D2}' -f $i
Protocol = 'RDP'
InheritCredential = $true
}
New-mRNGConnection @Splat
}
# Serialize the connections
$SerializedXml = ConvertTo-mRNGSerializedXml -Xml $Connections
# Write the XML to a file ready to import into mRemoteNG
Export-mRNGXml -Path "$ENV:APPDATA\mRemoteNG\PowerShellGenerated.xml" -SerializedXml $SerializedXml
# Now open up mRemoteNG and press Ctrl+O and open up the exported XML file
#----------------------------------------------------------------
# Example 2: serialize a container which has connections
# You can also create containers and add connections and containers to them, which will be nested correctly when serialized
# If you specify the ParentContainer parameter for new connections then there will be no output unless the PassThru parameter is also used
$ProdServerCreds = Get-Credential
$ProdServers = New-mRNGContainer -Name 'ProdServers' -Credential $ProdServerCreds
foreach ($i in 1..3) {
# Create new connection
$Splat = @{
Name = 'Server-{0:D2}' -f $i
Hostname = 'Server-{0:D2}' -f $i
Protocol = 'RDP'
InheritCredential = $true
ParentContainer = $ProdServers
}
New-mRNGConnection @Splat
}
$ProdWebServers = New-mRNGContainer -Name 'WebServers' -ParentContainer $ProdServers -InheritCredential
foreach ($i in 1..3) {
# Create new connection
$Splat = @{
Name = 'WebServer-{0:D2}' -f $i
Hostname = 'WebServer-{0:D2}' -f $i
Protocol = 'SSH1'
InheritCredential = $true
ParentContainer = $ProdWebServers
}
New-mRNGConnection @Splat
}
$DevServers = New-mRNGContainer -Name 'DevServers'
foreach ($i in 1..3) {
# Create new connection
$Splat = @{
Name = 'DevServer-{0:D2}' -f $i
Hostname = 'DevServer-{0:D2}' -f $i
Protocol = 'RDP'
InheritCredential = $true
ParentContainer = $DevServers
PassThru = $true
}
# Specified the PassThru parameter in order to catch the connection and change a property
$Connection = New-mRNGConnection @Splat
$Connection.Resolution = 'FullScreen'
}
# Serialize the container
$SerializedXml = ConvertTo-mRNGSerializedXml -Xml $ProdServers, $DevServers
# Write the XML to a file ready to import into mRemoteNG
Export-mRNGXml -Path "$ENV:APPDATA\mRemoteNG\PowerShellGenerated.xml" -SerializedXml $SerializedXml
# Now open up mRemoteNG and press Ctrl+O and open up the exported XML file

Binary file not shown.

View File

@@ -0,0 +1,21 @@
param (
[string]
$SourcePath,
[string]
$DestinationDir
)
Write-Host $SourcePath
Write-Host $DestinationDir
if (!(Test-Path -Path $DestinationDir))
{
New-Item -Path $DestinationDir -ItemType "directory"
}
$sourceFiles = Get-ChildItem -Path $SourcePath -Recurse | ?{$_.Extension -match "exe|msi"}
foreach ($item in $sourceFiles)
{
Copy-Item -Path $item.FullName -Destination $DestinationDir -Force
}

View File

@@ -0,0 +1,109 @@
#Requires -Version 4.0
param (
[string]
[Parameter(Mandatory=$true)]
$TagName,
[string]
[Parameter(Mandatory=$true)]
[ValidateSet("Stable","Beta","Development")]
$UpdateChannel
)
function New-MsiUpdateFileContent {
param (
[System.IO.FileInfo]
[Parameter(Mandatory=$true)]
$MsiFile,
[string]
[Parameter(Mandatory=$true)]
$TagName
)
$version = $MsiFile.BaseName -replace "[a-zA-Z-]*"
$certThumbprint = (Get-AuthenticodeSignature -FilePath $MsiFile).SignerCertificate.Thumbprint
$hash = Get-FileHash -Algorithm SHA512 $MsiFile | % { $_.Hash }
$fileContents = `
"Version: $version
dURL: https://github.com/mRemoteNG/mRemoteNG/releases/download/$TagName/$($MsiFile.Name)
clURL: https://raw.githubusercontent.com/mRemoteNG/mRemoteNG/$TagName/CHANGELOG.md
CertificateThumbprint: $certThumbprint
Checksum: $hash"
Write-Output $fileContents
}
function New-ZipUpdateFileContent {
param (
[System.IO.FileInfo]
[Parameter(Mandatory=$true)]
$ZipFile,
[string]
[Parameter(Mandatory=$true)]
$TagName
)
$version = $ZipFile.BaseName -replace "[a-zA-Z-]*"
$hash = Get-FileHash -Algorithm SHA512 $ZipFile | % { $_.Hash }
$fileContents = `
"Version: $version
dURL: https://github.com/mRemoteNG/mRemoteNG/releases/download/$TagName/$($ZipFile.Name)
clURL: https://raw.githubusercontent.com/mRemoteNG/mRemoteNG/$TagName/CHANGELOG.TXT
Checksum: $hash"
Write-Output $fileContents
}
function Resolve-UpdateCheckFileName {
param (
[string]
[Parameter(Mandatory=$true)]
[ValidateSet("Stable","Beta","Development")]
$UpdateChannel,
[string]
[Parameter(Mandatory=$true)]
[ValidateSet("Normal","Portable")]
$Type
)
$fileName = ""
if ($UpdateChannel -eq "Beta") { $fileName += "beta-" }
elseif ($UpdateChannel -eq "Development") { $fileName += "dev-" }
$fileName += "update"
if ($Type -eq "Portable") { $fileName += "-portable" }
$fileName += ".txt"
Write-Output $fileName
}
$releaseFolder = Join-Path -Path $PSScriptRoot -ChildPath "..\Release" -Resolve
# build msi update file
$msiFile = Get-ChildItem -Path "$releaseFolder\*.msi" | sort LastWriteTime | select -last 1
$msiUpdateContents = New-MsiUpdateFileContent -MsiFile $msiFile -TagName $TagName
$msiUpdateFileName = Resolve-UpdateCheckFileName -UpdateChannel $UpdateChannel -Type Normal
Write-Output "`n`nMSI Update Check File Contents ($msiUpdateFileName)`n------------------------------"
Tee-Object -InputObject $msiUpdateContents -FilePath "$releaseFolder\$msiUpdateFileName"
# build zip update file
$zipFile = Get-ChildItem -Path "$releaseFolder\*.zip" | sort LastWriteTime | select -last 1
$zipUpdateContents = New-ZipUpdateFileContent -ZipFile $zipFile -TagName $TagName
$zipUpdateFileName = Resolve-UpdateCheckFileName -UpdateChannel $UpdateChannel -Type Portable
Write-Output "`n`nZip Update Check File Contents ($zipUpdateFileName)`n------------------------------"
Tee-Object -InputObject $zipUpdateContents -FilePath "$releaseFolder\$zipUpdateFileName"

BIN
Tools/exes/dumpbin.exe Normal file

Binary file not shown.

BIN
Tools/exes/editbin.exe Normal file

Binary file not shown.

BIN
Tools/exes/link.exe Normal file

Binary file not shown.

BIN
Tools/exes/mspdbcore.dll Normal file

Binary file not shown.

BIN
Tools/exes/sigcheck.exe Normal file

Binary file not shown.

61
Tools/find_vstool.ps1 Normal file
View File

@@ -0,0 +1,61 @@
[CmdletBinding()]
param (
[string]
# Name of the file to find
$FileName
)
function EditBinCertificateIsValid() {
param (
[string]
$Path
)
# Verify file certificate
$valid_microsoft_cert_thumbprints = @(
"3BDA323E552DB1FDE5F4FBEE75D6D5B2B187EEDC",
"98ED99A67886D020C564923B7DF25E9AC019DF26",
"108E2BA23632620C427C570B6D9DB51AC31387FE",
"5EAD300DC7E4D637948ECB0ED829A072BD152E17"
)
$file_signature = Get-AuthenticodeSignature -FilePath $Path
if (($file_signature.Status -ne "Valid") -or ($valid_microsoft_cert_thumbprints -notcontains $file_signature.SignerCertificate.Thumbprint)) {
Write-Warning "Could not validate the signature of $Path"
return $false
} else {
return $true
}
}
function ToolCanBeExecuted {
param (
[string]
$Path
)
$null = & $Path
Write-Output ($LASTEXITCODE -gt 0)
}
$rootSearchPaths = @(
[System.IO.Directory]::EnumerateFileSystemEntries("C:\Program Files", "*Visual Studio*", [System.IO.SearchOption]::TopDirectoryOnly),
[System.IO.Directory]::EnumerateFileSystemEntries("C:\Program Files (x86)", "*Visual Studio*", [System.IO.SearchOption]::TopDirectoryOnly)
)
# Returns the first full path to the $FileName that our search can find
foreach ($searchPath in $rootSearchPaths) {
foreach ($visualStudioFolder in $searchPath) {
Write-Verbose "Searching in folder '$visualStudioFolder'"
$matchingExes = [System.IO.Directory]::EnumerateFileSystemEntries($visualStudioFolder, $FileName, [System.IO.SearchOption]::AllDirectories)
foreach ($matchingExe in $matchingExes) {
if ((EditBinCertificateIsValid -Path $matchingExe) -and (ToolCanBeExecuted -Path $matchingExe)) {
return $matchingExe
}
}
}
}
Write-Error "Could not find any valid file by the name $FileName." -ErrorAction Stop

235
Tools/github_functions.ps1 Normal file
View File

@@ -0,0 +1,235 @@
$githubUrl = 'https://api.github.com'
# GitHub doesn't support the default powershell protocol (TLS 1.0)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
function Publish-GitHubRelease {
param (
[string]
[Parameter(Mandatory=$true)]
#
$Owner,
[string]
[Parameter(Mandatory=$true)]
#
$Repository,
[string]
[Parameter(Mandatory=$true)]
#
$ReleaseTitle,
[string]
[Parameter(Mandatory=$true)]
#
$TagName,
[string]
[Parameter(Mandatory=$true)]
# Either the SHA of the commit to target or the branch name.
$TargetCommitish,
[string]
[Parameter(Mandatory=$true)]
#
$Description,
[bool]
[Parameter(Mandatory=$true)]
#
$IsDraft,
[bool]
[Parameter(Mandatory=$true)]
#
$IsPrerelease,
[string]
[Parameter(Mandatory=$true)]
# The OAuth2 token to use for authentication.
$AuthToken
)
$body = New-GitHubReleaseRequestBody -TagName $TagName -TargetCommitish $TargetCommitish -ReleaseTitle $ReleaseTitle -Description $Description -IsDraft $IsDraft -IsPrerelease $IsPrerelease
$req_publishRelease = Invoke-WebRequest -Uri "$githubUrl/repos/$Owner/$Repository/releases" -Method Post -Headers @{"Authorization"="token $AuthToken"} -Body $body -ErrorAction Stop
$response_publishRelease = ConvertFrom-Json -InputObject $req_publishRelease.Content
Write-Output $response_publishRelease
}
function Edit-GitHubRelease {
param (
[string]
#[Parameter(Mandatory=$true)]
#
$Owner,
[string]
#[Parameter(Mandatory=$true)]
#
$Repository,
[string]
#[Parameter(Mandatory=$true)]
#
$ReleaseId,
[string]
#
$ReleaseTitle,
[string]
#
$TagName,
[string]
# Either the SHA of the commit to target or the branch name.
$TargetCommitish,
[string]
#
$Description,
[bool]
#
$IsDraft,
[bool]
#
$IsPrerelease,
[string]
#[Parameter(Mandatory=$true)]
# The OAuth2 token to use for authentication.
$AuthToken
)
$body_params = @{
"TagName" = $TagName
"TargetCommitish" = $TargetCommitish
"ReleaseTitle" = $ReleaseTitle
"Description" = $Description
}
if ($PSBoundParameters.ContainsKey("IsDraft")) { $body_params.Add("IsDraft", $IsDraft) }
if ($PSBoundParameters.ContainsKey("IsPrerelease")) { $body_params.Add("IsPrerelease", $IsPrerelease) }
$body = New-GitHubReleaseRequestBody @body_params
$req_editRelease = Invoke-WebRequest -Uri "$githubUrl/repos/$Owner/$Repository/releases/$ReleaseId" -Method Post -Headers @{"Authorization"="token $AuthToken"} -Body $body -ErrorAction Stop
$response_editRelease = ConvertFrom-Json -InputObject $req_editRelease.Content
Write-Output $response_editRelease
}
function Get-GitHubRelease {
param (
[string]
[Parameter(Mandatory=$true)]
#
$Owner,
[string]
[Parameter(Mandatory=$true)]
#
$Repository,
[string]
[Parameter(Mandatory=$true)]
#
$ReleaseId,
[string]
[Parameter(Mandatory=$true)]
# The OAuth2 token to use for authentication.
$AuthToken
)
$req_getRelease = Invoke-WebRequest -Uri "$githubUrl/repos/$Owner/$Repository/releases/$ReleaseId" -Method Get -Headers @{"Authorization"="token $AuthToken"} -ErrorAction Stop
$response_getRelease = ConvertFrom-Json -InputObject $req_getRelease.Content
Write-Output $response_getRelease
}
function Upload-GitHubReleaseAsset {
param (
[string]
[Parameter(Mandatory=$true)]
$UploadUri,
[string]
[Parameter(Mandatory=$true)]
# Path to the file to upload with the release
$FilePath,
[string]
[Parameter(Mandatory=$true)]
# Content type of the file
$ContentType,
[string]
[Parameter(Mandatory=$true)]
# The OAuth2 token to use for authentication.
$AuthToken,
[string]
# A short description label for the asset
$Label = ""
)
$UploadUri = $UploadUri -replace "(\{[\w,\?]*\})$"
$files = Get-Item -Path $FilePath
$labelParam = ""
if ($Label -ne "") {
$labelParam = "&label=$Label"
}
# Get-Item could produce an array of files if a wildcard is provided. (C:\*.txt)
# Upload each matching item individually
foreach ($file in $files) {
Write-Output "Uploading asset to GitHub release: '$($file.FullName)'"
$req_uploadZipAsset = Invoke-WebRequest -Uri "$($UploadUri)?name=$($file.Name)$labelParam" -Method Post -Headers @{"Authorization"="token $AuthToken"} -ContentType $ContentType -InFile $file.FullName -ErrorAction Stop
}
}
function New-GitHubReleaseRequestBody {
param (
[string]
#
$TagName,
[string]
# Either the SHA of the commit to target or the branch name.
$TargetCommitish,
[string]
# Title of the release
$ReleaseTitle,
[string]
# Description of the release
$Description,
[bool]
# Is this a draft?
$IsDraft,
[bool]
# Is this a pre-release?
$IsPrerelease
)
$body_params = [ordered]@{}
if ($TagName -ne "") { $body_params.Add("tag_name", $TagName) }
if ($TargetCommitish -ne "") { $body_params.Add("target_commitish", $TargetCommitish) }
if ($ReleaseTitle -ne "") { $body_params.Add("name", $ReleaseTitle) }
if ($Description -ne "") { $body_params.Add("body", $Description) }
if ($PSBoundParameters.ContainsKey("IsDraft")) { $body_params.Add("draft", $IsDraft) }
if ($PSBoundParameters.ContainsKey("IsPrerelease")) { $body_params.Add("prerelease", $IsPrerelease) }
$json_body = ConvertTo-Json -InputObject $body_params -Compress
Write-Output $json_body
}

View File

@@ -0,0 +1,44 @@
param (
[string]
[Parameter(Mandatory=$true)]
$SolutionDir,
[string]
[Parameter(Mandatory=$true)]
$TargetDir,
[string]
[Parameter(Mandatory=$true)]
$TargetFileName,
[string]
[Parameter(Mandatory=$true)]
$ConfigurationName,
[string]
$CertificatePath,
[string]
$CertificatePassword,
[string[]]
$ExcludeFromSigning
)
Write-Output "+=================================================================+"
Write-Output "| Beginning mRemoteNG Installer Post Build |"
Write-Output "+=================================================================+"
Format-Table -AutoSize -Wrap -InputObject @{
"SolutionDir" = $SolutionDir
"TargetDir" = $TargetDir
"TargetFileName" = $TargetFileName
"ConfigurationName" = $ConfigurationName
"CertificatePath" = $CertificatePath
"ExcludeFromSigning" = $ExcludeFromSigning
}
& "$PSScriptRoot\sign_binaries.ps1" -TargetDir $TargetDir -CertificatePath $CertificatePath -CertificatePassword $CertificatePassword -ConfigurationName $ConfigurationName -Exclude $ExcludeFromSigning -SolutionDir $SolutionDir
& "$PSScriptRoot\verify_binary_signatures.ps1" -TargetDir $TargetDir -ConfigurationName $ConfigurationName -CertificatePath $CertificatePath -SolutionDir $SolutionDir
& "$PSScriptRoot\rename_installer_with_version.ps1" -SolutionDir $SolutionDir
& "$PSScriptRoot\copy_release_installer.ps1" -SourcePath $TargetDir -DestinationDir (Join-Path -Path $SolutionDir -ChildPath "Release")

View File

@@ -0,0 +1,45 @@
param (
[string]
[Parameter(Mandatory=$true)]
$SolutionDir,
[string]
[Parameter(Mandatory=$true)]
$TargetDir,
[string]
[Parameter(Mandatory=$true)]
$TargetFileName,
[string]
[Parameter(Mandatory=$true)]
$ConfigurationName,
[string]
$CertificatePath,
[string]
$CertificatePassword,
[string[]]
$ExcludeFromSigning
)
Write-Output "+=================================================================+"
Write-Output "| Beginning mRemoteNG Post Build |"
Write-Output "+=================================================================+"
Format-Table -AutoSize -Wrap -InputObject @{
"SolutionDir" = $SolutionDir
"TargetDir" = $TargetDir
"TargetFileName" = $TargetFileName
"ConfigurationName" = $ConfigurationName
"CertificatePath" = $CertificatePath
"ExcludeFromSigning" = $ExcludeFromSigning
}
& "$PSScriptRoot\set_LargeAddressAware.ps1" -TargetDir $TargetDir -TargetFileName $TargetFileName
& "$PSScriptRoot\verify_LargeAddressAware.ps1" -TargetDir $TargetDir -TargetFileName $TargetFileName
& "$PSScriptRoot\tidy_files_for_release.ps1" -TargetDir $TargetDir -ConfigurationName $ConfigurationName
& "$PSScriptRoot\sign_binaries.ps1" -TargetDir $TargetDir -CertificatePath $CertificatePath -CertificatePassword $CertificatePassword -ConfigurationName $ConfigurationName -Exclude $ExcludeFromSigning -SolutionDir $SolutionDir
& "$PSScriptRoot\verify_binary_signatures.ps1" -TargetDir $TargetDir -ConfigurationName $ConfigurationName -CertificatePath $CertificatePath -SolutionDir $SolutionDir
& "$PSScriptRoot\zip_files.ps1" -SolutionDir $SolutionDir -TargetDir $TargetDir -ConfigurationName $ConfigurationName

View File

@@ -0,0 +1,25 @@
param (
[string]
[Parameter(Mandatory=$true)]
#
$Owner,
[string]
[Parameter(Mandatory=$true)]
#
$Repository,
[string]
[Parameter(Mandatory=$true)]
#
$ReleaseId,
[string]
[Parameter(Mandatory=$true)]
# The OAuth2 token to use for authentication.
$AuthToken
)
. "$PSScriptRoot\github_functions.ps1"
Edit-GitHubRelease -Owner $Owner -Repository $Repository -ReleaseId $ReleaseId -AuthToken $AuthToken -IsDraft $false

View File

@@ -0,0 +1,81 @@
param (
[string]
[Parameter(Mandatory=$true)]
#
$Owner,
[string]
[Parameter(Mandatory=$true)]
#
$Repository,
[string]
[Parameter(Mandatory=$true)]
#
$ReleaseTitle,
[string]
[Parameter(Mandatory=$true)]
#
$TagName,
[string]
[Parameter(Mandatory=$true)]
# Either the SHA of the commit to target or the branch name.
$TargetCommitish,
[string]
[Parameter(Mandatory=$true)]
#
$Description,
[string]
[Parameter(Mandatory=$true)]
[ValidateSet("true","false")]
# true/false
$IsDraft,
[string]
[Parameter(Mandatory=$true)]
[ValidateSet("true","false")]
# true/false
$IsPrerelease,
[string]
[Parameter(Mandatory=$true)]
# Path to the folder which contains release assets to upload
$ReleaseFolderPath,
[string]
[Parameter(Mandatory=$true)]
# The OAuth2 token to use for authentication.
$AuthToken,
[switch]
# Enable this switch to treat $Description as a Base64 encoded string. It will be decoded before being used elsewhere in the script.
$DescriptionIsBase64Encoded
)
if ($DescriptionIsBase64Encoded) {
$Description = ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Description)))
}
. "$PSScriptRoot\github_functions.ps1"
$releaseFolderItems = Get-ChildItem -Path $ReleaseFolderPath
$mrngPortablePath = ($releaseFolderItems | ?{$_.Name -match "portable-[\d\.]+\.zip"}).FullName
$mrngNormalPath = ($releaseFolderItems | ?{$_.Name -match "installer-[\d\.]+\.msi"}).FullName
$mrngPortableSymbolsPath = ($releaseFolderItems | ?{$_.Name -match "mremoteng-portable-symbols-[\d\.]+\.zip"}).FullName
$mrngNormalSymbolsPath = ($releaseFolderItems | ?{$_.Name -match "mremoteng-symbols-[\d\.]+\.zip"}).FullName
$release = Publish-GitHubRelease -Owner $Owner -Repository $Repository -ReleaseTitle $ReleaseTitle -TagName $TagName -TargetCommitish $TargetCommitish -Description $Description -IsDraft ([bool]::Parse($IsDraft)) -IsPrerelease ([bool]::Parse($IsPrerelease)) -AuthToken $AuthToken
$zipUpload = Upload-GitHubReleaseAsset -UploadUri $release.upload_url -FilePath $mrngPortablePath -ContentType "application/zip" -AuthToken $AuthToken -Label "Portable Edition (zip)"
$msiUpload = Upload-GitHubReleaseAsset -UploadUri $release.upload_url -FilePath $mrngNormalPath -ContentType "application/octet-stream" -AuthToken $AuthToken -Label "Normal Edition (msi)"
$portableEditionSymbols = Upload-GitHubReleaseAsset -UploadUri $release.upload_url -FilePath $mrngPortableSymbolsPath -ContentType "application/zip" -AuthToken $AuthToken -Label "Portable Edition Debug Symbols"
$normalEditionSymbols = Upload-GitHubReleaseAsset -UploadUri $release.upload_url -FilePath $mrngNormalSymbolsPath -ContentType "application/zip" -AuthToken $AuthToken -Label "Normal Edition Debug Symbols"
Write-Output (Get-GitHubRelease -Owner $Owner -Repository $Repository -ReleaseId $release.id -AuthToken $AuthToken)

View File

@@ -0,0 +1,29 @@
param (
[string]
$SolutionDir
)
$renameTarget = $SolutionDir + "mRemoteNGInstaller\Installer\bin\Release\en-US\mRemoteNG-Installer.msi"
Write-Host $SolutionDir
Write-Host $renameTarget
$targetVersionedFile = "$SolutionDir\mRemoteNG\bin\Release\mRemoteNG.exe"
$version = &"$SolutionDir\Tools\exes\sigcheck.exe" /accepteula -q -n $targetVersionedFile
$renameTargetFileObject = Get-Item -Path $renameTarget -ErrorAction SilentlyContinue
if ($renameTargetFileObject)
{
# Build the new file name
$oldFileName = $renameTargetFileObject.Name
$newFileName = $oldFileName -replace "$("\"+$renameTargetFileObject.Extension)",$("-"+$version+$renameTargetFileObject.Extension)
Write-Host $oldFileName
Write-Host $newFileName
# Delete any items that already exist with the new name (effectively an overwrite)
Remove-Item -Path "$($renameTargetFileObject.Directory.FullName)\$newFileName" -ErrorAction SilentlyContinue
# Rename file
Rename-Item -Path $renameTarget -NewName $newFileName -ErrorAction SilentlyContinue
}

View File

@@ -0,0 +1,22 @@
[CmdletBinding()]
param (
[string]
[Parameter(Mandatory=$true)]
$TargetDir,
[string]
[Parameter(Mandatory=$true)]
$TargetFileName
)
Write-Output "===== Beginning $($PSCmdlet.MyInvocation.MyCommand) ====="
$path_editBin = Join-Path -Path $PSScriptRoot -ChildPath "exes\editbin.exe"
$path_outputExe = Join-Path -Path $TargetDir -ChildPath $TargetFileName
# Set LargeAddressAware
Write-Output "Setting LargeAddressAware on binary file:`n`"$path_outputExe`" `nwith:`n`"$path_editBin`""
& "$path_editBin" /largeaddressaware "$path_outputExe"
Write-Output ""

88
Tools/sign_binaries.ps1 Normal file
View File

@@ -0,0 +1,88 @@
param (
[string]
[Parameter(Mandatory=$true)]
$TargetDir,
[string]
[Parameter(Mandatory=$true)]
$ConfigurationName,
[string[]]
# File names to exclude from signing
$Exclude,
[string]
[AllowEmptyString()]
# The code signing certificate to use when signing the files.
$CertificatePath,
[string]
# Password to unlock the code signing certificate.
$CertificatePassword,
[string]
$SolutionDir
)
Write-Output "===== Beginning $($PSCmdlet.MyInvocation.MyCommand) ====="
$timeserver = "http://timestamp.verisign.com/scripts/timstamp.dll"
# validate release versions and if the certificate value was passed
if ($ConfigurationName -match "Release" -And ($CertificatePath)) {
if(-Not ([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER)) ) {
$CertificatePath = Join-Path -Path $SolutionDir -ChildPath $CertificatePath
}
# make sure the cert is actually available
if ($CertificatePath -eq "" -or !(Test-Path -Path $CertificatePath -PathType Leaf))
{
Write-Output "Certificate is not present - we won't sign files."
return
}
if ($CertificatePassword -eq "") {
Write-Output "No certificate password was provided - we won't sign files."
return
}
try {
$certKeyStore = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertificatePath, $CertificatePassword, $certKeyStore) -ErrorAction Stop
} catch {
Write-Output "Error loading certificate file - we won't sign files."
Write-Output $Error[0]
return
}
# Sign MSI if we are building a release version and the certificate is available
Write-Output "Signing Binaries"
Write-Output "Getting files from path: $TargetDir"
$signableFiles = Get-ChildItem -Path $TargetDir -Recurse | ?{$_.Extension -match "dll|exe|msi"} | ?{$Exclude -notcontains $_.Name}
$excluded_files = Get-ChildItem -Path $TargetDir -Recurse | ?{$_.Extension -match "dll|exe|msi"} | ?{$Exclude -contains $_.Name}
$excluded_files | ForEach-Object `
-Begin { Write-Output "The following files were excluded from signing due to being on the exclusion list:" } `
-Process { Write-Output "-- $($_.FullName)" }
Write-Output "Signable files count: $($signableFiles.Count)"
foreach ($file in $signableFiles) {
Set-AuthenticodeSignature -Certificate $cert -TimestampServer $timeserver -IncludeChain all -FilePath $file.FullName
}
# Release certificate
if ($cert -ne $null) {
$cert.Dispose()
}
} else {
Write-Output "This is not a release build or CertificatePath wasn't provided - we won't sign files."
Write-Output "Config: $($ConfigurationName)`tCertPath: $($CertificatePath)"
}
Write-Output ""

31
Tools/signfiles.ps1 Normal file
View File

@@ -0,0 +1,31 @@
param(
[string]
[Parameter(Mandatory=$true)]
# Folder path that contains the files you would like to sign. Recursive.
$PathToSignableFiles,
[string]
# The code signing certificate to use when signing the files.
$CertificatePath = "C:\mRemoteNG_code_signing_cert.pfx",
[SecureString]
# Password to unlock the code signing certificate.
$CertificatePassword = (Get-Credential -Message "Enter password for the mRemoteNG code signing certificate" -UserName "USERNAME NOT NEEDED").Password,
[string[]]
# File names to exclude from signing
$Exclude
)
$timeserver = "http://timestamp.verisign.com/scripts/timstamp.dll"
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertificatePath, $CertificatePassword)
Write-Output "Getting files from path: $PathToSignableFiles"
$signableFiles = Get-ChildItem -Path $PathToSignableFiles -Recurse | ?{$_.Extension -match "dll|exe|msi"} | ?{$Exclude -notcontains $_.Name}
Write-Output "Signable files count: $($signableFiles.Count)"
foreach ($file in $signableFiles) {
Set-AuthenticodeSignature -Certificate $cert -TimestampServer $timeserver -IncludeChain all -FilePath $file.FullName
}

View File

@@ -0,0 +1,39 @@
param (
[string]
[Parameter(Mandatory=$true)]
$TargetDir,
[string]
[Parameter(Mandatory=$true)]
$ConfigurationName
)
Write-Output "===== Beginning $($PSCmdlet.MyInvocation.MyCommand) ====="
# Remove unnecessary files from Release versions
if ($ConfigurationName -match "Release") {
Write-Output "Removing unnecessary files from Release versions"
$test = Join-Path -Path $TargetDir -ChildPath "app.publish"
if (Test-Path $test -PathType Container) {
Remove-Item -Path (Join-Path -Path $TargetDir -ChildPath "app.publish") -Recurse -Force
}
$filesToDelete = Get-ChildItem -Path $TargetDir -Recurse -Include @(
"*.publish",
"*.xml",
"*.backup",
"*.log",
"*vshost*",
"*.tmp"
) -Exclude @(
"mRemoteNG.VisualElementsManifest.xml"
)
Remove-Item -Path $filesToDelete.FullName
Write-Output $filesToDelete.FullName
}
else {
Write-Output "We will not remove anything - this is not a release build."
}
Write-Output ""

View File

@@ -0,0 +1,17 @@
# $FullPath Full path to the Microsoft executable to validate
param (
[string]
[Parameter(Mandatory=$true)]
$FullPath
)
$validMSCertThumbprints = @("3BDA323E552DB1FDE5F4FBEE75D6D5B2B187EEDC", "108E2BA23632620C427C570B6D9DB51AC31387FE", "98ED99A67886D020C564923B7DF25E9AC019DF26", "5EAD300DC7E4D637948ECB0ED829A072BD152E17")
$exeSignature = Get-AuthenticodeSignature -FilePath $FullPath
$baseErrorMsg = "Could not validate the certificate of $FullPath. "
if ($exeSignature.Status -ne "Valid") {
Write-Error -Message ($baseErrorMsg+"The signature was invalid.") -ErrorAction Stop
}
elseif ($validMSCertThumbprints -notcontains $exeSignature.SignerCertificate.Thumbprint) {
Write-Error -Message ($baseErrorMsg+"The certificate thumbprint ($($exeSignature.SignerCertificate.Thumbprint)) is not trusted.") -ErrorAction Stop
}

View File

@@ -0,0 +1,30 @@
[CmdletBinding()]
param (
[string]
[Parameter(Mandatory=$true)]
$TargetDir,
[string]
[Parameter(Mandatory=$true)]
$TargetFileName
)
Write-Output "===== Beginning $($PSCmdlet.MyInvocation.MyCommand) ====="
$path_dumpBin = Join-Path -Path $PSScriptRoot -ChildPath "exes\dumpbin.exe"
$path_outputExe = Join-Path -Path $TargetDir -ChildPath $TargetFileName
# Dump exe header
$output = & "$path_dumpBin" /NOLOGO /HEADERS "$path_outputExe" | Select-String large
if ($output -eq $null)
{
Write-Warning "Could not validate LargeAddressAware"
}
else
{
Write-Output $output.ToString().TrimStart(" ")
}
Write-Output ""

View File

@@ -0,0 +1,59 @@
param (
[string]
[Parameter(Mandatory=$true)]
$TargetDir,
[string]
[Parameter(Mandatory=$true)]
$ConfigurationName,
[string]
[Parameter(Mandatory=$true)]
[AllowEmptyString()]
# The code signing certificate to use when signing the files.
$CertificatePath,
[string]
[Parameter(Mandatory=$true)]
$SolutionDir
)
Write-Output "===== Beginning $($PSCmdlet.MyInvocation.MyCommand) ====="
# validate release versions and if the certificate value was passed
if ($ConfigurationName -match "Release" -And ($CertificatePath)) {
if(-Not ([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER)) ) {
$CertificatePath = Join-Path -Path $SolutionDir -ChildPath $CertificatePath
}
# make sure the cert is actually available
if ($CertificatePath -eq "" -or !(Test-Path -Path $CertificatePath -PathType Leaf))
{
Write-Output "Certificate is not present - files likely not signed - we won't verify file signatures."
return
}
Write-Output "Verifying signature of binaries"
Write-Output "Getting files from path: $TargetDir"
$signableFiles = Get-ChildItem -Path $TargetDir -Recurse | ?{$_.Extension -match "dll|exe|msi"}
Write-Output "Signable files count: $($signableFiles.Count)"
$badSignatureFound = $false
foreach ($file in $signableFiles) {
$signature = Get-AuthenticodeSignature -FilePath $file.FullName
if ($signature.Status -ne "Valid") {
Write-Warning "File $($file.FullName) does not have a valid signature."
$badSignatureFound = $true
}
}
if ($badSignatureFound) {
Write-Output "One or more files were improperly signed."
} else {
Write-Output "All files have valid signatures."
}
} else {
Write-Output "This is not a release build or CertificatePath wasn't provided - we won't verify file signatures."
Write-Output "Config: $($ConfigurationName)`tCertPath: $($CertificatePath)"
}
Write-Output ""

72
Tools/zip_files.ps1 Normal file
View File

@@ -0,0 +1,72 @@
param (
[string]
[Parameter(Mandatory=$true)]
$SolutionDir,
[string]
[Parameter(Mandatory=$true)]
$TargetDir,
[string]
[Parameter(Mandatory=$true)]
$ConfigurationName
)
Write-Output "===== Beginning $($PSCmdlet.MyInvocation.MyCommand) ====="
$ConfigurationName = $ConfigurationName.Trim()
Write-Output "Config Name (trimmed): '$($ConfigurationName)'"
$exe = Join-Path -Path $TargetDir -ChildPath $TargetFileName
$Version = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($exe).FileVersion
Write-Output "Version is $($version)"
# Fix for AppVeyor
if(!([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER))) {
if(!(test-path "Release")) {
New-Item -ItemType Directory -Force -Path "Release" | Out-Null
}
}
# Package debug symbols zip file
if ($ConfigurationName -match "Release") {
Write-Output "Packaging debug symbols"
if ($ConfigurationName -match "Portable") {
$zipFilePrefix = "mRemoteNG-Portable-symbols"
} else {
$zipFilePrefix = "mRemoteNG-symbols"
}
$debugFile = Join-Path -Path $TargetDir -ChildPath "mRemoteNG.pdb"
# AppVeyor build
if(!([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER))) {
$outputZipPath = Join-Path -Path $SolutionDir -ChildPath "Release\$zipFilePrefix-$($version).zip"
7z a $outputZipPath $debugFile
}
# Local build
else {
$outputZipPath = "$($SolutionDir)Release\$zipFilePrefix-$($version).zip"
Compress-Archive $debugFile $outputZipPath -Force
}
Remove-Item $debugFile
}
# Package portable release zip file
if ($ConfigurationName -eq "Release Portable") {
Write-Output "Packaging portable ZIP file"
# AppVeyor build
if(!([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER))) {
$outputZipPath = Join-Path -Path $SolutionDir -ChildPath "Release\mRemoteNG-Portable-$($version).zip"
7z a -bt -bd -bb1 -mx=9 -tzip -y -r $outputZipPath $TargetDir\*
}
# Local build
else {
$outputZipPath="$($SolutionDir)\Release\mRemoteNG-Portable-$($version).zip"
Compress-Archive $Source $outputZipPath -Force
}
}
Write-Output ""

Binary file not shown.

132
mRemoteNG.sln Normal file
View File

@@ -0,0 +1,132 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28803.352
MinimumVisualStudioVersion = 14.0.25420.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mRemoteNG", "mRemoteNG\mRemoteNG.csproj", "{4934A491-40BC-4E5B-9166-EA1169A220F6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mRemoteNGTests", "mRemoteNGTests\mRemoteNGTests.csproj", "{1453B37F-8621-499E-B0B2-6091F76DC0BB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "mRemoteNGInstaller", "mRemoteNGInstaller", "{4FE795BE-646E-4F1B-BAD0-A68EA26394DD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomActions", "mRemoteNGInstaller\CustomActions\CustomActions.csproj", "{5423D985-CB48-4344-B47F-E8C6D60C8B04}"
EndProject
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "Installer", "mRemoteNGInstaller\Installer\Installer.wixproj", "{F0168B9F-6815-40DF-BA53-46CEE7683B68}"
ProjectSection(ProjectDependencies) = postProject
{5423D985-CB48-4344-B47F-E8C6D60C8B04} = {5423D985-CB48-4344-B47F-E8C6D60C8B04}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mRemoteNGSpecs", "mRemoteNGSpecs\mRemoteNGSpecs.csproj", "{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug Portable|Any CPU = Debug Portable|Any CPU
Debug Portable|x86 = Debug Portable|x86
Debug|Any CPU = Debug|Any CPU
Debug|x86 = Debug|x86
Release Installer|Any CPU = Release Installer|Any CPU
Release Installer|x86 = Release Installer|x86
Release Portable|Any CPU = Release Portable|Any CPU
Release Portable|x86 = Release Portable|x86
Release|Any CPU = Release|Any CPU
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug Portable|Any CPU.ActiveCfg = Debug Portable|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug Portable|Any CPU.Build.0 = Debug Portable|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug Portable|x86.ActiveCfg = Debug Portable|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug Portable|x86.Build.0 = Debug Portable|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|Any CPU.ActiveCfg = Debug|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|Any CPU.Build.0 = Debug|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|x86.ActiveCfg = Debug|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|x86.Build.0 = Debug|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer|Any CPU.ActiveCfg = Release|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer|Any CPU.Build.0 = Release|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer|x86.ActiveCfg = Release|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer|x86.Build.0 = Release|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Portable|Any CPU.ActiveCfg = Release Portable|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Portable|Any CPU.Build.0 = Release Portable|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Portable|x86.ActiveCfg = Release Portable|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Portable|x86.Build.0 = Release Portable|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|Any CPU.ActiveCfg = Release|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|Any CPU.Build.0 = Release|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|x86.ActiveCfg = Release|x86
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|x86.Build.0 = Release|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug Portable|Any CPU.ActiveCfg = Debug Portable|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug Portable|Any CPU.Build.0 = Debug Portable|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug Portable|x86.ActiveCfg = Debug Portable|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug Portable|x86.Build.0 = Debug Portable|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug|Any CPU.ActiveCfg = Debug|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug|Any CPU.Build.0 = Debug|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug|x86.ActiveCfg = Debug|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug|x86.Build.0 = Debug|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Installer|Any CPU.ActiveCfg = Release|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Installer|Any CPU.Build.0 = Release|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Installer|x86.ActiveCfg = Release|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Installer|x86.Build.0 = Release|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Portable|Any CPU.ActiveCfg = Release Portable|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Portable|Any CPU.Build.0 = Release Portable|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Portable|x86.ActiveCfg = Release Portable|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Portable|x86.Build.0 = Release Portable|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release|Any CPU.ActiveCfg = Release|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release|Any CPU.Build.0 = Release|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release|x86.ActiveCfg = Release|x86
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release|x86.Build.0 = Release|x86
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug Portable|Any CPU.ActiveCfg = Debug|x86
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug Portable|Any CPU.Build.0 = Debug|x86
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug Portable|x86.ActiveCfg = Debug|x86
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug|Any CPU.ActiveCfg = Debug|x86
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug|x86.ActiveCfg = Debug|x86
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug|x86.Build.0 = Debug|x86
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release Installer|Any CPU.ActiveCfg = Release|x86
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release Installer|Any CPU.Build.0 = Release|x86
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release Installer|x86.ActiveCfg = Release|x86
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release Installer|x86.Build.0 = Release|x86
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release Portable|Any CPU.ActiveCfg = Release|x86
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release Portable|x86.ActiveCfg = Release|x86
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release|Any CPU.ActiveCfg = Release|x86
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release|Any CPU.Build.0 = Release|x86
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release|x86.ActiveCfg = Release|x86
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Debug Portable|Any CPU.ActiveCfg = Debug Portable|x86
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Debug Portable|Any CPU.Build.0 = Debug Portable|x86
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Debug Portable|x86.ActiveCfg = Debug Portable|x86
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Debug|Any CPU.ActiveCfg = Debug|x86
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Debug|x86.ActiveCfg = Debug|x86
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Installer|Any CPU.ActiveCfg = Release|x86
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Installer|Any CPU.Build.0 = Release|x86
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Installer|x86.ActiveCfg = Release|x86
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Installer|x86.Build.0 = Release|x86
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Portable|Any CPU.ActiveCfg = Release Portable|x86
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Portable|x86.ActiveCfg = Release Portable|x86
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release|Any CPU.ActiveCfg = Release|x86
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release|x86.ActiveCfg = Release|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Debug Portable|Any CPU.ActiveCfg = Release|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Debug Portable|Any CPU.Build.0 = Release|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Debug Portable|x86.ActiveCfg = Debug|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Debug Portable|x86.Build.0 = Debug|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Debug|Any CPU.ActiveCfg = Debug|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Debug|x86.ActiveCfg = Debug|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Debug|x86.Build.0 = Debug|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Release Installer|Any CPU.ActiveCfg = Release|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Release Installer|Any CPU.Build.0 = Release|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Release Installer|x86.ActiveCfg = Release|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Release Installer|x86.Build.0 = Release|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Release Portable|Any CPU.ActiveCfg = Release|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Release Portable|Any CPU.Build.0 = Release|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Release Portable|x86.ActiveCfg = Release|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Release Portable|x86.Build.0 = Release|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Release|Any CPU.ActiveCfg = Release|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Release|x86.ActiveCfg = Release|x86
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{5423D985-CB48-4344-B47F-E8C6D60C8B04} = {4FE795BE-646E-4F1B-BAD0-A68EA26394DD}
{F0168B9F-6815-40DF-BA53-46CEE7683B68} = {4FE795BE-646E-4F1B-BAD0-A68EA26394DD}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5D390A0C-2FC4-4908-B86E-7E4DEF5916EC}
EndGlobalSection
EndGlobal

53
mRemoteNG/.editorconfig Normal file
View File

@@ -0,0 +1,53 @@
# top-most EditorConfig file
root = true
[*]
end_of_line = crlf
indent_style = space
[*.xml]
indent_size = 4
[*.cs]
indent_size = 4
trim_trailing_whitespace = true
charset = utf-8-bom
# reStructuredText
[*.rst]
charset = utf-8
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
indent_size = 3
max_line_length = 120
# Organize usings
dotnet_sort_system_directives_first = true
# this. preferences
dotnet_style_qualification_for_field = false:none
dotnet_style_qualification_for_property = false:none
dotnet_style_qualification_for_method = false:none
dotnet_style_qualification_for_event = false:none
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Space preferences
csharp_space_after_cast = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_around_binary_operators = before_and_after
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false

View File

@@ -0,0 +1,110 @@
using System;
using System.Diagnostics;
using System.Windows.Forms;
using Microsoft.Win32;
using mRemoteNG.App.Info;
using mRemoteNG.Messages;
using mRemoteNG.Properties;
using mRemoteNG.UI.Forms;
using mRemoteNG.UI.TaskDialog;
namespace mRemoteNG.App
{
public static class CompatibilityChecker
{
public static void CheckCompatibility(MessageCollector messageCollector)
{
CheckFipsPolicy(messageCollector);
CheckLenovoAutoScrollUtility(messageCollector);
}
private static void CheckFipsPolicy(MessageCollector messageCollector)
{
if (Settings.Default.OverrideFIPSCheck)
{
messageCollector.AddMessage(MessageClass.InformationMsg, "OverrideFIPSCheck is set. Will skip check", true);
return;
}
messageCollector.AddMessage(MessageClass.InformationMsg, "Checking FIPS policy...", true);
messageCollector.AddMessage(MessageClass.InformationMsg, $"FIPS2003: {FipsPolicyEnabledForServer2003()}", true);
messageCollector.AddMessage(MessageClass.InformationMsg, $"FIPS2008+: {FipsPolicyEnabledForServer2008AndNewer()}", true);
if (!FipsPolicyEnabledForServer2003() && !FipsPolicyEnabledForServer2008AndNewer()) return;
var errorText = string.Format(Language.ErrorFipsPolicyIncompatible, GeneralAppInfo.ProductName);
messageCollector.AddMessage(MessageClass.ErrorMsg, errorText, true);
//About to pop up a message, let's not block it...
FrmSplashScreen.getInstance().Close();
var ShouldIStayOrShouldIGo = CTaskDialog.MessageBox(Application.ProductName,
Language.CompatibilityProblemDetected, errorText, "",
"",
Language.CheckboxDoNotShowThisMessageAgain,
ETaskDialogButtons.OkCancel, ESysIcons.Warning,
ESysIcons.Warning);
if (CTaskDialog.VerificationChecked && ShouldIStayOrShouldIGo == DialogResult.OK)
{
messageCollector.AddMessage(MessageClass.ErrorMsg, "User requests that FIPS check be overridden", true);
Settings.Default.OverrideFIPSCheck = true;
Settings.Default.Save();
return;
}
if (ShouldIStayOrShouldIGo == DialogResult.Cancel)
Environment.Exit(1);
}
private static bool FipsPolicyEnabledForServer2003()
{
var regKey = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\Lsa");
if (!(regKey?.GetValue("FIPSAlgorithmPolicy") is int fipsPolicy))
return false;
return fipsPolicy != 0;
}
private static bool FipsPolicyEnabledForServer2008AndNewer()
{
var regKey = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\Lsa\FIPSAlgorithmPolicy");
if (!(regKey?.GetValue("Enabled") is int fipsPolicy))
return false;
return fipsPolicy != 0;
}
private static void CheckLenovoAutoScrollUtility(MessageCollector messageCollector)
{
messageCollector.AddMessage(MessageClass.InformationMsg, "Checking Lenovo AutoScroll Utility...", true);
if (!Settings.Default.CompatibilityWarnLenovoAutoScrollUtility)
return;
var proccesses = new Process[] { };
try
{
proccesses = Process.GetProcessesByName("virtscrl");
}
catch (InvalidOperationException ex)
{
messageCollector.AddExceptionMessage("Error in CheckLenovoAutoScrollUtility", ex);
}
if (proccesses.Length <= 0)
{
messageCollector.AddMessage(MessageClass.InformationMsg, "Lenovo AutoScroll Utility not found", true);
return;
}
messageCollector.AddMessage(MessageClass.WarningMsg, "Lenovo AutoScroll Utility found", true);
CTaskDialog.MessageBox(Application.ProductName, Language.CompatibilityProblemDetected,
string.Format(Language.CompatibilityLenovoAutoScrollUtilityDetected,
Application.ProductName), "",
"", Language.CheckboxDoNotShowThisMessageAgain, ETaskDialogButtons.Ok,
ESysIcons.Warning,
ESysIcons.Warning);
if (CTaskDialog.VerificationChecked)
Settings.Default.CompatibilityWarnLenovoAutoScrollUtility = false;
}
}
}

116
mRemoteNG/App/Export.cs Normal file
View File

@@ -0,0 +1,116 @@
using System;
using System.Linq;
using System.Windows.Forms;
using mRemoteNG.Config.Connections;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Csv;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Xml;
using mRemoteNG.Connection;
using mRemoteNG.Container;
using mRemoteNG.Security;
using mRemoteNG.Security.Factories;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
using mRemoteNG.UI.Forms;
namespace mRemoteNG.App
{
public static class Export
{
public static void ExportToFile(ConnectionInfo selectedNode, IConnectionTreeModel connectionTreeModel)
{
try
{
var saveFilter = new SaveFilter();
using (var exportForm = new FrmExport())
{
if (selectedNode?.GetTreeNodeType() == TreeNodeType.Container)
exportForm.SelectedFolder = selectedNode as ContainerInfo;
else if (selectedNode?.GetTreeNodeType() == TreeNodeType.Connection)
{
if (selectedNode.Parent.GetTreeNodeType() == TreeNodeType.Container)
exportForm.SelectedFolder = selectedNode.Parent;
exportForm.SelectedConnection = selectedNode;
}
if (exportForm.ShowDialog(FrmMain.Default) != DialogResult.OK)
return;
ConnectionInfo exportTarget;
switch (exportForm.Scope)
{
case FrmExport.ExportScope.SelectedFolder:
exportTarget = exportForm.SelectedFolder;
break;
case FrmExport.ExportScope.SelectedConnection:
exportTarget = exportForm.SelectedConnection;
break;
default:
exportTarget = connectionTreeModel.RootNodes.First(node => node is RootNodeInfo);
break;
}
saveFilter.SaveUsername = exportForm.IncludeUsername;
saveFilter.SavePassword = exportForm.IncludePassword;
saveFilter.SaveDomain = exportForm.IncludeDomain;
saveFilter.SaveInheritance = exportForm.IncludeInheritance;
saveFilter.SaveCredentialId = exportForm.IncludeAssignedCredential;
SaveExportFile(exportForm.FileName, exportForm.SaveFormat, saveFilter, exportTarget);
}
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionMessage("App.Export.ExportToFile() failed.", ex);
}
}
private static void SaveExportFile(string fileName,
SaveFormat saveFormat,
SaveFilter saveFilter,
ConnectionInfo exportTarget)
{
try
{
ISerializer<ConnectionInfo, string> serializer;
switch (saveFormat)
{
case SaveFormat.mRXML:
var cryptographyProvider = new CryptoProviderFactoryFromSettings().Build();
var rootNode = exportTarget.GetRootParent() as RootNodeInfo;
var connectionNodeSerializer = new XmlConnectionNodeSerializer27(
cryptographyProvider,
rootNode?.PasswordString
.ConvertToSecureString() ??
new RootNodeInfo(RootNodeType
.Connection)
.PasswordString
.ConvertToSecureString(),
saveFilter);
serializer = new XmlConnectionsSerializer(cryptographyProvider, connectionNodeSerializer);
break;
case SaveFormat.mRCSV:
serializer = new CsvConnectionsSerializerMremotengFormat(saveFilter, Runtime.CredentialService.RepositoryList);
break;
default:
throw new ArgumentOutOfRangeException(nameof(saveFormat), saveFormat, null);
}
var serializedData = serializer.Serialize(exportTarget);
var fileDataProvider = new FileDataProvider(fileName);
fileDataProvider.Save(serializedData);
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionStackTrace($"Export.SaveExportFile(\"{fileName}\") failed.", ex);
}
finally
{
Runtime.ConnectionsService.RemoteConnectionsSyncronizer?.Enable();
}
}
}
}

133
mRemoteNG/App/Import.cs Normal file
View File

@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using mRemoteNG.Config.Import;
using mRemoteNG.Connection;
using mRemoteNG.Connection.Protocol;
using mRemoteNG.Container;
using mRemoteNG.Tools;
namespace mRemoteNG.App
{
public static class Import
{
public static void ImportFromFile(ContainerInfo importDestinationContainer)
{
try
{
using (var openFileDialog = new OpenFileDialog())
{
openFileDialog.CheckFileExists = true;
openFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
openFileDialog.Multiselect = true;
var fileTypes = new List<string>();
fileTypes.AddRange(new[] {Language.FilterAllImportable, "*.xml;*.rdp;*.rdg;*.dat;*.csv"});
fileTypes.AddRange(new[] {Language.FiltermRemoteXML, "*.xml"});
fileTypes.AddRange(new[] {Language.FiltermRemoteCSV, "*.csv"});
fileTypes.AddRange(new[] {Language.FilterRDP, "*.rdp"});
fileTypes.AddRange(new[] {Language.FilterRdgFiles, "*.rdg"});
fileTypes.AddRange(new[] {Language.FilterPuttyConnectionManager, "*.dat"});
fileTypes.AddRange(new[] {Language.FilterAll, "*.*"});
openFileDialog.Filter = string.Join("|", fileTypes.ToArray());
if (openFileDialog.ShowDialog() != DialogResult.OK)
return;
HeadlessFileImport(
openFileDialog.FileNames,
importDestinationContainer,
Runtime.ConnectionsService,
fileName => MessageBox.Show(string.Format(Language.ImportFileFailedContent, fileName), Language.AskUpdatesMainInstruction,
MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1));
}
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionMessage("Unable to import file.", ex);
}
}
public static void HeadlessFileImport(
IEnumerable<string> filePaths,
ContainerInfo importDestinationContainer,
ConnectionsService connectionsService,
Action<string> exceptionAction = null)
{
using (connectionsService.BatchedSavingContext())
{
foreach (var fileName in filePaths)
{
try
{
var importer = BuildConnectionImporterFromFileExtension(fileName);
importer.Import(fileName, importDestinationContainer);
}
catch (Exception ex)
{
exceptionAction?.Invoke(fileName);
Runtime.MessageCollector.AddExceptionMessage($"Error occurred while importing file '{fileName}'.", ex);
}
}
}
}
public static void ImportFromActiveDirectory(string ldapPath,
ContainerInfo importDestinationContainer,
bool importSubOu)
{
try
{
using (Runtime.ConnectionsService.BatchedSavingContext())
{
ActiveDirectoryImporter.Import(ldapPath, importDestinationContainer, importSubOu);
}
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionMessage("App.Import.ImportFromActiveDirectory() failed.", ex);
}
}
public static void ImportFromPortScan(IEnumerable<ScanHost> hosts,
ProtocolType protocol,
ContainerInfo importDestinationContainer)
{
try
{
using (Runtime.ConnectionsService.BatchedSavingContext())
{
var importer = new PortScanImporter(protocol);
importer.Import(hosts, importDestinationContainer);
}
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionMessage("App.Import.ImportFromPortScan() failed.", ex);
}
}
private static IConnectionImporter<string> BuildConnectionImporterFromFileExtension(string fileName)
{
// TODO: Use the file contents to determine the file type instead of trusting the extension
var extension = Path.GetExtension(fileName) ?? "";
switch (extension.ToLowerInvariant())
{
case ".xml":
return new MRemoteNGXmlImporter();
case ".csv":
return new MRemoteNGCsvImporter();
case ".rdp":
return new RemoteDesktopConnectionImporter();
case ".rdg":
return new RemoteDesktopConnectionManagerImporter();
case ".dat":
return new PuttyConnectionManagerImporter();
default:
throw new FileFormatException("Unrecognized file format.");
}
}
}
}

View File

@@ -0,0 +1,10 @@
namespace mRemoteNG.App.Info
{
public static class ConnectionsFileInfo
{
public static readonly string DefaultConnectionsPath = SettingsFileInfo.SettingsPath;
public static readonly string DefaultConnectionsFile = "confCons.xml";
public static readonly string DefaultConnectionsFileNew = "confConsNew.xml";
public static readonly double ConnectionFileVersion = 2.8;
}
}

View File

@@ -0,0 +1,10 @@
namespace mRemoteNG.App.Info
{
public class CredentialsFileInfo
{
public static readonly string CredentialsPath = SettingsFileInfo.SettingsPath;
public static readonly string CredentialsFile = "confCreds.xml";
public static readonly string CredentialsFileNew = "confCredsNew.xml";
public static readonly double CredentialsFileVersion = 1.0;
}
}

View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
using static System.Environment;
namespace mRemoteNG.App.Info
{
public static class GeneralAppInfo
{
public const string UrlHome = "https://www.mremoteng.org";
public const string UrlDonate = "https://mremoteng.org/contribute";
public const string UrlForum = "https://www.reddit.com/r/mRemoteNG";
public const string UrlBugs = "https://bugs.mremoteng.org";
public const string UrlDocumentation = "https://mremoteng.readthedocs.io/en/latest/";
public static string ApplicationVersion = Application.ProductVersion;
public static readonly string ProductName = Application.ProductName;
public static readonly string Copyright =
((AssemblyCopyrightAttribute)Attribute.GetCustomAttribute(Assembly.GetExecutingAssembly(),
typeof(AssemblyCopyrightAttribute), false))
.Copyright;
public static readonly string HomePath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
//public static string ReportingFilePath = "";
public static readonly string PuttyPath = HomePath + "\\PuTTYNG.exe";
public static string UserAgent
{
get
{
var details = new List<string>
{
"compatible",
OSVersion.Platform == PlatformID.Win32NT
? $"Windows NT {OSVersion.Version.Major}.{OSVersion.Version.Minor}"
: OSVersion.VersionString
};
if (Is64BitProcess)
{
details.Add("WOW64");
}
details.Add(Thread.CurrentThread.CurrentUICulture.Name);
details.Add($".NET CLR {Environment.Version}");
var detailsString = string.Join("; ", details.ToArray());
return $"Mozilla/5.0 ({detailsString}) {ProductName}/{ApplicationVersion}";
}
}
public static Version GetApplicationVersion()
{
System.Version.TryParse(ApplicationVersion, out var v);
return v;
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using mRemoteNG.Connection;
namespace mRemoteNG.App.Info
{
public static class SettingsFileInfo
{
private static readonly string ExePath =
Path.GetDirectoryName(Assembly.GetAssembly(typeof(ConnectionInfo))?.Location);
public static string SettingsPath =>
Runtime.IsPortableEdition
? ExePath
: Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\" + Application.ProductName;
public static string LayoutFileName { get; } = "pnlLayout.xml";
public static string ExtAppsFilesName { get; } = "extApps.xml";
public static string ThemesFileName { get; } = "Themes.xml";
public static string ThemeFolder { get; } =
SettingsPath != null ? Path.Combine(SettingsPath, "Themes") : String.Empty;
public static string InstalledThemeFolder { get; } =
ExePath != null ? Path.Combine(ExePath, "Themes") : String.Empty;
}
}

View File

@@ -0,0 +1,77 @@
using System;
using mRemoteNG.Properties;
// ReSharper disable InconsistentNaming
namespace mRemoteNG.App.Info
{
public static class UpdateChannelInfo
{
public const string STABLE = "Stable";
public const string PREVIEW = "Preview";
public const string NIGHTLY = "Nightly";
public const string STABLE_PORTABLE = "update-portable.txt";
public const string PREVIEW_PORTABLE = "preview-update-portable.txt";
public const string NIGHTLY_PORTABLE = "nightly-update-portable.txt";
public const string STABLE_MSI = "update.txt";
public const string PREVIEW_MSI = "preview-update.txt";
public const string NIGHTLY_MSI = "nightly-update.txt";
public static Uri GetUpdateChannelInfo()
{
var channel = IsValidChannel(Settings.Default.UpdateChannel) ? Settings.Default.UpdateChannel : STABLE;
return GetUpdateTxtUri(channel);
}
private static string GetChannelFileName(string channel)
{
return Runtime.IsPortableEdition
? GetChannelFileNamePortableEdition(channel)
: GetChannelFileNameNormalEdition(channel);
}
private static string GetChannelFileNameNormalEdition(string channel)
{
switch (channel)
{
case STABLE:
return STABLE_MSI;
case PREVIEW:
return PREVIEW_MSI;
case NIGHTLY:
return NIGHTLY_MSI;
default:
return STABLE_MSI;
}
}
private static string GetChannelFileNamePortableEdition(string channel)
{
switch (channel)
{
case STABLE:
return STABLE_PORTABLE;
case PREVIEW:
return PREVIEW_PORTABLE;
case NIGHTLY:
return NIGHTLY_PORTABLE;
default:
return STABLE_PORTABLE;
}
}
private static Uri GetUpdateTxtUri(string channel)
{
return new Uri(new Uri(Settings.Default.UpdateAddress),
new Uri(GetChannelFileName(channel), UriKind.Relative));
}
private static bool IsValidChannel(string s)
{
return s.Equals(STABLE) || s.Equals(PREVIEW) || s.Equals(NIGHTLY);
}
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.IO;
using mRemoteNG.Connection;
namespace mRemoteNG.App.Initialization
{
public class ConnectionIconLoader
{
private readonly string _path;
public ConnectionIconLoader(string folderPath)
{
if (string.IsNullOrEmpty(folderPath))
throw new ArgumentException($"{nameof(folderPath)} must be a valid folder path.");
_path = folderPath;
}
public void GetConnectionIcons()
{
if (Directory.Exists(_path) == false)
return;
foreach (var f in Directory.GetFiles(_path, "*.ico", SearchOption.AllDirectories))
{
var fInfo = new FileInfo(f);
Array.Resize(ref ConnectionIcon.Icons, ConnectionIcon.Icons.Length + 1);
ConnectionIcon.Icons.SetValue(fInfo.Name.Replace(".ico", ""), ConnectionIcon.Icons.Length - 1);
}
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.IO;
using System.Linq;
using mRemoteNG.Config.Connections;
using mRemoteNG.Connection;
using mRemoteNG.Credential;
using mRemoteNG.Tools;
using mRemoteNG.Properties;
namespace mRemoteNG.App.Initialization
{
public class CredsAndConsSetup
{
public void LoadCredsAndCons(ConnectionsService connectionsService, CredentialService credentialService, SaveConnectionsOnEdit connectionsOnEdit)
{
connectionsOnEdit.Subscribe(connectionsService);
if (Settings.Default.FirstStart && !Settings.Default.LoadConsFromCustomLocation &&
!File.Exists(connectionsService.GetStartupConnectionFileName()))
connectionsService.NewConnectionsFile(connectionsService.GetStartupConnectionFileName());
credentialService.LoadRepositoryList();
LoadDefaultConnectionCredentials(credentialService);
Runtime.LoadConnections();
}
private void LoadDefaultConnectionCredentials(CredentialService credentialService)
{
var defaultCredId = Settings.Default.ConDefaultCredentialRecord;
var matchedCredentials = credentialService
.GetCredentialRecords()
.Where(record => record.Id.Equals(defaultCredId))
.ToArray();
DefaultConnectionInfo.Instance.CredentialRecordId = Optional<Guid>.FromNullable(matchedCredentials.FirstOrDefault()?.Id);
}
}
}

View File

@@ -0,0 +1,75 @@
using System.Collections.Generic;
using System.Linq;
using mRemoteNG.Messages;
using mRemoteNG.Messages.MessageFilteringOptions;
using mRemoteNG.Messages.MessageWriters;
using mRemoteNG.Messages.WriterDecorators;
namespace mRemoteNG.App.Initialization
{
public class MessageCollectorSetup
{
public static void SetupMessageCollector(MessageCollector messageCollector,
IList<IMessageWriter> messageWriterList)
{
messageCollector.CollectionChanged += (o, args) =>
{
var messages = args.NewItems.Cast<IMessage>().ToArray();
foreach (var printer in messageWriterList)
foreach (var message in messages)
printer.Write(message);
};
}
public static void BuildMessageWritersFromSettings(IList<IMessageWriter> messageWriterList)
{
#if DEBUG
messageWriterList.Add(BuildDebugConsoleWriter());
#endif
messageWriterList.Add(BuildTextLogMessageWriter());
messageWriterList.Add(BuildNotificationPanelMessageWriter());
messageWriterList.Add(BuildPopupMessageWriter());
}
private static IMessageWriter BuildDebugConsoleWriter()
{
return new DebugConsoleMessageWriter();
}
private static IMessageWriter BuildTextLogMessageWriter()
{
return new MessageTypeFilterDecorator(
new LogMessageTypeFilteringOptions(),
new TextLogMessageWriter(Logger.Instance)
);
}
private static IMessageWriter BuildNotificationPanelMessageWriter()
{
return new OnlyLogMessageFilter(
new MessageTypeFilterDecorator(
new
NotificationPanelMessageFilteringOptions(),
new MessageFocusDecorator(
Windows.ErrorsForm,
new
NotificationPanelSwitchOnMessageFilteringOptions(),
new
NotificationPanelMessageWriter(Windows
.ErrorsForm)
)
)
);
}
private static IMessageWriter BuildPopupMessageWriter()
{
return new OnlyLogMessageFilter(
new MessageTypeFilterDecorator(
new PopupMessageFilteringOptions(),
new PopupMessageWriter()
)
);
}
}
}

View File

@@ -0,0 +1,123 @@
using System;
using System.Management;
using System.Threading;
using System.Windows.Forms;
using mRemoteNG.Messages;
namespace mRemoteNG.App.Initialization
{
public class StartupDataLogger
{
private readonly MessageCollector _messageCollector;
public StartupDataLogger(MessageCollector messageCollector)
{
if (messageCollector == null)
throw new ArgumentNullException(nameof(messageCollector));
_messageCollector = messageCollector;
}
public void LogStartupData()
{
LogApplicationData();
LogCmdLineArgs();
LogSystemData();
LogClrData();
LogCultureData();
}
private void LogSystemData()
{
var osData = GetOperatingSystemData();
var architecture = GetArchitectureData();
var nonEmptyData = Array.FindAll(new[] {osData, architecture}, s => !string.IsNullOrEmpty(s));
var data = string.Join(" ", nonEmptyData);
_messageCollector.AddMessage(MessageClass.InformationMsg, data, true);
}
private string GetOperatingSystemData()
{
var osVersion = string.Empty;
var servicePack = string.Empty;
try
{
foreach (var o in new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem WHERE Primary=True")
.Get())
{
var managementObject = (ManagementObject)o;
osVersion = Convert.ToString(managementObject.GetPropertyValue("Caption")).Trim();
servicePack = GetOSServicePack(servicePack, managementObject);
}
}
catch (Exception ex)
{
_messageCollector.AddExceptionMessage("Error retrieving operating system information from WMI.", ex);
}
var osData = string.Join(" ", osVersion, servicePack);
return osData;
}
private string GetOSServicePack(string servicePack, ManagementObject managementObject)
{
var servicePackNumber = Convert.ToInt32(managementObject.GetPropertyValue("ServicePackMajorVersion"));
if (servicePackNumber != 0)
{
servicePack = $"Service Pack {servicePackNumber}";
}
return servicePack;
}
private string GetArchitectureData()
{
var architecture = string.Empty;
try
{
foreach (var o in new ManagementObjectSearcher("SELECT AddressWidth FROM Win32_Processor WHERE DeviceID=\'CPU0\'")
.Get())
{
var managementObject = (ManagementObject)o;
var addressWidth = Convert.ToInt32(managementObject.GetPropertyValue("AddressWidth"));
architecture = $"{addressWidth}-bit";
}
}
catch (Exception ex)
{
_messageCollector.AddExceptionMessage("Error retrieving operating system address width from WMI.", ex);
}
return architecture;
}
private void LogApplicationData()
{
var data = $"{Application.ProductName} {Application.ProductVersion}";
if (Runtime.IsPortableEdition)
data += $" {Language.PortableEdition}";
data += " starting.";
_messageCollector.AddMessage(MessageClass.InformationMsg, data, true);
}
private void LogCmdLineArgs()
{
var data = $"Command Line: {string.Join(" ", Environment.GetCommandLineArgs())}";
_messageCollector.AddMessage(MessageClass.InformationMsg, data, true);
}
private void LogClrData()
{
var data = $"Microsoft .NET CLR {Environment.Version}";
_messageCollector.AddMessage(MessageClass.InformationMsg, data, true);
}
private void LogCultureData()
{
var data =
$"System Culture: {Thread.CurrentThread.CurrentUICulture.Name}/{Thread.CurrentThread.CurrentUICulture.NativeName}";
_messageCollector.AddMessage(MessageClass.InformationMsg, data, true);
}
}
}

71
mRemoteNG/App/Logger.cs Normal file
View File

@@ -0,0 +1,71 @@
using System;
using System.IO;
using System.Windows.Forms;
using log4net;
using log4net.Appender;
using log4net.Config;
using mRemoteNG.Properties;
// ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.App
{
public class Logger
{
public static readonly Logger Instance = new Logger();
public ILog Log { get; private set; }
public static string DefaultLogPath => BuildLogFilePath();
private Logger()
{
Initialize();
}
private void Initialize()
{
XmlConfigurator.Configure(LogManager.CreateRepository("mRemoteNG"));
if (string.IsNullOrEmpty(Settings.Default.LogFilePath))
Settings.Default.LogFilePath = BuildLogFilePath();
SetLogPath(Settings.Default.LogToApplicationDirectory ? DefaultLogPath : Settings.Default.LogFilePath);
}
public void SetLogPath(string path)
{
var repository = LogManager.GetRepository("mRemoteNG");
var appenders = repository.GetAppenders();
foreach (var appender in appenders)
{
var fileAppender = (RollingFileAppender)appender;
if (fileAppender == null || fileAppender.Name != "LogFileAppender") continue;
fileAppender.File = path;
fileAppender.ActivateOptions();
}
Log = LogManager.GetLogger("mRemoteNG", "Logger");
}
private static string BuildLogFilePath()
{
var logFilePath = Runtime.IsPortableEdition ? GetLogPathPortableEdition() : GetLogPathNormalEdition();
var logFileName = Path.ChangeExtension(Application.ProductName, ".log");
if (logFileName == null) return "mRemoteNG.log";
var logFile = Path.Combine(logFilePath, logFileName);
return logFile;
}
private static string GetLogPathNormalEdition()
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
Application.ProductName);
}
private static string GetLogPathPortableEdition()
{
return Application.StartupPath;
}
}
}

View File

@@ -0,0 +1,547 @@
using System;
using System.Drawing;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
#pragma warning disable 649
#pragma warning disable 169
namespace mRemoteNG.App
{
public static class NativeMethods
{
#region Functions
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool AppendMenu(IntPtr hMenu, int uFlags, IntPtr uIDNewItem, string lpNewItem);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr CreatePopupMenu();
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr FindWindowEx(IntPtr parentHandle,
IntPtr childAfter,
string lclassName,
string windowTitle);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool InsertMenu(IntPtr hMenu,
int uPosition,
int uFlags,
IntPtr uIDNewItem,
string lpNewItem);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int IsIconic(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool MoveWindow(IntPtr hWnd, int x, int y, int cx, int cy, bool repaint);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, StringBuilder lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, string lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern IntPtr SendMessage([In] IntPtr hWnd,
[In] uint msg,
[Out] StringBuilder wParam,
[In] IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool SetMenuItemBitmaps(IntPtr hMenu,
int uPosition,
int uFlags,
IntPtr hBitmapUnchecked,
IntPtr hBitmapChecked);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern long SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern IntPtr WindowFromPoint(Point point);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int GetDlgCtrlID(IntPtr hwndCtl);
[DllImport("user32", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]
internal static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
[DllImport("user32", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]
internal static extern bool SetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
[DllImport("kernel32", SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
internal static extern bool CloseHandle(IntPtr handle);
#endregion
#region Structures
[StructLayout(LayoutKind.Sequential)]
internal struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
}
internal struct WINDOWPLACEMENT
{
public uint length;
public uint flags;
public uint showCmd;
public POINT ptMinPosition;
public POINT ptMaxPosition;
public RECT rcNormalPosition;
}
internal struct POINT
{
public long x;
public long y;
}
internal struct RECT
{
public long left;
public long top;
public long right;
public long bottom;
}
#endregion
#region Helpers
public static int MAKELONG(int wLow, int wHigh)
{
return wLow | wHigh << 16;
}
public static int MAKELPARAM(ref int wLow, ref int wHigh)
{
return MAKELONG(wLow, wHigh);
}
public static int LOWORD(int value)
{
return value & 0xFFFF;
}
public static int LOWORD(IntPtr value)
{
return LOWORD(value.ToInt32());
}
public static int HIWORD(int value)
{
return value >> 16;
}
public static int HIWORD(IntPtr value)
{
return HIWORD(value.ToInt32());
}
#endregion
#region Constants
public const int TRUE = 1;
#region GetWindowLong
public const int GWL_STYLE = (-16);
#endregion
#region AppendMenu / ModifyMenu / DeleteMenu / RemoveMenu
public const int MF_BYCOMMAND = 0x0;
public const int MF_BYPOSITION = 0x400;
public const int MF_STRING = 0x0;
public const int MF_POPUP = 0x10;
public const int MF_SEPARATOR = 0x800;
#endregion
#region WM_LBUTTONDOWN / WM_LBUTTONUP
public const int MK_LBUTTON = 0x1;
#endregion
#region ShowWindow
public const uint SW_HIDE = 0;
public const uint SW_SHOWNORMAL = 1;
public const uint SW_SHOWMINIMIZED = 2;
public const uint SW_SHOWMAXIMIZED = 3;
public const uint SW_MAXIMIZE = 3;
public const uint SW_SHOWNOACTIVATE = 4;
public const uint SW_SHOW = 5;
public const uint SW_MINIMIZE = 6;
public const uint SW_SHOWMINNOACTIVE = 7;
public const uint SW_SHOWNA = 8;
public const uint SW_RESTORE = 9;
#endregion
#region SetWindowPos / WM_WINDOWPOSCHANGING / WM_WINDOWPOSCHANGED
/// <summary>
/// Retains the current size (ignores the cx and cy parameters).
/// </summary>
public const int SWP_NOSIZE = 0x1;
/// <summary>
/// Retains the current position (ignores the x and y parameters).
/// </summary>
public const int SWP_NOMOVE = 0x2;
/// <summary>
/// Retains the current Z order (ignores the hWndInsertAfter parameter).
/// </summary>
public const int SWP_NOZORDER = 0x4;
/// <summary>
/// Does not redraw changes. If this flag is set, no repainting of any kind occurs. This applies to the client area, the nonclient area (including the title bar and scroll bars), and any part of the parent window uncovered as a result of the window being moved. When this flag is set, the application must explicitly invalidate or redraw any parts of the window and parent window that need redrawing.
/// </summary>
public const int SWP_NOREDRAW = 0x8;
/// <summary>
/// Does not activate the window. If this flag is not set, the window is activated and moved to the top of either the topmost or non-topmost group (depending on the setting of the hWndInsertAfter parameter).
/// </summary>
public const int SWP_NOACTIVATE = 0x10;
/// <summary>
/// Draws a frame (defined in the window's class description) around the window.
/// </summary>
public const int SWP_DRAWFRAME = 0x20;
/// <summary>
/// Sends a WM_NCCALCSIZE message to the window, even if the window's size is not being changed. If this flag is not specified, WM_NCCALCSIZE is sent only when the window's size is being changed.
/// </summary>
public const int SWP_FRAMECHANGED = 0x20;
/// <summary>
/// Displays the window.
/// </summary>
public const int SWP_SHOWWINDOW = 0x40;
/// <summary>
/// Hides the window.
/// </summary>
public const int SWP_HIDEWINDOW = 0x80;
/// <summary>
/// Discards the entire contents of the client area. If this flag is not specified, the valid contents of the client area are saved and copied back into the client area after the window is sized or repositioned.
/// </summary>
public const int SWP_NOCOPYBITS = 0x100;
/// <summary>
/// Does not change the owner window's position in the Z order.
/// </summary>
public const int SWP_NOOWNERZORDER = 0x200;
/// <summary>
/// Prevents the window from receiving the WM_WINDOWPOSCHANGING message.
/// </summary>
public const int SWP_NOSENDCHANGING = 0x400;
/// <summary>
///
/// </summary>
public const int SWP_NOCLIENTSIZE = 0x800;
/// <summary>
///
/// </summary>
public const int SWP_NOCLIENTMOVE = 0x1000;
/// <summary>
/// Prevents generation of the WM_SYNCPAINT message.
/// </summary>
public const int SWP_DEFERERASE = 0x2000;
/// <summary>
/// If the calling thread and the thread that owns the window are attached to different input queues, the system posts the request to the thread that owns the window. This prevents the calling thread from blocking its execution while other threads process the request.
/// </summary>
public const int SWP_ASYNCWINDOWPOS = 0x4000;
/// <summary>
///
/// </summary>
public const int SWP_STATECHANGED = 0x8000;
#endregion
#region Window Placement Flags (WPF)
public const uint WPF_SETMINPOSITION = 0x1;
public const uint WPF_RESTORETOMAXIMIZED = 0x2;
public const uint WPF_ASYNCWINDOWPLACEMENT = 0x4;
#endregion
#region WM_ACTIVATE
/// <summary>
///
/// </summary>
public const int WA_INACTIVE = 0x0;
/// <summary>
///
/// </summary>
public const int WA_ACTIVE = 0x1;
/// <summary>
/// Sent to both the window being activated and the window being deactivated.
/// If the windows use the same input queue, the message is sent synchronously, first to the window procedure of the
/// top-level window being deactivated, then to the window procedure of the top-level window being activated. If the
/// windows use different input queues, the message is sent asynchronously, so the window is activated immediately.
/// </summary>
public const int WA_CLICKACTIVE = 0x2;
#endregion
#region Window Messages
/// <summary>
/// Sent when an application requests that a window be created by calling the CreateWindowEx or CreateWindow function. (The message is sent before the function returns.) The window procedure of the new window receives this message after the window is created, but before the window becomes visible.
/// </summary>
public const int WM_CREATE = 0x1;
/// <summary>
/// Sent when a window is being destroyed. It is sent to the window procedure of the window being destroyed after the window is removed from the screen. This message is sent first to the window being destroyed and then to the child windows(if any) as they are destroyed.During the processing of the message, it can be assumed that all child windows still exist.
/// </summary>
public const int WM_DESTROY = 0x2;
/// <summary>
/// Sent to both the window being activated and the window being deactivated. If the windows use the same input queue, the message is sent synchronously, first to the window procedure of the top-level window being deactivated, then to the window procedure of the top-level window being activated. If the windows use different input queues, the message is sent asynchronously, so the window is activated immediately.
/// </summary>
public const int WM_ACTIVATE = 0x6;
/// <summary>
///
/// </summary>
public const int WM_SETTEXT = 0xC;
/// <summary>
/// Copies the text that corresponds to a window into a buffer provided by the caller.
/// </summary>
public const int WM_GETTEXT = 0xD;
/// <summary>
/// Sent as a signal that a window or an application should terminate.
/// </summary>
public const int WM_CLOSE = 0x10;
/// <summary>
/// Sent when a window belonging to a different application than the active window is about to be activated. The message is sent to the application whose window is being activated and to the application whose window is being deactivated.
/// </summary>
public const int WM_ACTIVATEAPP = 0x1C;
/// <summary>
/// Sent to a window if the mouse causes the cursor to move within a window and mouse input is not captured.
/// </summary>
public const int WM_SETCURSOR = 0x20;
/// <summary>
/// Sent when the cursor is in an inactive window and the user presses a mouse button. The parent window receives this message only if the child window passes it to the DefWindowProc function.
/// </summary>
public const int WM_MOUSEACTIVATE = 0x21;
/// <summary>
/// Sent to a window when the size or position of the window is about to change. An application can use this message to override the window's default maximized size and position, or its default minimum or maximum tracking size.
/// </summary>
public const int WM_GETMINMAXINFO = 0x24;
/// <summary>
/// Sent to a window whose size, position, or place in the Z order is about to change as a result of a call to the SetWindowPos function or another window-management function.
/// </summary>
public const int WM_WINDOWPOSCHANGING = 0x46;
/// <summary>
/// Sent to a window whose size, position, or place in the Z order has changed as a result of a call to the SetWindowPos function or another window-management function.
/// </summary>
public const int WM_WINDOWPOSCHANGED = 0x47;
/// <summary>
/// Posted to the window with the keyboard focus when a nonsystem key is pressed. A nonsystem key is a key that is pressed when the ALT key is not pressed.
/// </summary>
public const int WM_KEYDOWN = 0x100;
/// <summary>
/// Posted to the window with the keyboard focus when a nonsystem key is released. A nonsystem key is a key that is pressed when the ALT key is not pressed, or a keyboard key that is pressed when a window has the keyboard focus.
/// </summary>
public const int WM_KEYUP = 0x101;
/// <summary>
/// Posted to the window with the keyboard focus when a WM_KEYDOWN message is translated by the TranslateMessage function. The WM_CHAR message contains the character code of the key that was pressed.
/// </summary>
public const int WM_CHAR = 0x102;
/// <summary>
/// Sent when the user selects a command item from a menu, when a control sends a notification message to its parent window, or when an accelerator keystroke is translated.
/// </summary>
public const int WM_COMMAND = 0x111;
/// <summary>
/// A window receives this message when the user chooses a command from the Window menu (formerly known as the system or control menu) or when the user chooses the maximize button, minimize button, restore button, or close button.
/// </summary>
public const int WM_SYSCOMMAND = 0x112;
/// <summary>
/// Posted to a window when the cursor moves. If the mouse is not captured, the message is posted to the window that contains the cursor. Otherwise, the message is posted to the window that has captured the mouse.
/// </summary>
public const int WM_MOUSEMOVE = 0x200;
/// <summary>
/// Posted when the user presses the left mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse.
/// </summary>
public const int WM_LBUTTONDOWN = 0x201;
/// <summary>
/// Posted when the user releases the left mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse.
/// </summary>
public const int WM_LBUTTONUP = 0x202;
/// <summary>
/// Posted when the user presses the right mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse.
/// </summary>
public const int WM_RBUTTONDOWN = 0x204;
/// <summary>
/// Posted when the user releases the right mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse.
/// </summary>
public const int WM_RBUTTONUP = 0x205;
/// <summary>
/// Posted when the user presses the middle mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse.
/// </summary>
public const int WM_MBUTTONDOWN = 0x207;
/// <summary>
/// Posted when the user releases the middle mouse button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse.
/// </summary>
public const int WM_MBUTTONUP = 0x208;
/// <summary>
/// Posted when the user presses the first or second X button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse.
/// </summary>
public const int WM_XBUTTONDOWN = 0x20B;
/// <summary>
/// Posted when the user releases the first or second X button while the cursor is in the client area of a window. If the mouse is not captured, the message is posted to the window beneath the cursor. Otherwise, the message is posted to the window that has captured the mouse.
/// </summary>
public const int WM_XBUTTONUP = 0x20C;
/// <summary>
/// Sent to a window when a significant action occurs on a descendant window. This message is now extended to include the WM_POINTERDOWN event. When the child window is being created, the system sends WM_PARENTNOTIFY just before the CreateWindow or CreateWindowEx function that creates the window returns. When the child window is being destroyed, the system sends the message before any processing to destroy the window takes place.
/// </summary>
public const int WM_PARENTNOTIFY = 0x210;
/// <summary>
/// Sent one time to a window after it enters the moving or sizing modal loop. The window enters the moving or sizing modal loop when the user clicks the window's title bar or sizing border, or when the window passes the WM_SYSCOMMAND message to the DefWindowProc function and the wParam parameter of the message specifies the SC_MOVE or SC_SIZE value. The operation is complete when DefWindowProc returns.
/// </summary>
public const int WM_ENTERSIZEMOVE = 0x231;
/// <summary>
/// Sent one time to a window, after it has exited the moving or sizing modal loop. The window enters the moving or sizing modal loop when the user clicks the window's title bar or sizing border, or when the window passes the WM_SYSCOMMAND message to the DefWindowProc function and the wParam parameter of the message specifies the SC_MOVE or SC_SIZE value. The operation is complete when DefWindowProc returns.
/// </summary>
public const int WM_EXITSIZEMOVE = 0x232;
/// <summary>
/// Sent to the first window in the clipboard viewer chain when the content of the clipboard changes. This enables a clipboard viewer window to display the new content of the clipboard.
/// </summary>
public const int WM_DRAWCLIPBOARD = 0x308;
/// <summary>
/// Sent to the first window in the clipboard viewer chain when a window is being removed from the chain.
/// </summary>
public const int WM_CHANGECBCHAIN = 0x30D;
#endregion
#region Window Styles
public const int WS_MAXIMIZE = 0x1000000;
public const int WS_VISIBLE = 0x10000000;
public const int WS_CHILD = 0x40000000;
public const int WS_EX_MDICHILD = 0x40;
#endregion
#region Virtual Key Codes
public const int VK_CONTROL = 0x11;
public const int VK_C = 0x67;
#endregion
#region EM
public const uint ECM_FIRST = 0x1500;
public const uint EM_SETCUEBANNER = ECM_FIRST + 1;
public const uint EM_GETCUEBANNER = ECM_FIRST + 2;
#endregion
#region LB
public const int LB_ERR = -1;
public const int LB_SELECTSTRING = 0x18C;
#endregion
#region TCM
internal const int TCM_ADJUSTRECT = 0x1328;
#endregion
#endregion
}
}

View File

@@ -0,0 +1,107 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
using mRemoteNG.Properties;
using mRemoteNG.UI.Forms;
namespace mRemoteNG.App
{
public static class ProgramRoot
{
private static Mutex _mutex;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static void Main(string[] args)
{
if (Settings.Default.SingleInstance)
StartApplicationAsSingleInstance();
else
StartApplication();
}
private static void StartApplication()
{
CatchAllUnhandledExceptions();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var frmSplashScreen = FrmSplashScreen.getInstance();
frmSplashScreen.Show();
Application.Run(FrmMain.Default);
}
public static void CloseSingletonInstanceMutex()
{
_mutex?.Close();
}
private static void StartApplicationAsSingleInstance()
{
const string mutexID = "mRemoteNG_SingleInstanceMutex";
_mutex = new Mutex(false, mutexID, out var newInstanceCreated);
if (!newInstanceCreated)
{
SwitchToCurrentInstance();
return;
}
StartApplication();
GC.KeepAlive(_mutex);
}
private static void SwitchToCurrentInstance()
{
var singletonInstanceWindowHandle = GetRunningSingletonInstanceWindowHandle();
if (singletonInstanceWindowHandle == IntPtr.Zero) return;
if (NativeMethods.IsIconic(singletonInstanceWindowHandle) != 0)
NativeMethods.ShowWindow(singletonInstanceWindowHandle, (int)NativeMethods.SW_RESTORE);
NativeMethods.SetForegroundWindow(singletonInstanceWindowHandle);
}
private static IntPtr GetRunningSingletonInstanceWindowHandle()
{
var windowHandle = IntPtr.Zero;
var currentProcess = Process.GetCurrentProcess();
foreach (var enumeratedProcess in Process.GetProcessesByName(currentProcess.ProcessName))
{
if (enumeratedProcess.Id != currentProcess.Id &&
enumeratedProcess.MainModule.FileName == currentProcess.MainModule.FileName &&
enumeratedProcess.MainWindowHandle != IntPtr.Zero)
windowHandle = enumeratedProcess.MainWindowHandle;
}
return windowHandle;
}
private static void CatchAllUnhandledExceptions()
{
Application.ThreadException += ApplicationOnThreadException;
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
}
private static void ApplicationOnThreadException(object sender, ThreadExceptionEventArgs e)
{
if (!FrmSplashScreen.getInstance().IsDisposed)
FrmSplashScreen.getInstance().Close();
if (FrmMain.Default.IsDisposed) return;
var window = new FrmUnhandledException(e.Exception, false);
window.ShowDialog(FrmMain.Default);
}
private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
if (!FrmSplashScreen.getInstance().IsDisposed)
FrmSplashScreen.getInstance().Close();
var window = new FrmUnhandledException(e.ExceptionObject as Exception, e.IsTerminating);
window.ShowDialog(FrmMain.Default);
}
}
}

222
mRemoteNG/App/Runtime.cs Normal file
View File

@@ -0,0 +1,222 @@
using mRemoteNG.App.Info;
using mRemoteNG.Config.Putty;
using mRemoteNG.Connection;
using mRemoteNG.Credential;
using mRemoteNG.Messages;
using mRemoteNG.Security;
using mRemoteNG.Tools;
using mRemoteNG.Tree.Root;
using mRemoteNG.UI;
using mRemoteNG.UI.Forms;
using mRemoteNG.UI.TaskDialog;
using System;
using System.IO;
using System.Security;
using System.Threading;
using System.Windows.Forms;
using mRemoteNG.Properties;
namespace mRemoteNG.App
{
public static class Runtime
{
public static bool IsPortableEdition
{
get
{
#if PORTABLE
return true;
#else
return false;
#endif
}
}
/// <summary>
/// Feature flag to enable the credential manager feature
/// </summary>
public static bool UseCredentialManager => true;
public static WindowList WindowList { get; set; }
public static MessageCollector MessageCollector { get; } = new MessageCollector();
public static NotificationAreaIcon NotificationAreaIcon { get; set; }
public static ExternalToolsService ExternalToolsService { get; } = new ExternalToolsService();
public static SecureString EncryptionKey { get; set; } =
new RootNodeInfo(RootNodeType.Connection).PasswordString.ConvertToSecureString();
public static CredentialService CredentialService { get; } = new CredentialServiceFactory().Build();
public static ConnectionsService ConnectionsService { get; } = new ConnectionsService(PuttySessionsManager.Instance, CredentialService);
#region Connections Loading/Saving
public static void LoadConnectionsAsync()
{
var t = new Thread(LoadConnectionsBGd);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
private static void LoadConnectionsBGd()
{
LoadConnections();
}
/// <summary>
///
/// </summary>
/// <param name="withDialog">
/// Should we show the file selection dialog to allow the user to select
/// a connection file
/// </param>
public static void LoadConnections(bool withDialog = false)
{
var connectionFileName = "";
try
{
// disable sql update checking while we are loading updates
ConnectionsService.RemoteConnectionsSyncronizer?.Disable();
if (withDialog)
{
var loadDialog = DialogFactory.BuildLoadConnectionsDialog();
if (loadDialog.ShowDialog() != DialogResult.OK)
return;
connectionFileName = loadDialog.FileName;
Settings.Default.UseSQLServer = false;
Settings.Default.Save();
}
else if (!Settings.Default.UseSQLServer)
{
connectionFileName = ConnectionsService.GetStartupConnectionFileName();
}
ConnectionsService.LoadConnections(Settings.Default.UseSQLServer, connectionFileName);
if (Settings.Default.UseSQLServer)
{
ConnectionsService.LastSqlUpdate = DateTime.Now;
}
// re-enable sql update checking after updates are loaded
ConnectionsService.RemoteConnectionsSyncronizer?.Enable();
}
catch (Exception ex)
{
FrmSplashScreen.getInstance().Close();
if (Settings.Default.UseSQLServer)
{
MessageCollector.AddExceptionMessage(Language.LoadFromSqlFailed, ex);
var commandButtons = string.Join("|", Language._TryAgain,
Language.CommandOpenConnectionFile,
string.Format(Language.CommandExitProgram,
Application.ProductName));
CTaskDialog.ShowCommandBox(Application.ProductName, Language.LoadFromSqlFailed,
Language.LoadFromSqlFailedContent,
MiscTools.GetExceptionMessageRecursive(ex), "", "",
commandButtons, false, ESysIcons.Error, ESysIcons.Error);
switch (CTaskDialog.CommandButtonResult)
{
case 0:
LoadConnections(withDialog);
return;
case 1:
Settings.Default.UseSQLServer = false;
LoadConnections(true);
return;
default:
Application.Exit();
return;
}
}
if (ex is FileNotFoundException && !withDialog)
{
MessageCollector.AddExceptionMessage(
string.Format(Language.ConnectionsFileCouldNotBeLoadedNew,
connectionFileName), ex,
MessageClass.InformationMsg);
string[] commandButtons =
{
Language.ConfigurationCreateNew,
Language.ConfigurationCustomPath,
Language.ConfigurationImportFile,
Language.Exit
};
var answered = false;
while (!answered)
{
try
{
CTaskDialog.ShowTaskDialogBox(
GeneralAppInfo.ProductName,
Language.ConnectionFileNotFound,
"", "", "", "", "",
string.Join(" | ", commandButtons),
ETaskDialogButtons.None,
ESysIcons.Question,
ESysIcons.Question);
switch (CTaskDialog.CommandButtonResult)
{
case 0:
ConnectionsService.NewConnectionsFile(connectionFileName);
answered = true;
break;
case 1:
LoadConnections(true);
answered = true;
break;
case 2:
ConnectionsService.NewConnectionsFile(connectionFileName);
Import.ImportFromFile(ConnectionsService.ConnectionTreeModel.RootNodes[0]);
answered = true;
break;
case 3:
Application.Exit();
answered = true;
break;
}
}
catch (Exception exc)
{
MessageCollector.AddExceptionMessage(
string
.Format(Language.ConnectionsFileCouldNotBeLoadedNew,
connectionFileName), exc,
MessageClass.InformationMsg);
}
}
return;
}
MessageCollector.AddExceptionStackTrace(
string.Format(Language.ConnectionsFileCouldNotBeLoaded,
connectionFileName), ex);
if (connectionFileName != ConnectionsService.GetStartupConnectionFileName())
{
LoadConnections(withDialog);
}
else
{
MessageBox.Show(FrmMain.Default,
string.Format(Language.ErrorStartupConnectionFileLoad, Environment.NewLine,
Application.ProductName,
ConnectionsService.GetStartupConnectionFileName(),
MiscTools.GetExceptionMessageRecursive(ex)),
@"Could not load startup file.", MessageBoxButtons.OK, MessageBoxIcon.Error);
Application.Exit();
}
}
}
#endregion
}
}

36
mRemoteNG/App/Screens.cs Normal file
View File

@@ -0,0 +1,36 @@
using System.Windows.Forms;
using mRemoteNG.UI.Forms;
using WeifenLuo.WinFormsUI.Docking;
namespace mRemoteNG.App
{
public static class Screens
{
public static void SendFormToScreen(Screen screen)
{
var frmMain = FrmMain.Default;
var wasMax = false;
if (frmMain.WindowState == FormWindowState.Maximized)
{
wasMax = true;
frmMain.WindowState = FormWindowState.Normal;
}
frmMain.Location = screen.Bounds.Location;
if (wasMax)
{
frmMain.WindowState = FormWindowState.Maximized;
}
}
public static void SendPanelToScreen(DockContent panel, Screen screen)
{
panel.DockState = DockState.Float;
if (panel.ParentForm == null) return;
panel.ParentForm.Left = screen.Bounds.Location.X;
panel.ParentForm.Top = screen.Bounds.Location.Y;
}
}
}

98
mRemoteNG/App/Shutdown.cs Normal file
View File

@@ -0,0 +1,98 @@
using mRemoteNG.Tools;
using System;
using System.Diagnostics;
using System.Windows.Forms;
using mRemoteNG.Config.Putty;
using mRemoteNG.Properties;
using mRemoteNG.UI.Controls;
using mRemoteNG.UI.Forms;
// ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.App
{
public static class Shutdown
{
private static string _updateFilePath;
private static bool UpdatePending
{
get { return !string.IsNullOrEmpty(_updateFilePath); }
}
public static void Quit(string updateFilePath = null)
{
_updateFilePath = updateFilePath;
FrmMain.Default.Close();
ProgramRoot.CloseSingletonInstanceMutex();
}
public static void Cleanup(Control quickConnectToolStrip,
ExternalToolsToolStrip externalToolsToolStrip,
MultiSshToolStrip multiSshToolStrip,
FrmMain frmMain)
{
try
{
StopPuttySessionWatcher();
DisposeNotificationAreaIcon();
SaveConnections();
SaveSettings(quickConnectToolStrip, externalToolsToolStrip, multiSshToolStrip, frmMain);
UnregisterBrowsers();
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionStackTrace(Language.SettingsCouldNotBeSavedOrTrayDispose, ex);
}
}
private static void StopPuttySessionWatcher()
{
PuttySessionsManager.Instance.StopWatcher();
}
private static void DisposeNotificationAreaIcon()
{
if (Runtime.NotificationAreaIcon != null && Runtime.NotificationAreaIcon.Disposed == false)
Runtime.NotificationAreaIcon.Dispose();
}
private static void SaveConnections()
{
if (Settings.Default.SaveConsOnExit)
Runtime.ConnectionsService.SaveConnections();
}
private static void SaveSettings(Control quickConnectToolStrip,
ExternalToolsToolStrip externalToolsToolStrip,
MultiSshToolStrip multiSshToolStrip,
FrmMain frmMain)
{
Config.Settings.SettingsSaver.SaveSettings(quickConnectToolStrip, externalToolsToolStrip, multiSshToolStrip,
frmMain);
}
private static void UnregisterBrowsers()
{
IeBrowserEmulation.Unregister();
}
public static void StartUpdate()
{
try
{
RunUpdateFile();
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionStackTrace("The update could not be started.", ex);
}
}
private static void RunUpdateFile()
{
if (UpdatePending)
Process.Start(_updateFilePath);
}
}
}

127
mRemoteNG/App/Startup.cs Normal file
View File

@@ -0,0 +1,127 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using mRemoteNG.App.Info;
using mRemoteNG.App.Initialization;
using mRemoteNG.App.Update;
using mRemoteNG.Config.Connections.Multiuser;
using mRemoteNG.Connection;
using mRemoteNG.Messages;
using mRemoteNG.Properties;
using mRemoteNG.Tools;
using mRemoteNG.Tools.Cmdline;
using mRemoteNG.UI;
using mRemoteNG.UI.Forms;
namespace mRemoteNG.App
{
public class Startup
{
private AppUpdater _appUpdate;
private readonly ConnectionIconLoader _connectionIconLoader;
private readonly FrmMain _frmMain = FrmMain.Default;
public static Startup Instance { get; } = new Startup();
private Startup()
{
_appUpdate = new AppUpdater();
_connectionIconLoader = new ConnectionIconLoader(GeneralAppInfo.HomePath + "\\Icons\\");
}
static Startup()
{
}
public void InitializeProgram(MessageCollector messageCollector)
{
Debug.Print("---------------------------" + Environment.NewLine + "[START] - " +
Convert.ToString(DateTime.Now, CultureInfo.InvariantCulture));
var startupLogger = new StartupDataLogger(messageCollector);
startupLogger.LogStartupData();
CompatibilityChecker.CheckCompatibility(messageCollector);
ParseCommandLineArgs(messageCollector);
IeBrowserEmulation.Register();
_connectionIconLoader.GetConnectionIcons();
DefaultConnectionInfo.Instance.LoadFrom(Settings.Default, a => "ConDefault" + a);
DefaultConnectionInheritance.Instance.LoadFrom(Settings.Default, a => "InhDefault" + a);
}
private static void ParseCommandLineArgs(MessageCollector messageCollector)
{
var interpreter = new StartupArgumentsInterpreter(messageCollector);
interpreter.ParseArguments(Environment.GetCommandLineArgs());
}
public void CreateConnectionsProvider(MessageCollector messageCollector)
{
messageCollector.AddMessage(MessageClass.DebugMsg, "Determining if we need a database syncronizer");
if (!Settings.Default.UseSQLServer) return;
messageCollector.AddMessage(MessageClass.DebugMsg, "Creating database syncronizer");
Runtime.ConnectionsService.RemoteConnectionsSyncronizer =
new RemoteConnectionsSyncronizer(new SqlConnectionsUpdateChecker());
Runtime.ConnectionsService.RemoteConnectionsSyncronizer.Enable();
}
public void CheckForUpdate()
{
if (_appUpdate == null)
{
_appUpdate = new AppUpdater();
}
else if (_appUpdate.IsGetUpdateInfoRunning)
{
return;
}
var nextUpdateCheck =
Convert.ToDateTime(Settings.Default.CheckForUpdatesLastCheck.Add(
TimeSpan
.FromDays(Convert.ToDouble(Settings
.Default
.CheckForUpdatesFrequencyDays))));
if (!Settings.Default.UpdatePending && DateTime.UtcNow < nextUpdateCheck)
{
return;
}
_appUpdate.GetUpdateInfoCompletedEvent += GetUpdateInfoCompleted;
_appUpdate.GetUpdateInfoAsync();
}
private void GetUpdateInfoCompleted(object sender, AsyncCompletedEventArgs e)
{
if (_frmMain.InvokeRequired)
{
_frmMain.Invoke(new AsyncCompletedEventHandler(GetUpdateInfoCompleted), sender, e);
return;
}
try
{
_appUpdate.GetUpdateInfoCompletedEvent -= GetUpdateInfoCompleted;
if (e.Cancelled)
{
return;
}
if (e.Error != null)
{
throw e.Error;
}
if (_appUpdate.IsUpdateAvailable())
{
Windows.Show(WindowType.Update);
}
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionMessage("GetUpdateInfoCompleted() failed.", ex);
}
}
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.Serialization;
using mRemoteNG.Properties;
// ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.App
{
[Serializable]
public sealed class SupportedCultures : Dictionary<string, string>
{
private static SupportedCultures _Instance;
private static SupportedCultures SingletonInstance
{
get { return _Instance ?? (_Instance = new SupportedCultures()); }
}
private SupportedCultures()
{
foreach (var CultureName in Settings.Default.SupportedUICultures.Split(','))
{
try
{
var CultureInfo = new CultureInfo(CultureName.Trim());
Add(CultureInfo.Name, CultureInfo.TextInfo.ToTitleCase(CultureInfo.NativeName));
}
catch (Exception ex)
{
Debug.Print(
$"An exception occurred while adding the culture {CultureName} to the list of supported cultures. {ex.StackTrace}");
}
}
}
// fix CA2229 - https://docs.microsoft.com/en-us/visualstudio/code-quality/ca2229-implement-serialization-constructors?view=vs-2017
private SupportedCultures(SerializationInfo info, StreamingContext context)
{
throw new NotImplementedException();
}
public static bool IsNameSupported(string CultureName)
{
return SingletonInstance.ContainsKey(CultureName);
}
public static bool IsNativeNameSupported(string CultureNativeName)
{
return SingletonInstance.ContainsValue(CultureNativeName);
}
public static string get_CultureName(string CultureNativeName)
{
var Names = new string[SingletonInstance.Count + 1];
var NativeNames = new string[SingletonInstance.Count + 1];
SingletonInstance.Keys.CopyTo(Names, 0);
SingletonInstance.Values.CopyTo(NativeNames, 0);
for (var Index = 0; Index <= SingletonInstance.Count; Index++)
{
if (NativeNames[Index] == CultureNativeName)
{
return Names[Index];
}
}
throw (new KeyNotFoundException());
}
public static string get_CultureNativeName(string CultureName)
{
return SingletonInstance[CultureName];
}
public static List<string> CultureNativeNames
{
get
{
var ValueList = new List<string>();
foreach (var Value in SingletonInstance.Values)
{
ValueList.Add(Value);
}
return ValueList;
}
}
}
}

View File

@@ -0,0 +1,410 @@
using System;
using System.IO;
using System.Net;
using System.ComponentModel;
using System.Threading;
using System.Reflection;
using mRemoteNG.App.Info;
using mRemoteNG.Security.SymmetricEncryption;
using System.Security.Cryptography;
using mRemoteNG.Properties;
#if !PORTABLE
using mRemoteNG.Tools;
#else
using System.Windows.Forms;
#endif
// ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.App.Update
{
public class AppUpdater
{
private WebProxy _webProxy;
private Thread _getUpdateInfoThread;
private Thread _getChangeLogThread;
#region Public Properties
public UpdateInfo CurrentUpdateInfo { get; private set; }
public string ChangeLog { get; private set; }
public bool IsGetUpdateInfoRunning
{
get { return _getUpdateInfoThread != null && _getUpdateInfoThread.IsAlive; }
}
private bool IsGetChangeLogRunning
{
get { return _getChangeLogThread != null && _getChangeLogThread.IsAlive; }
}
public bool IsDownloadUpdateRunning
{
get { return _downloadUpdateWebClient != null; }
}
#endregion
#region Public Methods
public AppUpdater()
{
SetProxySettings();
}
private void SetProxySettings()
{
var shouldWeUseProxy = Settings.Default.UpdateUseProxy;
var proxyAddress = Settings.Default.UpdateProxyAddress;
var port = Settings.Default.UpdateProxyPort;
var useAuthentication = Settings.Default.UpdateProxyUseAuthentication;
var username = Settings.Default.UpdateProxyAuthUser;
var cryptographyProvider = new LegacyRijndaelCryptographyProvider();
var password = cryptographyProvider.Decrypt(Settings.Default.UpdateProxyAuthPass, Runtime.EncryptionKey);
SetProxySettings(shouldWeUseProxy, proxyAddress, port, useAuthentication, username, password);
}
public void SetProxySettings(bool useProxy,
string address,
int port,
bool useAuthentication,
string username,
string password)
{
if (useProxy && !string.IsNullOrEmpty(address))
{
_webProxy = port != 0 ? new WebProxy(address, port) : new WebProxy(address);
_webProxy.Credentials = useAuthentication ? new NetworkCredential(username, password) : null;
}
else
{
_webProxy = null;
}
}
public bool IsUpdateAvailable()
{
if (CurrentUpdateInfo == null || !CurrentUpdateInfo.IsValid)
{
return false;
}
return CurrentUpdateInfo.Version > GeneralAppInfo.GetApplicationVersion();
}
public void GetUpdateInfoAsync()
{
if (IsGetUpdateInfoRunning)
{
_getUpdateInfoThread.Abort();
}
_getUpdateInfoThread = new Thread(GetUpdateInfo);
_getUpdateInfoThread.SetApartmentState(ApartmentState.STA);
_getUpdateInfoThread.IsBackground = true;
_getUpdateInfoThread.Start();
}
public void GetChangeLogAsync()
{
if (CurrentUpdateInfo == null || !CurrentUpdateInfo.IsValid)
{
throw new InvalidOperationException(
"CurrentUpdateInfo is not valid. GetUpdateInfoAsync() must be called before calling GetChangeLogAsync().");
}
if (IsGetChangeLogRunning)
{
_getChangeLogThread.Abort();
}
_getChangeLogThread = new Thread(GetChangeLog);
_getChangeLogThread.SetApartmentState(ApartmentState.STA);
_getChangeLogThread.IsBackground = true;
_getChangeLogThread.Start();
}
public void DownloadUpdateAsync()
{
if (_downloadUpdateWebClient != null)
{
throw new InvalidOperationException("A previous call to DownloadUpdateAsync() is still in progress.");
}
if (CurrentUpdateInfo == null || !CurrentUpdateInfo.IsValid)
{
throw new InvalidOperationException(
"CurrentUpdateInfo is not valid. GetUpdateInfoAsync() must be called before calling DownloadUpdateAsync().");
}
#if !PORTABLE
CurrentUpdateInfo.UpdateFilePath =
Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetRandomFileName(), "msi"));
#else
var sfd = new SaveFileDialog
{
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
FileName = CurrentUpdateInfo.FileName,
RestoreDirectory = true
};
if (sfd.ShowDialog() == DialogResult.OK)
{
CurrentUpdateInfo.UpdateFilePath = sfd.FileName;
}
else
{
return;
}
#endif
DownloadUpdateWebClient.DownloadFileAsync(CurrentUpdateInfo.DownloadAddress,
CurrentUpdateInfo.UpdateFilePath);
}
#endregion
#region Private Properties
private WebClient _downloadUpdateWebClient;
private WebClient DownloadUpdateWebClient
{
get
{
if (_downloadUpdateWebClient != null)
{
return _downloadUpdateWebClient;
}
_downloadUpdateWebClient = CreateWebClient();
_downloadUpdateWebClient.DownloadProgressChanged += DownloadUpdateProgressChanged;
_downloadUpdateWebClient.DownloadFileCompleted += DownloadUpdateCompleted;
return _downloadUpdateWebClient;
}
}
#endregion
#region Private Methods
private WebClient CreateWebClient()
{
var webClient = new WebClient();
webClient.Headers.Add("user-agent", GeneralAppInfo.UserAgent);
webClient.Proxy = _webProxy;
return webClient;
}
private static DownloadStringCompletedEventArgs NewDownloadStringCompletedEventArgs(string result,
Exception exception,
bool cancelled,
object userToken)
{
var type = typeof(DownloadStringCompletedEventArgs);
const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
Type[] argumentTypes = {typeof(string), typeof(Exception), typeof(bool), typeof(object)};
var constructor = type.GetConstructor(bindingFlags, null, argumentTypes, null);
object[] arguments = {result, exception, cancelled, userToken};
if (constructor == null)
return null;
return (DownloadStringCompletedEventArgs)constructor.Invoke(arguments);
}
private DownloadStringCompletedEventArgs DownloadString(Uri address)
{
var webClient = CreateWebClient();
var result = string.Empty;
Exception exception = null;
var cancelled = false;
try
{
result = webClient.DownloadString(address);
}
catch (ThreadAbortException)
{
cancelled = true;
}
catch (Exception ex)
{
exception = ex;
}
return NewDownloadStringCompletedEventArgs(result, exception, cancelled, null);
}
private void GetUpdateInfo()
{
var e = DownloadString(UpdateChannelInfo.GetUpdateChannelInfo());
if (!e.Cancelled && e.Error == null)
{
try
{
CurrentUpdateInfo = UpdateInfo.FromString(e.Result);
Settings.Default.CheckForUpdatesLastCheck = DateTime.UtcNow;
if (!Settings.Default.UpdatePending)
{
Settings.Default.UpdatePending = IsUpdateAvailable();
}
}
catch (Exception ex)
{
e = NewDownloadStringCompletedEventArgs(e.Result, ex, e.Cancelled, null);
}
}
GetUpdateInfoCompletedEventEvent?.Invoke(this, e);
}
private void GetChangeLog()
{
var e = DownloadString(CurrentUpdateInfo.ChangeLogAddress);
if (!e.Cancelled && e.Error == null)
{
ChangeLog = e.Result;
}
GetChangeLogCompletedEventEvent?.Invoke(this, e);
}
private void DownloadUpdateProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
DownloadUpdateProgressChangedEventEvent?.Invoke(sender, e);
}
private void DownloadUpdateCompleted(object sender, AsyncCompletedEventArgs e)
{
var raiseEventArgs = e;
if (!e.Cancelled && e.Error == null)
{
try
{
#if !PORTABLE
var updateAuthenticode = new Authenticode(CurrentUpdateInfo.UpdateFilePath)
{
RequireThumbprintMatch = true,
ThumbprintToMatch = CurrentUpdateInfo.CertificateThumbprint
};
if (updateAuthenticode.Verify() != Authenticode.StatusValue.Verified)
{
if (updateAuthenticode.Status == Authenticode.StatusValue.UnhandledException)
{
throw updateAuthenticode.Exception;
}
throw new Exception(updateAuthenticode.GetStatusMessage());
}
#endif
using (var cksum = SHA512.Create())
{
using (var stream = File.OpenRead(CurrentUpdateInfo.UpdateFilePath))
{
var hash = cksum.ComputeHash(stream);
var hashString = BitConverter.ToString(hash).Replace("-", "").ToUpperInvariant();
if (!hashString.Equals(CurrentUpdateInfo.Checksum))
throw new Exception("SHA512 Hashes didn't match!");
}
}
}
catch (Exception ex)
{
raiseEventArgs = new AsyncCompletedEventArgs(ex, false, null);
}
}
if (raiseEventArgs.Cancelled || raiseEventArgs.Error != null)
{
File.Delete(CurrentUpdateInfo.UpdateFilePath);
}
DownloadUpdateCompletedEventEvent?.Invoke(this, raiseEventArgs);
_downloadUpdateWebClient.Dispose();
_downloadUpdateWebClient = null;
}
#endregion
#region Events
private AsyncCompletedEventHandler GetUpdateInfoCompletedEventEvent;
public event AsyncCompletedEventHandler GetUpdateInfoCompletedEvent
{
add
{
GetUpdateInfoCompletedEventEvent =
(AsyncCompletedEventHandler)Delegate.Combine(GetUpdateInfoCompletedEventEvent, value);
}
remove
{
GetUpdateInfoCompletedEventEvent =
(AsyncCompletedEventHandler)Delegate.Remove(GetUpdateInfoCompletedEventEvent, value);
}
}
private AsyncCompletedEventHandler GetChangeLogCompletedEventEvent;
public event AsyncCompletedEventHandler GetChangeLogCompletedEvent
{
add
{
GetChangeLogCompletedEventEvent =
(AsyncCompletedEventHandler)Delegate.Combine(GetChangeLogCompletedEventEvent, value);
}
remove
{
GetChangeLogCompletedEventEvent =
(AsyncCompletedEventHandler)Delegate.Remove(GetChangeLogCompletedEventEvent, value);
}
}
private DownloadProgressChangedEventHandler DownloadUpdateProgressChangedEventEvent;
public event DownloadProgressChangedEventHandler DownloadUpdateProgressChangedEvent
{
add
{
DownloadUpdateProgressChangedEventEvent =
(DownloadProgressChangedEventHandler)Delegate.Combine(DownloadUpdateProgressChangedEventEvent,
value);
}
remove
{
DownloadUpdateProgressChangedEventEvent =
(DownloadProgressChangedEventHandler)Delegate.Remove(DownloadUpdateProgressChangedEventEvent,
value);
}
}
private AsyncCompletedEventHandler DownloadUpdateCompletedEventEvent;
public event AsyncCompletedEventHandler DownloadUpdateCompletedEvent
{
add
{
DownloadUpdateCompletedEventEvent =
(AsyncCompletedEventHandler)Delegate.Combine(DownloadUpdateCompletedEventEvent, value);
}
remove
{
DownloadUpdateCompletedEventEvent =
(AsyncCompletedEventHandler)Delegate.Remove(DownloadUpdateCompletedEventEvent, value);
}
}
#endregion
}
}

View File

@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace mRemoteNG.App.Update
{
public class UpdateFile
{
#region Public Properties
// ReSharper disable MemberCanBePrivate.Local
// ReSharper disable once MemberCanBePrivate.Global
public Dictionary<string, string> Items { get; } =
new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
#endregion
#region Public Methods
public UpdateFile(string content)
{
FromString(content);
}
// ReSharper disable MemberCanBePrivate.Local
// ReSharper disable once MemberCanBePrivate.Global
public void FromString(string content)
{
if (string.IsNullOrEmpty(content)) return;
char[] keyValueSeparators = {':', '='};
char[] commentCharacters = {'#', ';', '\''};
// no separators means no valid update data...
if (content.Trim().IndexOfAny(keyValueSeparators) == -1) return;
using (var sr = new StringReader(content))
{
string line;
while ((line = sr.ReadLine()) != null)
{
var trimmedLine = line.Trim();
if (trimmedLine.Length == 0)
continue;
if (trimmedLine.Substring(0, 1).IndexOfAny(commentCharacters) != -1)
continue;
var parts = trimmedLine.Split(keyValueSeparators, 2);
if (parts.Length != 2)
continue;
// make sure we have valid data in both parts before adding to the collection. If either part is empty, then it's not valid data.
if (string.IsNullOrEmpty(parts[0].Trim()) || string.IsNullOrEmpty(parts[1].Trim()))
continue;
Items.Add(parts[0].Trim(), parts[1].Trim());
}
}
}
// ReSharper disable MemberCanBePrivate.Local
private string GetString(string key)
{
// ReSharper restore MemberCanBePrivate.Local
return !Items.ContainsKey(key) ? string.Empty : Items[key];
}
public Version GetVersion(string key = "Version")
{
var value = GetString(key);
return string.IsNullOrEmpty(value) ? null : new Version(value);
}
public Uri GetUri(string key)
{
var value = GetString(key);
return string.IsNullOrEmpty(value) ? null : new Uri(value);
}
public string GetThumbprint(string key = "CertificateThumbprint")
{
return GetString(key).Replace(" ", "").ToUpperInvariant();
}
public string GetFileName()
{
var value = GetString("dURL");
var sv = value.Split('/');
return sv[sv.Length - 1];
}
public string GetChecksum(string key = "Checksum")
{
return GetString(key).Replace(" ", "").ToUpperInvariant();
}
#endregion
}
}

View File

@@ -0,0 +1,78 @@
using System;
// ReSharper disable UnusedAutoPropertyAccessor.Local
namespace mRemoteNG.App.Update
{
public class UpdateInfo
{
public bool IsValid { get; private set; }
public Version Version { get; private set; }
public Uri DownloadAddress { get; private set; }
public string UpdateFilePath { get; set; }
public Uri ChangeLogAddress { get; private set; }
public Uri ImageAddress { get; private set; }
public Uri ImageLinkAddress { get; private set; }
#if !PORTABLE
public string CertificateThumbprint { get; private set; }
#endif
// ReSharper disable once MemberCanBePrivate.Global
public string FileName { get; set; }
public string Checksum { get; private set; }
public static UpdateInfo FromString(string input)
{
var newInfo = new UpdateInfo();
if (string.IsNullOrEmpty(input))
{
newInfo.IsValid = false;
}
else
{
var updateFile = new UpdateFile(input);
newInfo.Version = updateFile.GetVersion();
newInfo.DownloadAddress = updateFile.GetUri("dURL");
newInfo.ChangeLogAddress = updateFile.GetUri("clURL");
#if false
newInfo.ImageAddress = updateFile.GetUri("imgURL");
newInfo.ImageLinkAddress = updateFile.GetUri("imgURLLink");
#endif
#if !PORTABLE
newInfo.CertificateThumbprint = updateFile.GetThumbprint();
#endif
newInfo.FileName = updateFile.GetFileName();
newInfo.Checksum = updateFile.GetChecksum();
newInfo.IsValid = newInfo.CheckIfValid();
}
return newInfo;
}
public bool CheckIfValid()
{
if (string.IsNullOrEmpty(Version.ToString()))
return false;
if (string.IsNullOrEmpty(DownloadAddress.AbsoluteUri))
return false;
if (string.IsNullOrEmpty(ChangeLogAddress.AbsoluteUri))
return false;
#if false
if (string.IsNullOrEmpty(ImageAddress.AbsoluteUri))
return false;
if (string.IsNullOrEmpty(ImageLinkAddress.AbsoluteUri))
return false;
#endif
#if !PORTABLE
if (string.IsNullOrEmpty(CertificateThumbprint))
return false;
#endif
if (string.IsNullOrEmpty(FileName))
return false;
// ReSharper disable once ConvertIfStatementToReturnStatement
if (string.IsNullOrEmpty(Checksum))
return false;
return true;
}
}
}

80
mRemoteNG/App/Windows.cs Normal file
View File

@@ -0,0 +1,80 @@
using System;
using mRemoteNG.UI;
using mRemoteNG.UI.Forms;
using mRemoteNG.UI.Window;
namespace mRemoteNG.App
{
public static class Windows
{
private static ActiveDirectoryImportWindow _adimportForm;
private static ExternalToolsWindow _externalappsForm;
private static PortScanWindow _portscanForm;
private static UltraVNCWindow _ultravncscForm;
private static ConnectionTreeWindow _treeForm;
internal static ConnectionTreeWindow TreeForm
{
get => _treeForm ?? (_treeForm = new ConnectionTreeWindow());
set => _treeForm = value;
}
internal static ConfigWindow ConfigForm { get; set; } = new ConfigWindow();
internal static ErrorAndInfoWindow ErrorsForm { get; set; } = new ErrorAndInfoWindow();
private static UpdateWindow UpdateForm { get; set; } = new UpdateWindow();
internal static SSHTransferWindow SshtransferForm { get; private set; } = new SSHTransferWindow();
public static void Show(WindowType windowType)
{
try
{
var dockPanel = FrmMain.Default.pnlDock;
// ReSharper disable once SwitchStatementMissingSomeCases
switch (windowType)
{
case WindowType.ActiveDirectoryImport:
if (_adimportForm == null || _adimportForm.IsDisposed)
_adimportForm = new ActiveDirectoryImportWindow();
_adimportForm.Show(dockPanel);
break;
case WindowType.Options:
using (var optionsForm = new FrmOptions())
{
optionsForm.ShowDialog(dockPanel);
}
break;
case WindowType.SSHTransfer:
if (SshtransferForm == null || SshtransferForm.IsDisposed)
SshtransferForm = new SSHTransferWindow();
SshtransferForm.Show(dockPanel);
break;
case WindowType.Update:
if (UpdateForm == null || UpdateForm.IsDisposed)
UpdateForm = new UpdateWindow();
UpdateForm.Show(dockPanel);
break;
case WindowType.ExternalApps:
if (_externalappsForm == null || _externalappsForm.IsDisposed)
_externalappsForm = new ExternalToolsWindow();
_externalappsForm.Show(dockPanel);
break;
case WindowType.PortScan:
_portscanForm = new PortScanWindow();
_portscanForm.Show(dockPanel);
break;
case WindowType.UltraVNCSC:
if (_ultravncscForm == null || _ultravncscForm.IsDisposed)
_ultravncscForm = new UltraVNCWindow();
_ultravncscForm.Show(dockPanel);
break;
}
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionStackTrace("App.Runtime.Windows.Show() failed.", ex);
}
}
}
}

View File

@@ -0,0 +1,11 @@
namespace mRemoteNG.Config
{
public enum ConfirmCloseEnum
{
Unspecified = 0,
Never = 1,
Exit = 2,
Multiple = 3,
All = 4
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using mRemoteNG.Credential;
namespace mRemoteNG.Config
{
public class ConnectionToCredentialMap : Dictionary<Guid, ICredentialRecord>
{
private readonly IEqualityComparer<ICredentialRecord> _credentialComparer = new CredentialDomainUserPasswordComparer();
public IEnumerable<ICredentialRecord> DistinctCredentialRecords => Values.Distinct(_credentialComparer);
}
}

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using mRemoteNG.Connection;
using mRemoteNG.Tools;
using mRemoteNG.Tree;
namespace mRemoteNG.Config.Connections
{
public class ConnectionsLoadedEventArgs : EventArgs
{
/// <summary>
/// The previous <see cref="ConnectionTreeModel"/> that is being
/// unloaded.
/// </summary>
public List<ConnectionInfo> RemovedConnections { get; }
/// <summary>
/// True if the previous <see cref="ConnectionTreeModel"/> was loaded from
/// a database.
/// </summary>
public bool PreviousSourceWasDatabase { get; }
/// <summary>
/// The new <see cref="ConnectionTreeModel"/> that is being loaded.
/// </summary>
public List<ConnectionInfo> AddedConnections { get; }
/// <summary>
/// True if the new <see cref="ConnectionTreeModel"/> was loaded from
/// a database.
/// </summary>
public bool NewSourceIsDatabase { get; }
/// <summary>
/// The path to the new connections source.
/// If <see cref="NewSourceIsDatabase"/> is True, this will be the server and database name.
/// If False, it will be a file path to the connection file.
/// </summary>
public string NewSourcePath { get; }
public IConnectionTreeModel NewConnectionTreeModel { get; set; } = new ConnectionTreeModel();
public ConnectionsLoadedEventArgs(
List<ConnectionInfo> removedConnections, List<ConnectionInfo> addedConnections,
bool previousSourceWasDatabase, bool newSourceIsDatabase,
string newSourcePath)
{
RemovedConnections = removedConnections.ThrowIfNull(nameof(removedConnections));
PreviousSourceWasDatabase = previousSourceWasDatabase;
AddedConnections = addedConnections.ThrowIfNull(nameof(addedConnections));
NewSourceIsDatabase = newSourceIsDatabase;
NewSourcePath = newSourcePath.ThrowIfNull(nameof(newSourcePath));
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using mRemoteNG.Tree;
namespace mRemoteNG.Config.Connections
{
public class ConnectionsSavedEventArgs
{
public IConnectionTreeModel ModelThatWasSaved { get; }
public bool PreviouslyUsingDatabase { get; }
public bool UsingDatabase { get; }
public string ConnectionFileName { get; }
public ConnectionsSavedEventArgs(IConnectionTreeModel modelThatWasSaved,
bool previouslyUsingDatabase,
bool usingDatabase,
string connectionFileName)
{
if (modelThatWasSaved == null)
throw new ArgumentNullException(nameof(modelThatWasSaved));
ModelThatWasSaved = modelThatWasSaved;
PreviouslyUsingDatabase = previouslyUsingDatabase;
UsingDatabase = usingDatabase;
ConnectionFileName = connectionFileName;
}
}
}

View File

@@ -0,0 +1,34 @@
using System;
using mRemoteNG.App;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Csv;
using mRemoteNG.Security;
using mRemoteNG.Tree;
namespace mRemoteNG.Config.Connections
{
public class CsvConnectionsSaver : ISaver<ConnectionTreeModel>
{
private readonly string _connectionFileName;
private readonly SaveFilter _saveFilter;
public CsvConnectionsSaver(string connectionFileName, SaveFilter saveFilter)
{
if (string.IsNullOrEmpty(connectionFileName))
throw new ArgumentException($"Argument '{nameof(connectionFileName)}' cannot be null or empty");
if (saveFilter == null)
throw new ArgumentNullException(nameof(saveFilter));
_connectionFileName = connectionFileName;
_saveFilter = saveFilter;
}
public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
{
var csvConnectionsSerializer = new CsvConnectionsSerializerMremotengFormat(_saveFilter, Runtime.CredentialService.RepositoryList);
var dataProvider = new FileDataProvider(_connectionFileName);
var csvContent = csvConnectionsSerializer.Serialize(connectionTreeModel);
dataProvider.Save(csvContent);
}
}
}

View File

@@ -0,0 +1,9 @@
using mRemoteNG.Config.Serializers;
namespace mRemoteNG.Config.Connections
{
public interface IConnectionsLoader
{
SerializationResult Load();
}
}

View File

@@ -0,0 +1,23 @@
using System;
using mRemoteNG.Config.DatabaseConnectors;
namespace mRemoteNG.Config.Connections.Multiuser
{
public delegate void
ConnectionsUpdateAvailableEventHandler(object sender, ConnectionsUpdateAvailableEventArgs args);
public class ConnectionsUpdateAvailableEventArgs : EventArgs
{
public IDatabaseConnector DatabaseConnector { get; private set; }
public DateTime UpdateTime { get; private set; }
public bool Handled { get; set; }
public ConnectionsUpdateAvailableEventArgs(IDatabaseConnector databaseConnector, DateTime updateTime)
{
if (databaseConnector == null)
throw new ArgumentNullException(nameof(databaseConnector));
DatabaseConnector = databaseConnector;
UpdateTime = updateTime;
}
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace mRemoteNG.Config.Connections.Multiuser
{
public delegate void UpdateCheckFinishedEventHandler(object sender, ConnectionsUpdateCheckFinishedEventArgs args);
public class ConnectionsUpdateCheckFinishedEventArgs : EventArgs
{
public bool UpdateAvailable { get; set; }
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace mRemoteNG.Config.Connections.Multiuser
{
public interface IConnectionsUpdateChecker : IDisposable
{
bool IsUpdateAvailable();
void IsUpdateAvailableAsync();
event EventHandler UpdateCheckStarted;
event UpdateCheckFinishedEventHandler UpdateCheckFinished;
event ConnectionsUpdateAvailableEventHandler ConnectionsUpdateAvailable;
}
}

View File

@@ -0,0 +1,93 @@
using mRemoteNG.App;
using System;
using System.Timers;
// ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.Config.Connections.Multiuser
{
public class RemoteConnectionsSyncronizer : IConnectionsUpdateChecker
{
private readonly Timer _updateTimer;
private readonly IConnectionsUpdateChecker _updateChecker;
public double TimerIntervalInMilliseconds
{
get { return _updateTimer.Interval; }
}
public RemoteConnectionsSyncronizer(IConnectionsUpdateChecker updateChecker)
{
_updateChecker = updateChecker;
_updateTimer = new Timer(3000);
SetEventListeners();
}
private void SetEventListeners()
{
_updateChecker.UpdateCheckStarted += OnUpdateCheckStarted;
_updateChecker.UpdateCheckFinished += OnUpdateCheckFinished;
_updateChecker.ConnectionsUpdateAvailable +=
(sender, args) => ConnectionsUpdateAvailable?.Invoke(sender, args);
_updateTimer.Elapsed += (sender, args) => _updateChecker.IsUpdateAvailableAsync();
ConnectionsUpdateAvailable += Load;
}
private void Load(object sender, ConnectionsUpdateAvailableEventArgs args)
{
Runtime.ConnectionsService.LoadConnections(true, "");
args.Handled = true;
}
public void Enable()
{
_updateTimer.Start();
}
public void Disable()
{
_updateTimer.Stop();
}
public bool IsUpdateAvailable()
{
return _updateChecker.IsUpdateAvailable();
}
public void IsUpdateAvailableAsync()
{
_updateChecker.IsUpdateAvailableAsync();
}
private void OnUpdateCheckStarted(object sender, EventArgs eventArgs)
{
_updateTimer.Stop();
UpdateCheckStarted?.Invoke(sender, eventArgs);
}
private void OnUpdateCheckFinished(object sender, ConnectionsUpdateCheckFinishedEventArgs eventArgs)
{
_updateTimer.Start();
UpdateCheckFinished?.Invoke(sender, eventArgs);
}
public event EventHandler UpdateCheckStarted;
public event UpdateCheckFinishedEventHandler UpdateCheckFinished;
public event ConnectionsUpdateAvailableEventHandler ConnectionsUpdateAvailable;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool itIsSafeToAlsoFreeManagedObjects)
{
if (!itIsSafeToAlsoFreeManagedObjects) return;
_updateTimer.Dispose();
_updateChecker.Dispose();
}
}
}

View File

@@ -0,0 +1,133 @@
using System;
using System.Data;
using System.Data.Common;
using System.Threading;
using mRemoteNG.App;
using mRemoteNG.Config.DatabaseConnectors;
using mRemoteNG.Messages;
namespace mRemoteNG.Config.Connections.Multiuser
{
public class SqlConnectionsUpdateChecker : IConnectionsUpdateChecker
{
private readonly IDatabaseConnector _dbConnector;
private readonly DbCommand _dbQuery;
private DateTime LastUpdateTime => Runtime.ConnectionsService.LastSqlUpdate;
private DateTime _lastDatabaseUpdateTime;
public SqlConnectionsUpdateChecker()
{
_dbConnector = DatabaseConnectorFactory.DatabaseConnectorFromSettings();
_dbQuery = _dbConnector.DbCommand("SELECT * FROM tblUpdate");
_lastDatabaseUpdateTime = default(DateTime);
}
public bool IsUpdateAvailable()
{
RaiseUpdateCheckStartedEvent();
ConnectToSqlDb();
var updateIsAvailable = DatabaseIsMoreUpToDateThanUs();
if (updateIsAvailable)
RaiseConnectionsUpdateAvailableEvent();
RaiseUpdateCheckFinishedEvent(updateIsAvailable);
return updateIsAvailable;
}
public void IsUpdateAvailableAsync()
{
var thread = new Thread(() => IsUpdateAvailable());
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
private void ConnectToSqlDb()
{
try
{
_dbConnector.Connect();
}
catch (Exception e)
{
Runtime.MessageCollector.AddMessage(MessageClass.WarningMsg,
"Unable to connect to Sql DB to check for updates." +
Environment.NewLine + e.Message, true);
}
}
private bool DatabaseIsMoreUpToDateThanUs()
{
var lastUpdateInDb = GetLastUpdateTimeFromDbResponse();
var amTheLastoneUpdated = CheckIfIAmTheLastOneUpdated(lastUpdateInDb);
return (lastUpdateInDb > LastUpdateTime && !amTheLastoneUpdated);
}
private bool CheckIfIAmTheLastOneUpdated(DateTime lastUpdateInDb)
{
DateTime lastSqlUpdateWithoutMilliseconds =
new DateTime(LastUpdateTime.Ticks - (LastUpdateTime.Ticks % TimeSpan.TicksPerSecond),
LastUpdateTime.Kind);
return lastUpdateInDb == lastSqlUpdateWithoutMilliseconds;
}
private DateTime GetLastUpdateTimeFromDbResponse()
{
var lastUpdateInDb = default(DateTime);
try
{
var sqlReader = _dbQuery.ExecuteReader(CommandBehavior.CloseConnection);
sqlReader.Read();
if (sqlReader.HasRows)
lastUpdateInDb = Convert.ToDateTime(sqlReader["LastUpdate"]);
sqlReader.Close();
}
catch (Exception ex)
{
Runtime.MessageCollector.AddMessage(MessageClass.WarningMsg,
"Error executing Sql query to get updates from the DB." +
Environment.NewLine + ex.Message, true);
}
_lastDatabaseUpdateTime = lastUpdateInDb;
return lastUpdateInDb;
}
public event EventHandler UpdateCheckStarted;
private void RaiseUpdateCheckStartedEvent()
{
UpdateCheckStarted?.Invoke(this, EventArgs.Empty);
}
public event UpdateCheckFinishedEventHandler UpdateCheckFinished;
private void RaiseUpdateCheckFinishedEvent(bool updateAvailable)
{
var args = new ConnectionsUpdateCheckFinishedEventArgs {UpdateAvailable = updateAvailable};
UpdateCheckFinished?.Invoke(this, args);
}
public event ConnectionsUpdateAvailableEventHandler ConnectionsUpdateAvailable;
private void RaiseConnectionsUpdateAvailableEvent()
{
Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Remote connection update is available");
var args = new ConnectionsUpdateAvailableEventArgs(_dbConnector, _lastDatabaseUpdateTime);
ConnectionsUpdateAvailable?.Invoke(this, args);
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool itIsSafeToDisposeManagedObjects)
{
if (!itIsSafeToDisposeManagedObjects) return;
_dbConnector.Disconnect();
_dbConnector.Dispose();
_dbQuery.Dispose();
}
}
}

View File

@@ -0,0 +1,47 @@
using System.Collections.Specialized;
using System.ComponentModel;
using mRemoteNG.Connection;
using mRemoteNG.Tools;
namespace mRemoteNG.Config.Connections
{
public class SaveConnectionsOnEdit
{
private IConnectionsService _connectionsService;
public void Subscribe(IConnectionsService connectionsService)
{
_connectionsService = connectionsService.ThrowIfNull(nameof(connectionsService));
connectionsService.ConnectionTreeModel.CollectionChanged += ConnectionTreeModelOnCollectionChanged;
connectionsService.ConnectionTreeModel.PropertyChanged += ConnectionTreeModelOnPropertyChanged;
}
public void Unsubscribe()
{
_connectionsService.ConnectionTreeModel.CollectionChanged -= ConnectionTreeModelOnCollectionChanged;
_connectionsService.ConnectionTreeModel.PropertyChanged -= ConnectionTreeModelOnPropertyChanged;
_connectionsService = null;
}
private void ConnectionTreeModelOnPropertyChanged(object sender,
PropertyChangedEventArgs propertyChangedEventArgs)
{
SaveConnectionOnEdit(propertyChangedEventArgs.PropertyName);
}
private void ConnectionTreeModelOnCollectionChanged(object sender,
NotifyCollectionChangedEventArgs
notifyCollectionChangedEventArgs)
{
SaveConnectionOnEdit();
}
private void SaveConnectionOnEdit(string propertyName = "")
{
if (!Properties.Settings.Default.SaveConnectionsAfterEveryEdit)
return;
_connectionsService?.SaveConnectionsAsync(propertyName);
}
}
}

View File

@@ -0,0 +1,10 @@
namespace mRemoteNG.Config.Connections
{
public enum SaveFormat
{
None,
mRXML,
mRCSV,
SQL
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using mRemoteNG.Config.DatabaseConnectors;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers;
using mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql;
using mRemoteNG.Config.Serializers.Versioning;
using mRemoteNG.Container;
using mRemoteNG.Security;
using mRemoteNG.Security.Authentication;
using mRemoteNG.Security.SymmetricEncryption;
using mRemoteNG.Tools;
using mRemoteNG.Tree.Root;
namespace mRemoteNG.Config.Connections
{
public class SqlConnectionsLoader : IConnectionsLoader
{
private readonly IDeserializer<string, IEnumerable<LocalConnectionPropertiesModel>>
_localConnectionPropertiesDeserializer;
private readonly IDataProvider<string> _dataProvider;
public Func<Optional<SecureString>> AuthenticationRequestor { get; set; } =
() => MiscTools.PasswordDialog("", false);
public SqlConnectionsLoader(
IDeserializer<string, IEnumerable<LocalConnectionPropertiesModel>> localConnectionPropertiesDeserializer,
IDataProvider<string> dataProvider)
{
_localConnectionPropertiesDeserializer =
localConnectionPropertiesDeserializer.ThrowIfNull(nameof(localConnectionPropertiesDeserializer));
_dataProvider = dataProvider.ThrowIfNull(nameof(dataProvider));
}
public SerializationResult Load()
{
var connector = DatabaseConnectorFactory.DatabaseConnectorFromSettings();
var dataProvider = new SqlDataProvider(connector);
var metaDataRetriever = new SqlDatabaseMetaDataRetriever();
var databaseVersionVerifier = new SqlDatabaseVersionVerifier(connector);
var cryptoProvider = new LegacyRijndaelCryptographyProvider();
var metaData = metaDataRetriever.GetDatabaseMetaData(connector) ??
HandleFirstRun(metaDataRetriever, connector);
var decryptionKey = GetDecryptionKey(metaData);
if (!decryptionKey.Any())
throw new Exception("Could not load SQL connections");
databaseVersionVerifier.VerifyDatabaseVersion(metaData.ConfVersion);
var dataTable = dataProvider.Load();
var deserializer = new DataTableDeserializer(cryptoProvider, decryptionKey.First());
var serializationResult = deserializer.Deserialize(dataTable);
ApplyLocalConnectionProperties(serializationResult.ConnectionRecords.OfType<RootNodeInfo>().First());
return serializationResult;
}
private Optional<SecureString> GetDecryptionKey(SqlConnectionListMetaData metaData)
{
var cryptographyProvider = new LegacyRijndaelCryptographyProvider();
var cipherText = metaData.Protected;
var authenticator = new PasswordAuthenticator(cryptographyProvider, cipherText, AuthenticationRequestor);
var authenticated =
authenticator.Authenticate(new RootNodeInfo(RootNodeType.Connection).DefaultPassword
.ConvertToSecureString());
if (authenticated)
return authenticator.LastAuthenticatedPassword;
return Optional<SecureString>.Empty;
}
private void ApplyLocalConnectionProperties(ContainerInfo rootNode)
{
var localPropertiesXml = _dataProvider.Load();
var localConnectionProperties = _localConnectionPropertiesDeserializer.Deserialize(localPropertiesXml);
rootNode
.GetRecursiveChildList()
.Join(localConnectionProperties,
con => con.ConstantID,
locals => locals.ConnectionId,
(con, locals) => new {Connection = con, LocalProperties = locals})
.ForEach(x =>
{
x.Connection.PleaseConnect = x.LocalProperties.Connected;
x.Connection.Favorite = x.LocalProperties.Favorite;
if (x.Connection is ContainerInfo container)
container.IsExpanded = x.LocalProperties.Expanded;
});
}
private SqlConnectionListMetaData HandleFirstRun(SqlDatabaseMetaDataRetriever metaDataRetriever, IDatabaseConnector connector)
{
metaDataRetriever.WriteDatabaseMetaData(new RootNodeInfo(RootNodeType.Connection), connector);
return metaDataRetriever.GetDatabaseMetaData(connector);
}
}
}

View File

@@ -0,0 +1,183 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using mRemoteNG.App;
using mRemoteNG.App.Info;
using mRemoteNG.Config.DatabaseConnectors;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers;
using mRemoteNG.Config.Serializers.ConnectionSerializers.MsSql;
using mRemoteNG.Config.Serializers.Versioning;
using mRemoteNG.Connection;
using mRemoteNG.Container;
using mRemoteNG.Messages;
using mRemoteNG.Security;
using mRemoteNG.Security.SymmetricEncryption;
using mRemoteNG.Tools;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
namespace mRemoteNG.Config.Connections
{
public class SqlConnectionsSaver : ISaver<IConnectionTreeModel>
{
private readonly SaveFilter _saveFilter;
private readonly ISerializer<IEnumerable<LocalConnectionPropertiesModel>, string> _localPropertiesSerializer;
private readonly IDataProvider<string> _dataProvider;
public SqlConnectionsSaver(SaveFilter saveFilter,
ISerializer<IEnumerable<LocalConnectionPropertiesModel>, string>
localPropertieSerializer,
IDataProvider<string> localPropertiesDataProvider)
{
if (saveFilter == null)
throw new ArgumentNullException(nameof(saveFilter));
_saveFilter = saveFilter;
_localPropertiesSerializer = localPropertieSerializer.ThrowIfNull(nameof(localPropertieSerializer));
_dataProvider = localPropertiesDataProvider.ThrowIfNull(nameof(localPropertiesDataProvider));
}
public void Save(IConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
{
var rootTreeNode = connectionTreeModel.RootNodes.OfType<RootNodeInfo>().First();
UpdateLocalConnectionProperties(rootTreeNode);
if (PropertyIsLocalOnly(propertyNameTrigger))
{
Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg,
$"Property {propertyNameTrigger} is local only. Not saving to database.");
return;
}
if (SqlUserIsReadOnly())
{
Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg,
"Trying to save connection tree but the SQL read only checkbox is checked, aborting!");
return;
}
using (var dbConnector = DatabaseConnectorFactory.DatabaseConnectorFromSettings())
{
dbConnector.Connect();
var databaseVersionVerifier = new SqlDatabaseVersionVerifier(dbConnector);
var metaDataRetriever = new SqlDatabaseMetaDataRetriever();
var metaData = metaDataRetriever.GetDatabaseMetaData(dbConnector);
if (!databaseVersionVerifier.VerifyDatabaseVersion(metaData.ConfVersion))
{
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg,
Language.ErrorConnectionListSaveFailed);
return;
}
metaDataRetriever.WriteDatabaseMetaData(rootTreeNode, dbConnector);
UpdateConnectionsTable(rootTreeNode, dbConnector);
UpdateUpdatesTable(dbConnector);
}
Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Saved connections to database");
}
/// <summary>
/// Determines if a given property name should be only saved
/// locally.
/// </summary>
/// <param name="property">
/// The name of the property that triggered the save event
/// </param>
/// <returns></returns>
private bool PropertyIsLocalOnly(string property)
{
return property == nameof(ConnectionInfo.OpenConnections) ||
property == nameof(ContainerInfo.IsExpanded) ||
property == nameof(ContainerInfo.Favorite);
}
private void UpdateLocalConnectionProperties(ContainerInfo rootNode)
{
var a = rootNode.GetRecursiveChildList().Select(info => new LocalConnectionPropertiesModel
{
ConnectionId = info.ConstantID,
Connected = info.OpenConnections.Count > 0,
Expanded = info is ContainerInfo c && c.IsExpanded,
Favorite = info.Favorite,
});
var serializedProperties = _localPropertiesSerializer.Serialize(a);
_dataProvider.Save(serializedProperties);
Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Saved local connection properties");
}
private void UpdateRootNodeTable(RootNodeInfo rootTreeNode, IDatabaseConnector databaseConnector)
{
var cryptographyProvider = new LegacyRijndaelCryptographyProvider();
string strProtected;
if (rootTreeNode != null)
{
if (rootTreeNode.Password)
{
var password = rootTreeNode.PasswordString.ConvertToSecureString();
strProtected = cryptographyProvider.Encrypt("ThisIsProtected", password);
}
else
{
strProtected = cryptographyProvider.Encrypt("ThisIsNotProtected", Runtime.EncryptionKey);
}
}
else
{
strProtected = cryptographyProvider.Encrypt("ThisIsNotProtected", Runtime.EncryptionKey);
}
var dbQuery = databaseConnector.DbCommand("DELETE FROM tblRoot");
dbQuery.ExecuteNonQuery();
if (rootTreeNode != null)
{
dbQuery =
databaseConnector.DbCommand(
"INSERT INTO tblRoot (Name, Export, Protected, ConfVersion) VALUES(\'" +
MiscTools.PrepareValueForDB(rootTreeNode.Name) + "\', 0, \'" + strProtected + "\'," +
ConnectionsFileInfo.ConnectionFileVersion.ToString(CultureInfo.InvariantCulture) + ")");
dbQuery.ExecuteNonQuery();
}
else
{
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg,
$"UpdateRootNodeTable: rootTreeNode was null. Could not insert!");
}
}
private void UpdateConnectionsTable(RootNodeInfo rootTreeNode, IDatabaseConnector databaseConnector)
{
var dataProvider = new SqlDataProvider(databaseConnector);
var currentDataTable = dataProvider.Load();
var cryptoProvider = new LegacyRijndaelCryptographyProvider();
var serializer = new DataTableSerializer(_saveFilter, cryptoProvider,
rootTreeNode.PasswordString.ConvertToSecureString());
serializer.SetSourceDataTable(currentDataTable);
var dataTable = serializer.Serialize(rootTreeNode);
//var dbQuery = databaseConnector.DbCommand("DELETE FROM tblCons");
//dbQuery.ExecuteNonQuery();
dataProvider.Save(dataTable);
}
private void UpdateUpdatesTable(IDatabaseConnector databaseConnector)
{
var dbQuery = databaseConnector.DbCommand("DELETE FROM tblUpdate");
dbQuery.ExecuteNonQuery();
dbQuery = databaseConnector.DbCommand("INSERT INTO tblUpdate (LastUpdate) VALUES(\'" + MiscTools.DBDate(DateTime.Now) + "\')");
dbQuery.ExecuteNonQuery();
}
private bool SqlUserIsReadOnly()
{
return Properties.Settings.Default.SQLReadOnly;
}
}
}

View File

@@ -0,0 +1,59 @@
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Tools;
using System;
using System.IO;
using System.Security;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Xml;
using mRemoteNG.App.Info;
using mRemoteNG.Config.Serializers;
using mRemoteNG.Connection;
using mRemoteNG.Credential;
using mRemoteNG.UI.Forms;
namespace mRemoteNG.Config.Connections
{
public class XmlConnectionsLoader : IConnectionsLoader
{
private readonly string _credentialFilePath = Path.Combine(CredentialsFileInfo.CredentialsPath, CredentialsFileInfo.CredentialsFile);
private readonly string _connectionFilePath;
private readonly ConnectionsService _connectionsService;
private readonly ICredentialService _credentialService;
public XmlConnectionsLoader(string connectionFilePath, ICredentialService credentialService, ConnectionsService connectionsService)
{
if (string.IsNullOrEmpty(connectionFilePath))
throw new ArgumentException($"{nameof(connectionFilePath)} cannot be null or empty");
if (!File.Exists(connectionFilePath))
throw new FileNotFoundException($"{connectionFilePath} does not exist");
_connectionFilePath = connectionFilePath;
_connectionsService = connectionsService.ThrowIfNull(nameof(connectionsService));
_credentialService = credentialService.ThrowIfNull(nameof(credentialService));
}
public SerializationResult Load()
{
var dataProvider = new FileDataProvider(_connectionFilePath);
var xmlString = dataProvider.Load();
var deserializer = new CredentialManagerUpgradeForm
{
ConnectionFilePath = _connectionFilePath,
NewCredentialRepoPath = _credentialFilePath,
ConnectionsService = _connectionsService,
CredentialService = _credentialService,
ConnectionDeserializer = new XmlConnectionsDeserializer(PromptForPassword)
};
var serializationResult = deserializer.Deserialize(xmlString);
return serializationResult;
}
private Optional<SecureString> PromptForPassword()
{
var password = MiscTools.PasswordDialog(Path.GetFileName(_connectionFilePath), false);
return password;
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
using System.Linq;
using mRemoteNG.App;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Xml;
using mRemoteNG.Security;
using mRemoteNG.Security.Factories;
using mRemoteNG.Tree;
using mRemoteNG.Tree.Root;
namespace mRemoteNG.Config.Connections
{
public class XmlConnectionsSaver : ISaver<IConnectionTreeModel>
{
private readonly string _connectionFileName;
private readonly SaveFilter _saveFilter;
public XmlConnectionsSaver(string connectionFileName, SaveFilter saveFilter)
{
if (string.IsNullOrEmpty(connectionFileName))
throw new ArgumentException($"Argument '{nameof(connectionFileName)}' cannot be null or empty");
if (saveFilter == null)
throw new ArgumentNullException(nameof(saveFilter));
_connectionFileName = connectionFileName;
_saveFilter = saveFilter;
}
public void Save(IConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
{
try
{
var cryptographyProvider = new CryptoProviderFactoryFromSettings().Build();
var serializerFactory = new XmlConnectionSerializerFactory();
var xmlConnectionsSerializer = serializerFactory.Build(
cryptographyProvider,
connectionTreeModel,
_saveFilter,
Properties.Settings.Default.EncryptCompleteConnectionsFile);
var rootNode = connectionTreeModel.RootNodes.OfType<RootNodeInfo>().First();
var xml = xmlConnectionsSerializer.Serialize(rootNode);
var fileDataProvider = new FileDataProviderWithRollingBackup(_connectionFileName);
fileDataProvider.Save(xml);
}
catch (Exception ex)
{
Runtime.MessageCollector?.AddExceptionStackTrace("SaveToXml failed", ex);
}
}
}
}

View File

@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using System.Xml.Linq;
using mRemoteNG.Credential;
using mRemoteNG.Security;
using mRemoteNG.Security.Factories;
namespace mRemoteNG.Config
{
public class CredentialHarvester
{
private readonly IEqualityComparer<ICredentialRecord> _credentialComparer = new CredentialDomainUserComparer();
// maps a connectioninfo (by its id) to the credential object that was harvested
public Dictionary<Guid, ICredentialRecord> ConnectionToCredentialMap { get; } =
new Dictionary<Guid, ICredentialRecord>();
public IEnumerable<ICredentialRecord> Harvest(XDocument xDocument, SecureString decryptionKey)
{
if (xDocument == null)
throw new ArgumentNullException(nameof(xDocument));
var cryptoProvider = new CryptoProviderFactoryFromXml(xDocument.Root).Build();
foreach (var element in xDocument.Descendants("Node"))
{
if (!EntryHasSomeCredentialData(element)) continue;
var newCredential = BuildCredential(element, cryptoProvider, decryptionKey);
Guid connectionId;
Guid.TryParse(element.Attribute("Id")?.Value, out connectionId);
if (connectionId == Guid.Empty)
{
//error
}
if (ConnectionToCredentialMap.Values.Contains(newCredential, _credentialComparer))
{
var existingCredential =
ConnectionToCredentialMap.Values.First(record =>
_credentialComparer.Equals(newCredential, record));
ConnectionToCredentialMap.Add(connectionId, existingCredential);
}
else
ConnectionToCredentialMap.Add(connectionId, newCredential);
}
return ConnectionToCredentialMap.Values.Distinct(_credentialComparer);
}
private ICredentialRecord BuildCredential(XElement element,
ICryptographyProvider cryptographyProvider,
SecureString decryptionKey)
{
var credential = new CredentialRecord
{
Title = $"{element.Attribute("Username")?.Value}\\{element.Attribute("Domain")?.Value}",
Username = element.Attribute("Username")?.Value,
Domain = element.Attribute("Domain")?.Value,
Password = cryptographyProvider.Decrypt(element.Attribute("Password")?.Value, decryptionKey)
.ConvertToSecureString()
};
return credential;
}
private static bool EntryHasSomeCredentialData(XElement e)
{
return e.Attribute("Username")?.Value != "" ||
e.Attribute("Domain")?.Value != "" ||
e.Attribute("Password")?.Value != "";
}
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Security;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers;
using mRemoteNG.Credential;
namespace mRemoteNG.Config
{
public class CredentialRecordLoader
{
private readonly IDataProvider<string> _dataProvider;
private readonly ISecureDeserializer<string, IEnumerable<ICredentialRecord>> _deserializer;
public CredentialRecordLoader(IDataProvider<string> dataProvider,
ISecureDeserializer<string, IEnumerable<ICredentialRecord>> deserializer)
{
if (dataProvider == null)
throw new ArgumentNullException(nameof(dataProvider));
if (deserializer == null)
throw new ArgumentNullException(nameof(deserializer));
_dataProvider = dataProvider;
_deserializer = deserializer;
}
public IEnumerable<ICredentialRecord> Load(SecureString key)
{
var serializedCredentials = _dataProvider.Load();
return _deserializer.Deserialize(serializedCredentials, key);
}
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Security;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers;
using mRemoteNG.Credential;
namespace mRemoteNG.Config
{
public class CredentialRecordSaver
{
private readonly IDataProvider<string> _dataProvider;
private readonly ISecureSerializer<IEnumerable<ICredentialRecord>, string> _serializer;
public CredentialRecordSaver(IDataProvider<string> dataProvider,
ISecureSerializer<IEnumerable<ICredentialRecord>, string> serializer)
{
if (dataProvider == null)
throw new ArgumentNullException(nameof(dataProvider));
if (serializer == null)
throw new ArgumentNullException(nameof(serializer));
_dataProvider = dataProvider;
_serializer = serializer;
}
public void Save(IEnumerable<ICredentialRecord> credentialRecords, SecureString key)
{
var serializedCredentials = _serializer.Serialize(credentialRecords, key);
_dataProvider.Save(serializedCredentials);
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.CredentialProviderSerializer;
using mRemoteNG.Credential;
namespace mRemoteNG.Config
{
public class CredentialRepositoryListLoader : ILoader<IEnumerable<ICredentialRepository>>
{
private readonly IDataProvider<string> _dataProvider;
private readonly CredentialRepositoryListDeserializer _deserializer;
public CredentialRepositoryListLoader(IDataProvider<string> dataProvider,
CredentialRepositoryListDeserializer deserializer)
{
if (dataProvider == null)
throw new ArgumentNullException(nameof(dataProvider));
if (deserializer == null)
throw new ArgumentNullException(nameof(deserializer));
_dataProvider = dataProvider;
_deserializer = deserializer;
}
public IEnumerable<ICredentialRepository> Load()
{
var data = _dataProvider.Load();
return _deserializer.Deserialize(data);
}
}
}

View File

@@ -0,0 +1,39 @@
using System.Collections.Generic;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.CredentialProviderSerializer;
using mRemoteNG.Credential;
using mRemoteNG.Credential.Repositories;
using mRemoteNG.Tools;
namespace mRemoteNG.Config
{
public class CredentialRepositoryListPersistor : ISaver<IEnumerable<ICredentialRepository>>, ILoader<IEnumerable<ICredentialRepository>>
{
private readonly IReadOnlyCollection<ICredentialRepositoryFactory> _repositoryFactories;
private readonly IDataProvider<string> _dataProvider;
private readonly CredentialRepositoryListDeserializer _deserializer;
private readonly CredentialRepositoryListSerializer _serializer;
public CredentialRepositoryListPersistor(
IDataProvider<string> dataProvider,
IReadOnlyCollection<ICredentialRepositoryFactory> repositoryFactories)
{
_repositoryFactories = repositoryFactories.ThrowIfNull(nameof(repositoryFactories));
_dataProvider = dataProvider.ThrowIfNull(nameof(dataProvider));
_deserializer = new CredentialRepositoryListDeserializer();
_serializer = new CredentialRepositoryListSerializer();
}
public IEnumerable<ICredentialRepository> Load()
{
var data = _dataProvider.Load();
return _deserializer.Deserialize(data, _repositoryFactories);
}
public void Save(IEnumerable<ICredentialRepository> repositories, string propertyNameTrigger = "")
{
var data = _serializer.Serialize(repositories);
_dataProvider.Save(data);
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.CredentialProviderSerializer;
using mRemoteNG.Credential;
namespace mRemoteNG.Config
{
public class CredentialRepositoryListSaver : ISaver<IEnumerable<ICredentialRepository>>
{
private readonly IDataProvider<string> _dataProvider;
public CredentialRepositoryListSaver(IDataProvider<string> dataProvider)
{
if (dataProvider == null)
throw new ArgumentNullException(nameof(dataProvider));
_dataProvider = dataProvider;
}
public void Save(IEnumerable<ICredentialRepository> repositories, string propertyNameTrigger = "")
{
var serializer = new CredentialRepositoryListSerializer();
var data = serializer.Serialize(repositories);
_dataProvider.Save(data);
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.IO;
using mRemoteNG.App;
using mRemoteNG.Messages;
namespace mRemoteNG.Config.DataProviders
{
public class FileBackupCreator
{
public void CreateBackupFile(string fileName)
{
try
{
if (WeDontNeedToBackup(fileName))
return;
var backupFileName =
string.Format(Properties.Settings.Default.BackupFileNameFormat, fileName, DateTime.Now);
File.Copy(fileName, backupFileName);
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionMessage(Language.ConnectionsFileBackupFailed, ex,
MessageClass.WarningMsg);
throw;
}
}
private bool WeDontNeedToBackup(string filePath)
{
return FeatureIsTurnedOff() || FileDoesntExist(filePath);
}
private bool FileDoesntExist(string filePath)
{
return !File.Exists(filePath);
}
private bool FeatureIsTurnedOff()
{
return Properties.Settings.Default.BackupFileKeepCount == 0;
}
}
}

View File

@@ -0,0 +1,32 @@
using System.IO;
using System.Linq;
namespace mRemoteNG.Config.DataProviders
{
public class FileBackupPruner
{
public void PruneBackupFiles(string filePath, int maxBackupsToKeep)
{
var fileName = Path.GetFileName(filePath);
var directoryName = Path.GetDirectoryName(filePath);
if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(directoryName))
return;
var searchPattern = string.Format(Properties.Settings.Default.BackupFileNameFormat, fileName, "*");
var files = Directory.GetFiles(directoryName, searchPattern);
if (files.Length <= maxBackupsToKeep)
return;
var filesToDelete = files
.OrderByDescending(s => s)
.Skip(maxBackupsToKeep);
foreach (var file in filesToDelete)
{
File.Delete(file);
}
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.IO;
using mRemoteNG.App;
namespace mRemoteNG.Config.DataProviders
{
public class FileDataProvider : IDataProvider<string>
{
public string FilePath { get; set; }
public FileDataProvider(string filePath)
{
FilePath = filePath;
}
public virtual string Load()
{
var fileContents = "";
try
{
fileContents = File.ReadAllText(FilePath);
}
catch (FileNotFoundException ex)
{
Runtime.MessageCollector.AddExceptionStackTrace(
$"Could not load file. File does not exist '{FilePath}'",
ex);
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionStackTrace($"Failed to load file {FilePath}", ex);
}
return fileContents;
}
public virtual void Save(string content)
{
try
{
CreateMissingDirectories();
File.WriteAllText(FilePath, content);
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionStackTrace($"Failed to save file {FilePath}", ex);
}
}
public virtual void MoveTo(string newPath)
{
try
{
File.Move(FilePath, newPath);
FilePath = newPath;
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionStackTrace($"Failed to move file {FilePath} to {newPath}", ex);
}
}
private void CreateMissingDirectories()
{
var dirname = Path.GetDirectoryName(FilePath);
if (dirname == null) return;
Directory.CreateDirectory(dirname);
}
}
}

View File

@@ -0,0 +1,18 @@
namespace mRemoteNG.Config.DataProviders
{
public class FileDataProviderWithRollingBackup : FileDataProvider
{
private readonly FileBackupCreator _fileBackupCreator;
public FileDataProviderWithRollingBackup(string filePath) : base(filePath)
{
_fileBackupCreator = new FileBackupCreator();
}
public override void Save(string content)
{
_fileBackupCreator.CreateBackupFile(FilePath);
base.Save(content);
}
}
}

View File

@@ -0,0 +1,9 @@
namespace mRemoteNG.Config.DataProviders
{
public interface IDataProvider<TFormat>
{
TFormat Load();
void Save(TFormat contents);
}
}

View File

@@ -0,0 +1,22 @@
namespace mRemoteNG.Config.DataProviders
{
public class InMemoryStringDataProvider : IDataProvider<string>
{
private string _contents;
public InMemoryStringDataProvider(string initialContents = "")
{
_contents = initialContents;
}
public string Load()
{
return _contents;
}
public void Save(string contents)
{
_contents = contents;
}
}
}

View File

@@ -0,0 +1,114 @@
using System.Data;
using mRemoteNG.Config.DatabaseConnectors;
using mRemoteNG.Messages;
using mRemoteNG.App;
using MySql.Data.MySqlClient;
using System.Data.SqlClient;
namespace mRemoteNG.Config.DataProviders
{
public class SqlDataProvider : IDataProvider<DataTable>
{
public IDatabaseConnector DatabaseConnector { get; }
public SqlDataProvider(IDatabaseConnector databaseConnector)
{
DatabaseConnector = databaseConnector;
}
public DataTable Load()
{
var dataTable = new DataTable();
var dbQuery = DatabaseConnector.DbCommand("SELECT * FROM tblCons ORDER BY PositionID ASC");
DatabaseConnector.AssociateItemToThisConnector(dbQuery);
if (!DatabaseConnector.IsConnected)
OpenConnection();
var dbDataReader = dbQuery.ExecuteReader(CommandBehavior.CloseConnection);
if (dbDataReader.HasRows)
dataTable.Load(dbDataReader);
dbDataReader.Close();
return dataTable;
}
public void Save(DataTable dataTable)
{
if (DbUserIsReadOnly())
{
Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg,
"Trying to save connections but the SQL read only checkbox is checked, aborting!");
return;
}
if (!DatabaseConnector.IsConnected)
OpenConnection();
if (DatabaseConnector.GetType() == typeof(MSSqlDatabaseConnector))
{
SqlConnection sqlConnection = (SqlConnection)DatabaseConnector.DbConnection();
using (SqlTransaction transaction = sqlConnection.BeginTransaction(System.Data.IsolationLevel.Serializable))
{
using (SqlCommand sqlCommand = new SqlCommand())
{
sqlCommand.Connection = sqlConnection;
sqlCommand.Transaction = transaction;
sqlCommand.CommandText = "SELECT * FROM tblCons";
using (SqlDataAdapter dataAdpater = new SqlDataAdapter())
{
dataAdpater.SelectCommand = sqlCommand;
SqlCommandBuilder builder = new SqlCommandBuilder(dataAdpater);
// Avoid optimistic concurrency, check if it is necessary.
builder.ConflictOption = ConflictOption.OverwriteChanges;
dataAdpater.UpdateCommand = builder.GetUpdateCommand();
dataAdpater.DeleteCommand = builder.GetDeleteCommand();
dataAdpater.InsertCommand = builder.GetInsertCommand();
dataAdpater.Update(dataTable);
transaction.Commit();
}
}
}
}
else if (DatabaseConnector.GetType() == typeof(MySqlDatabaseConnector))
{
var dbConnection = (MySqlConnection) DatabaseConnector.DbConnection();
using (MySqlTransaction transaction = dbConnection.BeginTransaction(System.Data.IsolationLevel.Serializable))
{
using (MySqlCommand sqlCommand = new MySqlCommand())
{
sqlCommand.Connection = dbConnection;
sqlCommand.Transaction = transaction;
sqlCommand.CommandText = "SELECT * FROM tblCons";
using (MySqlDataAdapter dataAdapter = new MySqlDataAdapter(sqlCommand))
{
dataAdapter.UpdateBatchSize = 1000;
using (MySqlCommandBuilder cb = new MySqlCommandBuilder(dataAdapter))
{
dataAdapter.Update(dataTable);
transaction.Commit();
}
}
}
}
}
}
public void OpenConnection()
{
DatabaseConnector.Connect();
}
public void CloseConnection()
{
DatabaseConnector.Disconnect();
}
private bool DbUserIsReadOnly()
{
return Properties.Settings.Default.SQLReadOnly;
}
}
}

View File

@@ -0,0 +1,11 @@
namespace mRemoteNG.Config.DatabaseConnectors
{
public enum ConnectionTestResult
{
ConnectionSucceded,
ServerNotAccessible,
UnknownDatabase,
CredentialsRejected,
UnknownError
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Data.SqlClient;
using System.Threading.Tasks;
namespace mRemoteNG.Config.DatabaseConnectors
{
/// <summary>
/// A helper class for testing database connectivity
/// </summary>
public class DatabaseConnectionTester
{
public async Task<ConnectionTestResult> TestConnectivity(string type,
string server,
string database,
string username,
string password)
{
using (var dbConnector = DatabaseConnectorFactory.DatabaseConnector(type, server, database, username, password))
{
try
{
await dbConnector.ConnectAsync();
return ConnectionTestResult.ConnectionSucceded;
}
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)
{
return ConnectionTestResult.UnknownError;
}
}
}
}
}

View File

@@ -0,0 +1,32 @@
using mRemoteNG.App;
using mRemoteNG.Security.SymmetricEncryption;
namespace mRemoteNG.Config.DatabaseConnectors
{
public class DatabaseConnectorFactory
{
public static IDatabaseConnector DatabaseConnectorFromSettings()
{
var sqlType = Properties.Settings.Default.SQLServerType;
var sqlHost = Properties.Settings.Default.SQLHost;
var sqlCatalog = Properties.Settings.Default.SQLDatabaseName;
var sqlUsername = Properties.Settings.Default.SQLUser;
var cryptographyProvider = new LegacyRijndaelCryptographyProvider();
var sqlPassword = cryptographyProvider.Decrypt(Properties.Settings.Default.SQLPass, Runtime.EncryptionKey);
return DatabaseConnector(sqlType, sqlHost, sqlCatalog, sqlUsername, sqlPassword);
}
public static IDatabaseConnector DatabaseConnector(string type, string server, string database, string username, string password)
{
switch (type)
{
case "mysql":
return new MySqlDatabaseConnector(server, database, username, password);
case "mssql":
default:
return new MSSqlDatabaseConnector(server, database, username, password);
}
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Data.Common;
using System.Threading.Tasks;
namespace mRemoteNG.Config.DatabaseConnectors
{
public interface IDatabaseConnector : IDisposable
{
DbConnection DbConnection();
DbCommand DbCommand(string dbCommand);
bool IsConnected { get; }
void Connect();
Task ConnectAsync();
void Disconnect();
void AssociateItemToThisConnector(DbCommand dbCommand);
}
}

View File

@@ -0,0 +1,110 @@
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Threading.Tasks;
// ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.Config.DatabaseConnectors
{
public class MSSqlDatabaseConnector : IDatabaseConnector
{
private DbConnection _dbConnection { get; set; } = default(SqlConnection);
private string _dbConnectionString = "";
private readonly string _dbHost;
private readonly string _dbCatalog;
private readonly string _dbUsername;
private readonly string _dbPassword;
public DbConnection DbConnection()
{
return _dbConnection;
}
public DbCommand DbCommand(string dbCommand)
{
return new SqlCommand(dbCommand, (SqlConnection) _dbConnection);
}
public bool IsConnected => (_dbConnection.State == ConnectionState.Open);
public MSSqlDatabaseConnector(string sqlServer, string catalog, string username, string password)
{
_dbHost = sqlServer;
_dbCatalog = catalog;
_dbUsername = username;
_dbPassword = password;
Initialize();
}
private void Initialize()
{
BuildSqlConnectionString();
_dbConnection = new SqlConnection(_dbConnectionString);
}
private void BuildSqlConnectionString()
{
if (_dbUsername != "")
BuildDbConnectionStringWithCustomCredentials();
else
BuildDbConnectionStringWithDefaultCredentials();
}
private void BuildDbConnectionStringWithCustomCredentials()
{
string[] hostParts = _dbHost.Split(new char[] { ':' }, 2);
var _dbPort = (hostParts.Length == 2) ? hostParts[1] : "1433";
_dbConnectionString = new SqlConnectionStringBuilder
{
DataSource = $"{hostParts[0]},{_dbPort}",
InitialCatalog = _dbCatalog,
UserID = _dbUsername,
Password = _dbPassword,
}.ToString();
}
private void BuildDbConnectionStringWithDefaultCredentials()
{
_dbConnectionString = new SqlConnectionStringBuilder
{
DataSource = _dbHost,
InitialCatalog = _dbCatalog,
IntegratedSecurity = true
}.ToString();
}
public void Connect()
{
_dbConnection.Open();
}
public async Task ConnectAsync()
{
await _dbConnection.OpenAsync();
}
public void Disconnect()
{
_dbConnection.Close();
}
public void AssociateItemToThisConnector(DbCommand dbCommand)
{
dbCommand.Connection = (SqlConnection) _dbConnection;
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool itIsSafeToFreeManagedObjects)
{
if (!itIsSafeToFreeManagedObjects) return;
_dbConnection.Close();
_dbConnection.Dispose();
}
}
}

View File

@@ -0,0 +1,85 @@
using System.Data;
using System.Data.Common;
using System.Threading.Tasks;
using MySql.Data.MySqlClient;
// ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.Config.DatabaseConnectors
{
public class MySqlDatabaseConnector : IDatabaseConnector
{
private DbConnection _dbConnection { get; set; } = default(MySqlConnection);
private string _dbConnectionString = "";
private readonly string _dbHost;
private readonly string _dbPort;
private readonly string _dbName;
private readonly string _dbUsername;
private readonly string _dbPassword;
public DbConnection DbConnection()
{
return _dbConnection;
}
public DbCommand DbCommand(string dbCommand)
{
return new MySqlCommand(dbCommand, (MySqlConnection) _dbConnection);
}
public bool IsConnected => (_dbConnection.State == ConnectionState.Open);
public MySqlDatabaseConnector(string host, string database, string username, string password)
{
string[] hostParts = host.Split(new char[]{':'}, 2);
_dbHost = hostParts[0];
_dbPort = (hostParts.Length == 2)?hostParts[1]:"3306";
_dbName = database;
_dbUsername = username;
_dbPassword = password;
Initialize();
}
private void Initialize()
{
BuildSqlConnectionString();
_dbConnection = new MySqlConnection(_dbConnectionString);
}
private void BuildSqlConnectionString()
{
_dbConnectionString = $"server={_dbHost};user={_dbUsername};database={_dbName};port={_dbPort};password={_dbPassword}";
}
public void Connect()
{
_dbConnection.Open();
}
public async Task ConnectAsync()
{
await _dbConnection.OpenAsync();
}
public void Disconnect()
{
_dbConnection.Close();
}
public void AssociateItemToThisConnector(DbCommand dbCommand)
{
dbCommand.Connection = (MySqlConnection) _dbConnection;
}
public void Dispose()
{
Dispose(true);
}
private void Dispose(bool itIsSafeToFreeManagedObjects)
{
if (!itIsSafeToFreeManagedObjects) return;
_dbConnection.Close();
_dbConnection.Dispose();
}
}
}

View File

@@ -0,0 +1,7 @@
namespace mRemoteNG.Config
{
public interface ILoader<out T>
{
T Load();
}
}

View File

@@ -0,0 +1,7 @@
namespace mRemoteNG.Config
{
public interface ISaver<in T>
{
void Save(T model, string propertyNameTrigger = "");
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Linq;
using mRemoteNG.App;
using mRemoteNG.Config.Serializers.MiscSerializers;
using mRemoteNG.Container;
using mRemoteNG.Tools;
namespace mRemoteNG.Config.Import
{
public class ActiveDirectoryImporter : IConnectionImporter<string>
{
public void Import(string ldapPath, ContainerInfo destinationContainer)
{
Import(ldapPath, destinationContainer, false);
}
public static void Import(string ldapPath, ContainerInfo destinationContainer, bool importSubOu)
{
try
{
ldapPath.ThrowIfNullOrEmpty(nameof(ldapPath));
var deserializer = new ActiveDirectoryDeserializer(ldapPath, importSubOu);
var connectionTreeModel = deserializer.Deserialize();
var importedRootNode = connectionTreeModel.RootNodes.First();
if (importedRootNode == null) return;
var childrenToAdd = importedRootNode.Children.ToArray();
destinationContainer.AddChildRange(childrenToAdd);
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionMessage("Config.Import.ActiveDirectory.Import() failed.", ex);
}
}
}
}

View File

@@ -0,0 +1,10 @@
using mRemoteNG.Container;
namespace mRemoteNG.Config.Import
{
public interface IConnectionImporter<in TSource>
where TSource : class
{
void Import(TSource source, ContainerInfo destinationContainer);
}
}

View File

@@ -0,0 +1,43 @@
using mRemoteNG.App;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Csv;
using mRemoteNG.Container;
using mRemoteNG.Messages;
using System.IO;
using System.Linq;
using mRemoteNG.UI.Forms;
namespace mRemoteNG.Config.Import
{
public class MRemoteNGCsvImporter : IConnectionImporter<string>
{
public void Import(string filePath, ContainerInfo destinationContainer)
{
if (string.IsNullOrEmpty(filePath))
{
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, "Unable to import file. File path is null.");
return;
}
if (!File.Exists(filePath))
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg,
$"Unable to import file. File does not exist. Path: {filePath}");
var dataProvider = new FileDataProvider(filePath);
var xmlString = dataProvider.Load();
var xmlConnectionsDeserializer = new CsvConnectionsDeserializerMremotengFormat();
var serializationResult = xmlConnectionsDeserializer.Deserialize(xmlString);
var credentialImportForm = new CredentialImportForm
{
ImportedCredentialRecords = serializationResult.ConnectionToCredentialMap.DistinctCredentialRecords.ToList(),
CredentialService = Runtime.CredentialService
};
credentialImportForm.ShowDialog();
var rootImportContainer = new ContainerInfo {Name = Path.GetFileNameWithoutExtension(filePath)};
rootImportContainer.AddChildRange(serializationResult.ConnectionRecords);
destinationContainer.AddChild(rootImportContainer);
}
}
}

View File

@@ -0,0 +1,36 @@
using System.IO;
using mRemoteNG.App;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Xml;
using mRemoteNG.Container;
using mRemoteNG.Messages;
namespace mRemoteNG.Config.Import
{
// ReSharper disable once InconsistentNaming
public class MRemoteNGXmlImporter : IConnectionImporter<string>
{
public void Import(string fileName, ContainerInfo destinationContainer)
{
if (fileName == null)
{
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, "Unable to import file. File path is null.");
return;
}
if (!File.Exists(fileName))
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg,
$"Unable to import file. File does not exist. Path: {fileName}");
var dataProvider = new FileDataProvider(fileName);
var xmlString = dataProvider.Load();
var xmlConnectionsDeserializer = new XmlConnectionsDeserializer();
var serializationResult = xmlConnectionsDeserializer.Deserialize(xmlString, true);
var rootImportContainer = new ContainerInfo {Name = Path.GetFileNameWithoutExtension(fileName)};
rootImportContainer.AddChildRange(serializationResult.ConnectionRecords);
destinationContainer.AddChild(rootImportContainer);
}
}
}

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Linq;
using mRemoteNG.Config.Serializers.MiscSerializers;
using mRemoteNG.Connection.Protocol;
using mRemoteNG.Container;
using mRemoteNG.Tools;
namespace mRemoteNG.Config.Import
{
public class PortScanImporter : IConnectionImporter<IEnumerable<ScanHost>>
{
private readonly ProtocolType _targetProtocolType;
public PortScanImporter(ProtocolType targetProtocolType)
{
_targetProtocolType = targetProtocolType;
}
public void Import(IEnumerable<ScanHost> hosts, ContainerInfo destinationContainer)
{
var deserializer = new PortScanDeserializer(_targetProtocolType);
var connectionTreeModel = deserializer.Deserialize(hosts);
var importedRootNode = connectionTreeModel.RootNodes.First();
if (importedRootNode == null) return;
var childrenToAdd = importedRootNode.Children.ToArray();
destinationContainer.AddChildRange(childrenToAdd);
}
}
}

View File

@@ -0,0 +1,21 @@
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.MiscSerializers;
using mRemoteNG.Container;
namespace mRemoteNG.Config.Import
{
public class PuttyConnectionManagerImporter : IConnectionImporter<string>
{
public void Import(string filePath, ContainerInfo destinationContainer)
{
var dataProvider = new FileDataProvider(filePath);
var xmlContent = dataProvider.Load();
var deserializer = new PuttyConnectionManagerDeserializer();
var serializationResult = deserializer.Deserialize(xmlContent);
destinationContainer.AddChildRange(serializationResult.ConnectionRecords);
}
}
}

View File

@@ -0,0 +1,27 @@
using System.IO;
using System.Linq;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.MiscSerializers;
using mRemoteNG.Container;
namespace mRemoteNG.Config.Import
{
public class RemoteDesktopConnectionImporter : IConnectionImporter<string>
{
public void Import(string fileName, ContainerInfo destinationContainer)
{
var dataProvider = new FileDataProvider(fileName);
var content = dataProvider.Load();
var deserializer = new RemoteDesktopConnectionDeserializer();
var serializationResult = deserializer.Deserialize(content);
var importedConnection = serializationResult.ConnectionRecords.FirstOrDefault();
if (importedConnection == null) return;
importedConnection.Name = Path.GetFileNameWithoutExtension(fileName);
destinationContainer.AddChild(importedConnection);
}
}
}

Some files were not shown because too many files have changed in this diff Show More