Compare commits

..

485 Commits

Author SHA1 Message Date
Dimitrij
a908a783be lib update 2025-01-24 13:25:58 +00:00
Dimitrij
1ae9960e82 lib update 2025-01-14 14:54:54 +00:00
Dimitrij
5aa8425209 Lib updates 2024-12-23 14:27:58 +00:00
Dimitrij
b794aa8089 update puttyng to ver. 0.82 2024-12-23 14:09:07 +00:00
Dimitrij
e8bb0bffaa lib upgrade + update
Some version should be not upgraded due .net limitation, what will be done once .net will be upgraded
2024-11-23 15:51:32 +00:00
Dimitrij
e1e0661f25 lib upgrade 2024-11-23 13:12:36 +00:00
Dimitrij
7848c83c72 lib updates 2024-11-23 12:50:16 +00:00
Dimitrij
2de37ed9c7 Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2024-11-21 11:17:24 +00:00
Dimitrij
425b433cd2 lib updates 2024-11-21 11:17:15 +00:00
Dimitrij
44e07e3cdb Merge pull request #2652 from kursataktas/mremoteng-guru
Introducing mRemoteNG Guru on Gurubase.io
2024-11-12 21:46:23 +00:00
Kursat Aktas
689f882104 Introducing mRemoteNG Guru on Gurubase.io
Signed-off-by: Kursat Aktas <kursat.ce@gmail.com>
2024-11-13 00:17:19 +03:00
Dimitrij
207beaee12 Lib updates + small changes 2024-11-12 11:47:47 +00:00
Dimitrij
bd21b85de7 Lib update 2024-11-01 11:17:54 +00:00
Dimitrij
2e7579cac5 Moved all language files into Language folder. To keep root folder clean
On language change new language will not apply, need more work on that later
2024-10-18 15:40:50 +01:00
Dimitrij
ec63812af0 lib update 2024-10-18 11:02:27 +01:00
Dimitrij
8dfe1a22c9 Moved packages dll's into Assembly folder to keep root folder cleaner 2024-10-18 10:58:49 +01:00
Dimitrij
c0e3f547ec Introducing Local settings manager class and example of default settings json schema
Idea to create json file with defaults settings, what will be automatically loaded into new local settings db at creating process.
Group 'default' means its comes from json file and if user change it later - we will have user id there, that allow us to have different settings for different users but at same time keeping "original" ones in case user decide to reset.

What one of the basic steps of transition all settings into local db. Once application is running - will be possible to import already existing settings into local db, as well as switch where to have save them: to windows registery or to local db.

Settings file also could be encrypted, and file will be locked on current machine, that allow all users to use it, but not allow to transfer it to another machine. (If they do it will not work)
mR don't use any hard-coded passwords for such, only hdd id + machine name (what is hashed by SHA256) so couldn't be traceable from memory dump. Later will be possible to lock by user with own password, but that will required to enter on every start as mR will not save it.
2024-10-16 11:37:31 +01:00
Dimitrij
dafb1b364c lib updates 2024-10-15 13:41:41 +01:00
Dimitrij
7d1b9d8635 Merge pull request #2643 from xRushG/asyncRegSetting
Registry Settings: enhancements and new settings implementation
2024-10-01 22:51:52 +01:00
xRushG
8041f58ffd feat: Add registry settings for Security Page and encryption key generator
- Added new registry settings for managing security-related configurations on the Security Page.
- Implemented an encryption key generator to securely generate a password for store in the registry.
- Passwords are placed encrypted on the clipboard for 30 seconds.
- Update documents and finalize comments
2024-10-01 16:02:59 +02:00
xRushG
d018fdbace feat: Add registry settings for Connections and SQL Server settings
- Added registry settings for the Connection Page.
- Added registry settings for the SQL Serverl Page.
- All settings implemented with async registry logic
- Comment unused settings for Connection Page
- Update documents
2024-10-01 12:35:34 +02:00
xRushG
489602ffb3 feat: Add registry settings forTabs and Panel settings
- Added registry settings for the Tabs and Panel Page.
- All settings implemented with async registry logic
- Comment unused settings
- Update documents
2024-10-01 12:32:16 +02:00
xRushG
600ba73f5f Removed the deprecated SaveConnectionsOnExit option from the registry settings related to startup and exit. 2024-10-01 12:29:01 +02:00
xRushG
88600ae6df feat: Add registry settings for Notification Page, Appearance Page, and Startup and Exit Page with async logic
- Added registry settings for the Notification Page.
- Added registry settings for the Appearance Page.
- Added registry settings for the Startup and Exit Page.
- All settings implemented with async registry logic
2024-10-01 12:27:33 +02:00
xRushG
5e3bce9a92 refactor: Apply the async logic for Credentials and Updates registry settings, remove obsolete code
- Moved async registry loading logic to OptRegistryCredentialsPage and OptRegistryUpdatesPage to improve structure and maintainability.
- Removed obsolete logic and redundant code that is no longer needed due to the new asynchronous architecture.
- Enhanced settings management to take advantage of the just-in-time loading mechanism.
- Improved performance by ensuring registry settings are loaded asynchronously right before they are needed by the UI or application logic, minimizing unnecessary loads.
- Update documents
2024-10-01 12:19:41 +02:00
xRushG
9b28b7057d feat: Introduce RegistryLoader class with Lazy<T> and async registry settings loading
- Added the RegistryLoader class to handle loading of registry settings.
- Implemented Lazy<T> for singleton pattern to ensure only one instance of RegistryLoader is created.
- Added support for asynchronous loading of registry settings to improve performance in async workflows.
- Designed the class with thread safety in mind, leveraging ConcurrentDictionary for storage.
- Included logging of debug messages to track the load process and timing.
2024-10-01 12:08:52 +02:00
Dimitrij
0a17220446 lib updates 2024-09-29 17:05:51 +01:00
Dimitrij
7684b19d13 lib updates 2024-09-18 10:32:34 +01:00
Dimitrij
47baf3b662 fix case 2024-08-28 11:35:56 +01:00
Dimitrij
bc5e2b9437 add project line name 2024-08-28 11:29:40 +01:00
Dimitrij
cf0ef6d9d0 remove older schemes from distribution 2024-08-28 09:54:49 +01:00
Dimitrij
b6f3047f82 lib updates 2024-08-28 09:39:02 +01:00
Dimitrij
0a418561d7 Merge pull request #2629 from ianselmi/v1.77.3-dev
Made the specflow compatible with net6
2024-08-27 11:29:10 +01:00
ianselmi
6da79a41df made the specflow compatible with net6 2024-08-18 10:12:16 +02:00
Dimitrij
3ddeac6c74 lib update 2024-08-05 13:35:58 +01:00
Dimitrij
98f97c442d fix build number calculation 2024-08-05 13:33:50 +01:00
Dimitrij
472d33ba12 Merge pull request #2550 from Risae/v1.77.3-dev
CI: Add GitHub Actions workflow
2024-08-01 16:08:44 +01:00
Dimitrij
d906cfcc1e lib updates 2024-07-31 10:24:44 +01:00
Dimitrij
0f0d8bdcdb Merge pull request #2623 from magriggs/issue-2622
issue-2622: mRemoteNGInstaller - remove explicit PuTTyNG fragment, as…
2024-07-30 13:09:31 +01:00
23439176+magriggs@users.noreply.github.com
005b2485f6 issue-2622: mRemoteNGInstaller - remove explicit PuTTyNG fragment, as PuttyNG is copied implicitly by FilesFragment. Fixes #2622. 2024-07-30 09:07:55 +08:00
Dimitrij
ffd90bed70 Merge pull request #2621 from magriggs/issue-726
Issue 726 - SecureString for ConnectionInfo.Password
2024-07-29 09:05:05 +01:00
23439176+magriggs@users.noreply.github.com
70264a24ab Fix null password handling 2024-07-28 21:30:10 +08:00
23439176+magriggs@users.noreply.github.com
ad3a37fde3 Move ConnectionInfo.Password to use SecureString. Only decrypt when required for connecting. Update tests to skip tests of password values where necessary. 2024-07-28 19:56:36 +08:00
Dimitrij
9dac0eeaac Updating due https://github.com/mRemoteNG/mRemoteNG/issues/243 2024-07-16 20:55:18 +01:00
Dimitrij
8dac393bb4 libs update 2024-07-10 10:26:50 +01:00
Dimitrij
287d1a5575 Merge pull request #2611 from Wolvverine/v1.77.3-dev
correct registry path
2024-07-05 22:31:42 +01:00
Michał Panasiewicz
20278e0e53 correct registry path 2024-07-05 20:50:41 +02:00
Dimitrij
a3c3ec8c5c lib updates 2024-06-20 10:22:13 +01:00
Dimitrij
3039b25a66 versioning fix 2024-06-11 14:11:52 +01:00
Dimitrij
0b7ce92af8 libs update 2024-06-11 11:36:17 +01:00
Dimitrij
e3ec521a1d Merge pull request #2601 from xRushG/RegSettings
Enhancement of Registry Settings capabilities
2024-06-11 11:21:39 +01:00
xRushG
88999263a7 Finalize Update Options Page
This commit finalizes the Update Options Page with the following changes:
- Removed unnecessary methods due to new features of the registry handler class.
- Updated and added comments to improve code readability and understanding.
- Implemented UI optimizations such as disabling the frequency control and showing "Never" in the combobox.
- Enabled the password storing ability, making it functional but not yet usable.
- Applied some code optimizations for improved efficiency.
2024-06-11 10:07:33 +02:00
xRushG
9687ccd01d Finalize Credentials Options Page
This commit finalizes the Credentials Options Page with the following changes:
- Updated and added comments to improve code readability and understanding.
- Enabled the password storing ability, making it functional but not yet usable.
- Applied some code optimizations for improved efficiency.
2024-06-11 09:44:20 +02:00
xRushG
ca62502499 Update dependent registry options settings to use new WindowsRegistryEntry class. 2024-06-11 09:08:37 +02:00
xRushG
8b3a68f58e Removing all unnecessary old files 2024-06-11 09:07:04 +02:00
xRushG
08eb35a360 Refactor registry settings interaction classes.
This commit refactors the registry settings interaction classes by consolidating the multiple entry classes for int, string, bool, etc., into a single class that supports multiple data types. This change simplifies the code and enhances readability.

- Consolidate multiple entry classes (int, string, bool, etc.) into a single class
- Introduce WindowsRegistryEntry<T> to support multiple data types
2024-06-11 09:03:34 +02:00
Dimitrij
dec82b41f2 lib update 2024-06-10 21:14:52 +01:00
Dimitrij
b9ab5c76f7 Merge pull request #2597 from CodeCasterNL/v1.77.3-dev
Remember the opened connection file on relaunch
2024-06-07 13:33:53 +01:00
Dimitrij
6de0c536e8 Merge pull request #2591 from tecxx/develop-orig
add Clickstudios Passwordstate API connector
2024-06-07 13:32:56 +01:00
tecxx
3f0aca97bf Merge branch 'mRemoteNG:v1.77.3-dev' into develop-orig 2024-06-06 09:17:37 +02:00
Dimitrij
ccb51a2603 Merge pull request #2599 from xRushG/fix2592
Fix for #2592 compiler issue
2024-05-30 10:03:35 +01:00
xRushG
99d9e70921 Fix #2592: Prevent GeneralAppInfo.ApplicationVersion from being set in Unit Tests. Not needed for tests. 2024-05-29 15:20:56 +02:00
CodeCasterNL
b8b54271dd Save ConnectionFilePath, fix typo in identifier 2024-05-25 17:13:11 +02:00
Dimitrij
49a1549e5a Merge pull request #2596 from xRushG/OpenLogBug
Log File Opening Fails on Notification Settings Page
2024-05-24 09:12:38 +01:00
xRushG
b3f936c194 - Added my missed comment for outlined LocalSettingsManager
- Fixed a bug that occurred when trying to open the log file on the Notification settings page.
- Removed unnecessary file association when user tries to open the log file on the Notification page. The "choose file" dialog handles the extension as it is fixed on .log
- Implemented fallback to open file explorer to the specified file location if mRem fails to open the log file.
2024-05-23 21:29:35 +02:00
Robert Rostek
f3b10d4c20 update credential vault documentation 2024-05-04 18:05:28 +02:00
Robert Rostek
94b17037d0 add Clickstudios Passwordstate API connector 2024-05-04 13:49:17 +02:00
Dimitrij
83f3846ce6 lib updates
correct build number calculations - now its days from last release + hour + minute of build
some changes to migrate to json schema + preparation of using db to save settings
2024-05-03 14:40:52 +01:00
Dimitrij
711a751117 Merge pull request #2589 from xRushG/Fix-2540
LocalSettingsManager class implementation is missing (#2540)
2024-04-29 13:56:53 +01:00
xRushG
e013e32792 Fix #2540
LocalSettingsManager class implementation is missing
2024-04-29 14:34:36 +02:00
Dimitrij
6e64b09256 Merge pull request #2571 from mRemoteNG/dependabot/nuget/mRemoteNG/System.Data.SqlClient-4.8.6
Bump System.Data.SqlClient from 4.8.5 to 4.8.6 in /mRemoteNG
2024-03-11 14:31:41 +00:00
dependabot[bot]
e32d268587 Bump System.Data.SqlClient from 4.8.5 to 4.8.6 in /mRemoteNG
Bumps [System.Data.SqlClient](https://github.com/dotnet/corefx) from 4.8.5 to 4.8.6.
- [Release notes](https://github.com/dotnet/corefx/releases)
- [Commits](https://github.com/dotnet/corefx/commits)

---
updated-dependencies:
- dependency-name: System.Data.SqlClient
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 14:30:32 +00:00
Dimitrij
05635dec93 Merge pull request #2559 from xRushG/v1.77.3-dev
Configuration via windows registry (updates and credentials)
2024-02-19 18:20:44 +00:00
Schmitti91
e694923d33 Merge branch 'v1.77.3-dev' of https://github.com/xRushG/mRemoteNG into v1.77.3-dev 2024-01-28 10:16:30 +01:00
Schmitti91
7322683465 Fix AppVeyor CA2200 and optimize CreateOrSetRegistryValue method 2024-01-28 10:15:34 +01:00
Schmitti91
64688f3512 Add documentation page for registry settings (credentials) 2024-01-27 21:01:38 +01:00
Schmitti91
762760af2f Adding the configuration capability for the Credentials page through the Windows Registry. 2024-01-27 20:55:12 +01:00
Schmitti91
48e6881401 Merge branch 'v1.77.3-dev' of https://github.com/xRushG/mRemoteNG into v1.77.3-dev 2024-01-26 21:13:22 +01:00
Schmitti91
95c958136f Add Dokupage for registry settings (update) 2024-01-26 21:12:24 +01:00
Schmitti91
53a6be0f82 remove placeholder code by commenting it out in its current state. (needed for later commit) 2024-01-26 21:07:04 +01:00
Schmitti91
0b4d95be1c Renameing the common setting name "CheckForUpdate Asked" to "AllowPromptForUpdates Preference" for consistency in naming conventions. 2024-01-23 21:43:29 +01:00
Schmitti91
aad3948475 make ProxyAuthPass not usable, placeholder code 2024-01-23 16:24:28 +01:00
Schmitti91
093370081e Add capability to set Update Settings via Registry
This commit enhances the functionality of the WindowsRegistryAdvanced class to provide the capability to set up Update Settings through the Windows Registry. The changes include the addition of methods or modifications that allow users to configure update-related settings using the Registry.
2024-01-23 16:14:36 +01:00
Schmitti91
33426ceee9 Add GetDwordValue method to WindowsRegistryAdvanced
This commit introduces the GetDwordValue method in the WindowsRegistryAdvanced class, allowing the retrieval of DWORD values from the Windows Registry. The implementation uses int.TryParse for efficient parsing and handles default values gracefully.
2024-01-23 14:29:07 +01:00
Schmitti91
5f5700b948 extend Registry handling capabilities in preparation for Registry settings
Enhanced the functionality of Registry handling in preparation for managing Registry settings
2024-01-23 12:54:43 +01:00
Risae
802e9abfef CI: Create build-x86_64.yml 2024-01-11 17:46:08 +01:00
Dimitrij
45149d6547 Merge pull request #2549 from magriggs/issue-2487
Issue 2487
2024-01-05 12:07:05 +00:00
23439176+magriggs@users.noreply.github.com
595681a982 Remove boilerplate code 2024-01-05 14:28:33 +08:00
23439176+magriggs@users.noreply.github.com
e1cac723d6 First commit of SecureCRT import functionality. 2024-01-05 13:59:36 +08:00
Dimitrij
683f84f053 Merge pull request #2545 from magriggs/issue-1308
Some fixes for issue mRemoteNG#1308. Inspired by dockpanelsuite/dockp…
2024-01-03 13:15:15 +00:00
23439176+magriggs@users.noreply.github.com
b6d882a06f Some fixes for issue mRemoteNG#1308. Inspired by dockpanelsuite/dockpanelsuite#526 (comment). Main change is to enable minimize and close buttons 2024-01-03 20:39:36 +08:00
Dimitrij
9d4bfbcb1c lib updates 2023-12-13 21:56:25 +00:00
Dimitrij
b563747046 Merge pull request #2534 from jcefoli/v1.77.3-dev
Handle case where COM object becomes separated from RCW
2023-12-13 21:47:47 +00:00
Joe Cefoli
1ebd0a430f Remove unnecessary line break 2023-12-13 14:42:13 -05:00
Joe Cefoli
ef4366be4d Handle case where COM object becomes sepatated from Runtime Callable Wrapper (resizing main window after disconnecting from fit to panel session) 2023-12-13 14:34:33 -05:00
Dimitrij
484283fef8 Merge pull request #2533 from tommwq/v1.77.3-dev
support local PowerShell connection
2023-12-13 11:02:10 +00:00
tommwq
b546a48a5f support local PowerShell connection 2023-12-12 23:26:58 +08:00
Dimitrij
51bd64d005 Merge pull request #2530 from BestiaPL/v1.77.3-dev
Update Language.pl.resx
2023-12-11 10:32:02 +00:00
Andrzej Rudnik
2c65d12b92 Update Language.pl.resx 2023-12-05 21:17:59 +01:00
Dimitrij
c43a3b5115 Merge pull request #2529 from xRushG/v1.77.3-dev
Pre-build event powershell code update #2524
2023-12-04 17:47:04 +00:00
Schmitti91
515778b13d Pre-build event powershell code update/fix 2023-12-02 21:59:56 +01:00
Dimitrij
bb4b9ce9e7 Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2023-11-29 09:42:06 +00:00
Dimitrij
1d6707fecd #2528
fix typo
2023-11-29 09:41:56 +00:00
Dimitrij
5b2a46ed91 Merge pull request #2523 from zeze0556/v1.77.3-dev
fixed mysql error
2023-11-20 09:27:46 +00:00
unknown
24fa97da4c fixed mysql error 2023-11-20 10:21:59 +08:00
Dimitrij
b0109c2581 lib update 2023-11-10 11:29:18 +00:00
Dimitrij
20f237ec4c Merge pull request #2519 from BestiaPL/v1.77.3-dev
Update Language.pl.resx
2023-11-04 23:38:09 +00:00
Andrzej Rudnik
6ea535ae29 Update Language.pl.resx 2023-11-04 16:59:46 +01:00
Dimitrij
57ca8a3eee Merge pull request #2514 from Schmitti91/v1.77.3-dev
Update Registry interaction capabilities.
2023-10-24 13:33:12 +01:00
Schmitti91
307ea42a0f Cleanup of references and correction of comments. 2023-10-23 07:58:13 +02:00
Schmitti91
ac41c04f67 mRemoteNG tools WindowsRegistry has been revised and the Windows Registry reading capabilities have been expanded in preparation for working with Registry Keys options. The "RegistryHive" enum was deleted because it is contained in "Microsoft.Win32". 2023-10-22 12:18:42 +02:00
Dimitrij
b3496e79de lib update 2023-10-17 16:05:09 +01:00
Dimitrij
4f7aa755e3 Merge pull request #2508 from RFC1920/v1.77.3-dev-putty
bugfix for Putty import
2023-10-17 09:30:56 +01:00
Dimitrij
d3af50f009 lib updates 2023-10-16 13:59:04 +01:00
RFC1920
ba77a7b7c0 Update Putty registry import to fix connection name, username, and port setting 2023-10-16 05:32:34 -05:00
Dimitrij
666a0e1537 Merge pull request #2507 from RFC1920/v1.77.3-dev-putty
Add Putty registry import
2023-10-16 11:32:21 +01:00
RFC1920
96f4307910 Add Putty registry import 2023-10-16 04:50:35 -05:00
Dimitrij
81e997eb60 Merge pull request #2506 from Schmitti91/v1.77.3-dev
Enhancing the PowerShell Connection.
2023-10-16 09:56:50 +01:00
Schmitti91
7132c6e7c2 Removing the "-NoProfile" option from PowerShell.exe parameters. 2023-10-15 21:48:01 +02:00
Schmitti91
cb01e92fe2 Enhancing the PowerShell Connection. The PowerShell script is designed to simplify multiple login attempts to a remote host using user-provided credentials, or not. In addition, granular error handling has been added to prevent errors, such as the conversion of empty or null strings to SecureString. Furthermore, hostname, username, and password are now passed as parameters in the PowerShell script block (#2195). 2023-10-15 21:13:36 +02:00
Dimitrij
2c9c512d74 Merge pull request #2503 from WojciechNagorski/new-sshnet
Update SSH.NET to 2023.0.0
2023-10-11 13:55:07 +01:00
Wojciech Nagórski
8d05c9040b Update SSH.NET to 2023.0.0 2023-10-11 14:45:01 +02:00
Dimitrij
4e994fb202 Merge pull request #2502 from BestiaPL/v1.77.3-dev
Updated Polish translation
2023-10-09 16:45:55 +01:00
Dimitrij
ab2a778694 Merge pull request #2496 from tecxx/develop-orig
use pwfile instead of cleartext password for putty connections
2023-10-09 09:27:00 +01:00
Andrzej Rudnik
115375820d Update Language.pl.resx 2023-10-08 20:51:22 +02:00
Andrzej Rudnik
8cb753916b Update Language.pl.resx 2023-10-05 23:36:45 +02:00
Robert Rostek
ec3a0cee9b remove unnecessary usings 2023-10-03 10:55:27 +02:00
Robert Rostek
13f8f82537 update changelog 2023-10-03 10:53:34 +02:00
Robert Rostek
54544dd2aa use pwfile instead of pw for puttyng 2023-10-03 10:52:41 +02:00
Dimitrij
ac99115424 Merge pull request #2482 from simonai1254/fix-2195
Fix 2195
2023-08-31 16:37:41 +01:00
Simon Monai
d6c6038193 Update CHANGELOG.md 2023-08-30 23:55:40 +02:00
Simon Monai
c97b84e4a2 Quote variable for #2195
In order to interpret variable as string and not arbitrary code
2023-08-30 23:46:42 +02:00
Dimitrij
6698ec0b06 Update README.md 2023-08-02 13:14:28 +01:00
Dimitrij
290b1c4de5 Update README.md 2023-08-02 13:14:02 +01:00
Dimitrij
d497a50727 Update README.md 2023-08-02 13:13:23 +01:00
Dimitrij
2837c91f5e Update README.md 2023-08-02 13:12:34 +01:00
Dimitrij
9a717f28c6 upd 2023-08-02 13:03:14 +01:00
Dimitrij
e416c2c9c9 upd 2023-08-02 13:01:21 +01:00
Dimitrij
331cdaa6ce upd 2023-08-02 12:54:27 +01:00
Dimitrij
aa329b1a9a upd 2023-08-02 12:51:30 +01:00
Dimitrij
16c080942d upd table 2023-08-02 12:47:15 +01:00
Dimitrij
4acc111b3d upd 2023-08-02 12:39:29 +01:00
Dimitrij
afe453d371 upd 2023-08-02 12:37:25 +01:00
Dimitrij
e93c120dac update table 2023-08-02 12:34:16 +01:00
Dimitrij
2f701458f4 mistype fix 2023-08-02 12:27:30 +01:00
Dimitrij
6a35961dfb Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2023-08-02 12:24:16 +01:00
Dimitrij
1a90095ba3 add add-ons list 2023-08-02 12:24:05 +01:00
Dimitrij
5dc87213b5 Merge pull request #2462 from jurepurgar/rdgw-access-token
Support for gatewayaccesstoken
2023-07-18 16:24:49 +01:00
Jure Purgar
ff8044b09b Delete resx 2023-07-18 16:23:48 +02:00
Jure Purgar
f1fa40f6fd Initial implementation of gatewayaccesstoken support 2023-07-18 16:07:06 +02:00
Dimitrij
9bc5b62828 enable signing 2023-07-15 22:44:26 +01:00
Dimitrij
5aae586812 lib update 2023-07-15 22:11:03 +01:00
Dimitrij
bc5e5be42e update cert 2023-07-15 22:06:47 +01:00
Dimitrij
be68573378 lib updates 2023-07-05 14:33:39 +01:00
Dimitrij
cf0bc3d270 fix links 2023-07-05 12:00:14 +01:00
Dimitrij
d5773eb2e9 doc update
adding image, fix link
2023-07-05 09:29:19 +01:00
Dimitrij
50523e5b67 update docs 2023-07-04 15:59:48 +01:00
Dimitrij
feeca86c6a small fix 2023-06-21 21:03:57 +01:00
Dimitrij
a2e2671483 update links 2023-06-21 20:55:35 +01:00
Dimitrij
0b4d6ef8ef update index 2023-06-21 20:48:23 +01:00
Dimitrij
bdf0e532a5 #2447
add screenshots for themes
2023-06-21 20:46:37 +01:00
Dimitrij
d8cf72d09b Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2023-06-21 12:08:25 +01:00
Dimitrij
6c3ec8f058 update puttyng to actual version 2023-06-21 12:08:07 +01:00
Dimitrij
fbd9b76a09 Merge pull request #2449 from simonai1254/update_documentation
Improve PuttyNG Issue Description
2023-06-21 09:41:55 +01:00
Simon Monai
bee216220a Improve PuttyNG Issue Description
Update PuttyNG Documentation
Do not recommend insecure changes
2023-06-21 09:29:05 +02:00
Dimitrij
4ca7f34989 code corrections 2023-06-16 19:24:33 +01:00
Dimitrij
51015e6b51 lib update 2023-06-16 19:24:00 +01:00
Dimitrij
46100d6d94 lib update 2023-06-08 10:58:05 +01:00
Dimitrij
c94eac3863 Merge pull request #2416 from BlueBlock/streamline_build_scripts_for_appveyor
modify build scripts for appveyor
2023-04-07 15:19:44 +01:00
BlueBlock
965b0e5b6d Update update_and_upload_assemblyinfocs.ps1
enable assemblyinfo.cs update when not (CI)
2023-03-31 11:27:49 -04:00
BlueBlock
000cea1dda test repo name 2023-03-31 11:16:40 -04:00
BlueBlock
6d4d60e3b1 Update postbuild.ps1
fix if matching
2023-03-31 11:02:49 -04:00
BlueBlock
9957282bfa Update postbuild_installer.ps1
disable assemblyinfo.cs update temporarily
2023-03-31 11:01:48 -04:00
BlueBlock
9b603dd956 Update postbuild.ps1
check variables
2023-03-31 10:10:10 -04:00
BlueBlock
759ee55780 modify build scripts for appveyor 2023-03-31 09:14:35 -04:00
Dimitrij
93b2edb5a5 Merge pull request #2415 from BlueBlock/fix_window_restore_resize
Update RdpProtocol8.cs
2023-03-28 14:59:36 +01:00
BlueBlock
4d49c14bb9 Update RdpProtocol8.cs
resize when restored from minimize
2023-03-28 08:49:52 -04:00
Dimitrij
6398808a0a corrected slashes 2023-03-25 22:29:53 +00:00
Dimitrij
64e20f3665 update 2023-03-25 21:45:22 +00:00
Dimitrij
3b542d8868 excluding AssemblyInfo from accidental update in repo 2023-03-25 21:40:42 +00:00
Dimitrij
d5af0e43a5 add condition 2023-03-25 18:44:40 +00:00
Dimitrij
7a8ef87dc7 run string as a command 2023-03-25 18:33:50 +00:00
Dimitrij
7a5293cde3 add wrapping ' 2023-03-25 18:24:08 +00:00
Dimitrij
26400631a0 replace " by ' to avoid path brake 2023-03-25 18:20:46 +00:00
Dimitrij
97a5c584be increase build number only then release in appveyor 2023-03-25 18:16:03 +00:00
Dimitrij
0700b6573c Merge pull request #2413 from BlueBlock/fix_running_of_unit_tests
Fix running of unit tests
2023-03-25 17:17:32 +00:00
Dimitrij
d8f66a4188 Merge pull request #2412 from BlueBlock/update_build_script
Update find_vstool.ps1
2023-03-25 16:39:49 +00:00
BlueBlock
c22971cf54 add unit test references
unit tests were not able to run since the refs were missing
2023-03-25 12:08:52 -04:00
BlueBlock
9d2dd5dd93 remove variable not used 2023-03-25 12:08:02 -04:00
BlueBlock
c19d512631 Update Logger.cs
code formatting and using adjustment
2023-03-25 12:07:28 -04:00
BlueBlock
9efda5fef3 lib update 2023-03-25 12:06:53 -04:00
BlueBlock
a27cdcfdb5 Update find_vstool.ps1
Do not attempt to verify the fingerprint since it changes often. It seems to be overkill to check it.
2023-03-25 12:03:37 -04:00
Dimitrij
5667a07b25 Merge pull request #2411 from BlueBlock/fix_log4net_logging
fix log4net logging
2023-03-25 07:04:21 +00:00
BlueBlock
7d0bd85466 fix log4net logging 2023-03-24 14:49:27 -04:00
Dimitrij
4f269442a6 Merge pull request #2410 from BlueBlock/fix_notificiation_bug_introduced
Fix notification bug introduced
2023-03-24 18:09:14 +00:00
BlueBlock
16d182fca2 Update NotificationPanelMessageWriter.cs
fix notification bug, text not displaying
2023-03-24 11:53:05 -04:00
BlueBlock
6d1a206cc9 code formatting 2023-03-24 11:51:33 -04:00
Dimitrij
d8a7fdbf4f lib update 2023-03-24 15:22:10 +00:00
Dimitrij
516b365ebc Merge pull request #2401 from BlueBlock/fix_options_ui_sizing
options pages size and position fixes
2023-03-24 07:16:15 +00:00
BlueBlock
6da6a5ede9 options pages size and position fixes
- options page was too small
- some tabs needed adjustment for spacing
2023-03-24 03:02:27 -04:00
Dimitrij
67244f1957 Merge pull request #2400 from BlueBlock/fix_database_columns_and_some_tests
Fix database columns and some tests
2023-03-24 06:08:48 +00:00
BlueBlock
0a304e9a03 add missing objects needed in tests 2023-03-24 01:35:37 -04:00
BlueBlock
05154af606 code formatting 2023-03-24 01:34:51 -04:00
BlueBlock
7454b15ec9 adjust app to use 2.8 xsd schema 2023-03-24 01:33:30 -04:00
BlueBlock
38f435f639 Update RemoteDesktopConnectionDeserializer.cs
add missing serializer
2023-03-24 01:32:21 -04:00
BlueBlock
5f38ec7210 Update SqlVersion29To30Upgrader.cs
add missing DB column UserViaAPI
2023-03-24 01:31:41 -04:00
BlueBlock
ca143bf969 Update mremoteng_confcons_v2_8.xsd
inherit should be boolean
2023-03-24 01:29:39 -04:00
BlueBlock
402e5c3665 Create mRemoteNG.lutconfig
live test runner config file
2023-03-24 01:27:52 -04:00
BlueBlock
290dbcc3ed Update SqlDatabaseMetaDataRetriever.cs
- add missing db column UserViaAPI
- formatting and remove commented code
2023-03-24 01:26:44 -04:00
Dimitrij
a7749c70e7 Merge pull request #2399 from BlueBlock/fix_database_use
Fix database use
2023-03-23 22:01:40 +00:00
BlueBlock
9f0cfd5f22 Update CsvConnectionsDeserializerMremotengFormatTests.cs
fix test
2023-03-23 17:47:27 -04:00
BlueBlock
f47434c09e Merge remote-tracking branch 'upstream/v1.77.3-dev' into fix_database_use 2023-03-23 17:44:39 -04:00
BlueBlock
795a4b571e Update AbstractConnectionRecord.cs
defaults set not needed
2023-03-23 17:26:11 -04:00
BlueBlock
f71727e05e Update SqlConnectionsUpdateChecker.cs
code formatting
2023-03-23 16:34:37 -04:00
BlueBlock
d2fa8e86b1 Update DatabaseConnectorFactory.cs
add TODO
2023-03-23 16:34:15 -04:00
BlueBlock
23dcd9d30f Update MySqlDatabaseConnector.cs
add TODO
2023-03-23 16:33:55 -04:00
BlueBlock
d6890c6ecd refactor to newer c# code statements 2023-03-23 16:32:14 -04:00
BlueBlock
b1a6ba78d4 fix database upgrades
- fix tested db upgrading for each version
- fix version check for 3.0
2023-03-23 16:31:28 -04:00
BlueBlock
e6abe3f3a1 simple code reformatting 2023-03-23 16:30:04 -04:00
BlueBlock
6c32540ecb refactor class mssql to sql
refactor class mssql to sql
code formatting
refactor null check
use db TRUNCATE instead of DELETE (mysql warns about mass DELETE)
2023-03-23 16:27:17 -04:00
BlueBlock
951ad5cd0a Update ConnectionsService.cs
use universal time for db time
2023-03-23 16:23:45 -04:00
BlueBlock
5f56477123 Update MiscTools.cs
- add GetBooleanValue method for getting bool from databases
- use universal time for db time
2023-03-23 16:22:38 -04:00
BlueBlock
3180f38a1d Update AbstractConnectionRecord.cs
fix to add redirectdiskdrives default value
2023-03-23 16:21:34 -04:00
BlueBlock
07f15993e5 Update SettingsLoader.cs
refactor null check
2023-03-23 16:20:55 -04:00
BlueBlock
fc8e9c7689 rename class from mssql to sql
This class is not dedicated to mssql but is used by all sql databases. Rename to reflect this.
2023-03-23 16:20:01 -04:00
BlueBlock
7cb20b9e28 Update Runtime.cs
use universal time for stored DB time since users can span time zones
2023-03-23 15:16:28 -04:00
BlueBlock
ffc2351d64 Update ConnectionsFileInfo.cs
latest db version should be 3.0
2023-03-23 15:15:36 -04:00
Dimitrij
f10fec61ac Merge pull request #2398 from BlueBlock/fix_deserializer_test
Update CsvConnectionsDeserializerMremotengFormatTests.cs
2023-03-23 16:39:01 +00:00
BlueBlock
e41200067c Update CsvConnectionsDeserializerMremotengFormatTests.cs
fix test, missing changes needed for RedirectDrives PR
2023-03-23 12:18:28 -04:00
Dimitrij
30f6d3dde7 Merge pull request #2397 from BlueBlock/fix_remove_exception_test
Update ProgramRoot.cs
2023-03-21 20:59:32 +00:00
BlueBlock
7f9dcb11f0 Update ProgramRoot.cs
remove exception test
2023-03-21 16:49:59 -04:00
Dimitrij
d5fa2f62c0 Merge pull request #2396 from BlueBlock/fix_load_misssing_localconnectionproperties_xml_file
Fix load misssing localconnectionproperties xml file
2023-03-21 20:44:59 +00:00
Dimitrij
fa7967190d Merge pull request #2395 from integratorbit/v1.77.3-dev
Improve Disk Drive Redirection
2023-03-21 20:33:33 +00:00
BlueBlock
20e04c0d75 resolve conflicts 2023-03-21 16:04:32 -04:00
BlueBlock
d5eea9db20 Merge remote-tracking branch 'upstream/v1.77.3-dev' into pr/2395 2023-03-21 16:00:50 -04:00
BlueBlock
8cb598ce25 Update FileDataProvider.cs
fix creation of file LocalConnectionProperties.xml if it does not exist
2023-03-21 15:26:01 -04:00
BlueBlock
f3a57d7f23 move filename value LocalConnectionProperties.xml into settings file 2023-03-21 15:25:32 -04:00
BlueBlock
14e47def9d Update SqlDatabaseMetaDataRetriever.cs
fix exception casting int64 to int32, unbox and cast to int
2023-03-21 15:24:49 -04:00
Dimitrij
7573081d7a Merge pull request #2394 from BlueBlock/fix_rdp_operation
Fix RDP operation
2023-03-21 18:48:56 +00:00
Dimitrij
aaca581324 Merge pull request #2393 from BlueBlock/do_not_display_splash_on_taskbar
Do not show splash on taskbar
2023-03-21 18:06:52 +00:00
BlueBlock
43c2797a51 Update ProgramRoot.cs
do not show splash on taskbar
2023-03-21 13:56:55 -04:00
integratorbit
da6bbf65f0 Improve Disk Drive Redirection 2023-03-21 14:31:15 -03:00
BlueBlock
7d0cbf423e add resize fallback
Add resize fallback if the target OS fails to resize using the most current method. (for example, this occurs with Server2008R2)
2023-03-21 13:03:21 -04:00
BlueBlock
d94eab71da Update RdpProtocol8.cs
remove test code
2023-03-21 12:58:58 -04:00
BlueBlock
b0a8ccc5b6 multiple RDP issues fixed
- fix scale of RDP session when moving between 4k and 1080 monitors etc.
- fix inefficient continuous RDP screen resizing while the window size is changed
- add a RDP version check before attempting to use each RDP version and properties
2023-03-21 12:23:59 -04:00
BlueBlock
5cbf1f1568 adjust method access level of common protocol methods 2023-03-21 12:22:20 -04:00
BlueBlock
22cde74db2 Delete RdpProtocol6.cs
class renamed to RdpProtocol
2023-03-21 12:07:18 -04:00
BlueBlock
cdc3759c04 Update Optional.cs
- use more efficient empty assignments
- use string interpolation
- adjust method access to minimal needed
2023-03-21 12:06:38 -04:00
BlueBlock
38abc8a167 Update NotificationPanelMessageWriter.cs
check for object _messageWindow before using it to avoid an exception
2023-03-21 12:04:52 -04:00
BlueBlock
7c7c7086ce Update RdpProtocolFactory.cs
class RdpProtocol6 renamed to RdpProtocol
2023-03-21 12:04:03 -04:00
BlueBlock
50daf64025 Update ProtocolBase.cs
- add check for object _interfaceControl before using it
- adjust methods to protected where appropriate
- remove unused using
2023-03-21 12:03:28 -04:00
BlueBlock
dc38df5389 Update ConnectionsService.cs
use object initializer and remove unused using
2023-03-21 12:00:54 -04:00
BlueBlock
a46b2c9d98 Update ProtocolFactory.cs
remove unused usings
2023-03-21 11:57:34 -04:00
BlueBlock
200756361c rename base class of RDP
So it is consistent with other protocols, rename the RDP base class to not reflect a version.
2023-03-21 11:55:17 -04:00
Dimitrij
877a1e4cd0 Merge pull request #2391 from BlueBlock/add_set_devenvdir_to_prebuild
Update mRemoteNG.csproj
2023-03-20 21:16:18 +00:00
BlueBlock
a382206f5a Update mRemoteNG.csproj
add setting of DevEnvDir in prebuild
2023-03-20 16:31:22 -04:00
Dimitrij
1af7a622a6 Merge pull request #2388 from BlueBlock/fix_remove_unsused_class
Delete RDPVersions.cs
2023-03-17 17:54:50 +00:00
Dimitrij
3f95b2aa7e Merge pull request #2387 from BlueBlock/fix_exception_in_disposeinterface
Update ProtocolBase.cs
2023-03-17 17:48:43 +00:00
BlueBlock
b7871e2e04 Delete RDPVersions.cs
Class is unused and not needed.
2023-03-17 13:18:14 -04:00
BlueBlock
6b6ceda497 Update ProtocolBase.cs
check if object is disposed before working with it
2023-03-17 13:07:58 -04:00
Dimitrij
b01b8e4bc8 adjust installer script 2023-03-17 00:42:46 +00:00
Dimitrij
20f7cb1cd0 update move installer 2023-03-17 00:17:52 +00:00
Dimitrij
3e1ee0056d small amendments 2023-03-16 23:01:06 +00:00
Dimitrij
a3a851e57f Merge pull request #2383 from BlueBlock/fix_null_check_not_needed
Update PuttySessionsRegistryProvider.cs
2023-03-16 20:10:57 +00:00
BlueBlock
cf650d4318 use more efficient array 2023-03-16 16:10:01 -04:00
Dimitrij
58e7420f04 Merge pull request #2386 from BlueBlock/fix_suppress_com_warnings_on_release_build
Update mRemoteNG.csproj
2023-03-16 19:24:18 +00:00
Dimitrij
aedaf084d9 Merge pull request #2382 from BlueBlock/fix_more_CA1416_warnings
fix more CA1416 warnings
2023-03-16 19:04:47 +00:00
BlueBlock
c18f638989 Update mRemoteNG.csproj
suppress com warnings on release build, but display warnings on debug builds
2023-03-16 15:00:46 -04:00
BlueBlock
fb50cdc058 Update PuttySessionsRegistryProvider.cs
remove unreachable code
- no need to test for null, PuttySessionsKey is a const with a value set on instantiation
2023-03-16 14:35:39 -04:00
BlueBlock
68e702c344 fix more CA1416 warnings 2023-03-16 14:15:44 -04:00
Dimitrij
651b18a8b3 Merge pull request #2381 from BlueBlock/fix_obsolete_SHA1CryptoServiceProvider
Update PuttyKeyFileGenerator.cs
2023-03-16 17:11:53 +00:00
BlueBlock
2174e56407 Update PuttyKeyFileGenerator.cs
Replace use of obsolete SHA1CryptoServiceProvider with replacement call.
2023-03-16 12:59:28 -04:00
Dimitrij
ea27a4da02 Merge pull request #2380 from BlueBlock/fix_appveyor_bulk_of_changes
appveyor build automation
2023-03-16 16:50:53 +00:00
BlueBlock
321fd1162b appveyor build automation
bulk of the changes for appveyor automation
2023-03-16 11:08:27 -04:00
Dimitrij
c51f38ddc7 Merge pull request #2379 from BlueBlock/fix_appveyor
additional appveyor change
2023-03-15 19:05:36 +00:00
BlueBlock
b34cd1acc5 additional appveyor change 2023-03-15 14:51:04 -04:00
Dimitrij
43f20bb83a Merge pull request #2378 from BlueBlock/fix_adj_appveyor_build
appveyor build adjustment
2023-03-15 16:57:16 +00:00
Dimitrij
79b8ddef15 Merge pull request #2377 from BlueBlock/fix_putty_registry_watcher
Fix putty registry watcher
2023-03-15 16:49:29 +00:00
BlueBlock
2642ea0601 appveyor build adjustment
Further work to test appveyor builds
2023-03-15 12:46:37 -04:00
Dimitrij
7a2f934f6a Merge pull request #2376 from BlueBlock/add_rdp_protocol_rdc11
add rdp protocol rdc11
2023-03-15 12:49:03 +00:00
BlueBlock
f299fefcdc Update PuttySessionsRegistryProvider.cs
Remove previous fixes return statement
2023-03-15 08:22:29 -04:00
BlueBlock
209319f460 Update PuttySessionsRegistryProvider.cs
Update the latest fix for the putty registry watcher, to create the registry path if it does not exist, so the watcher is able to always run.
2023-03-15 08:14:52 -04:00
BlueBlock
5ec4f4dcea add rdp rdc11 2023-03-15 08:04:01 -04:00
Dimitrij
16f67d58d1 Merge pull request #2372 from BlueBlock/fix_exception_when_no_putty_sessions_exist
fix exception when no putty sessions exist
2023-03-14 19:32:10 +00:00
Dimitrij
542d794ab4 Merge pull request #2373 from BlueBlock/fix_exception_with_notification_on_app_closing
Update MessageFocusDecorator.cs
2023-03-14 17:03:33 +00:00
Dimitrij
ede1573b56 Merge pull request #2375 from BlueBlock/fix_exception_for_protocolbase_when_closing_app
Update ProtocolBase.cs
2023-03-14 16:24:43 +00:00
Dimitrij
bc8eec8887 Merge pull request #2374 from BlueBlock/add_rdp_protocol_rdc10
add rdc10 protocol
2023-03-14 15:35:38 +00:00
Dimitrij
59b0654b95 Merge pull request #2371 from BlueBlock/fix_installer_build_order
Update mRemoteNG.sln
2023-03-14 15:24:05 +00:00
Dimitrij
cae3477591 Merge pull request #2370 from BlueBlock/fix_file_fragments
Update FilesFragment.wxs
2023-03-14 15:22:30 +00:00
BlueBlock
6a3115b80a Update ProtocolBase.cs
Do not attempt to dispose the control if the control is already closed, closing or disposed.
2023-03-14 04:44:42 -04:00
BlueBlock
1232d7c288 add rdc10 protocol 2023-03-13 22:38:13 -04:00
BlueBlock
8e1b4c9271 Update MessageFocusDecorator.cs
do not attempt to focus the notification panel if the application is closing, otherwise exceptions will occur since _frmMain is closing
2023-03-13 20:08:48 -04:00
BlueBlock
0f8810e22a fix exception when no putty sessions exist
When putty is not installed or no sessions exist in the registry, an exception is thrown when starting the eventwatcher. Only start the eventwatcher if sessions registry path exists.
2023-03-13 18:53:00 -04:00
BlueBlock
7bbff38b6b Update mRemoteNG.sln
fix install build order by also including building mremoteng
2023-03-13 18:47:14 -04:00
BlueBlock
4a7b8fa250 Update FilesFragment.wxs
fix some dll paths and remove unused dll
2023-03-13 18:44:26 -04:00
Dimitrij
53acc94976 Merge pull request #2369 from BlueBlock/fix_find_vstool.ps1
Update find_vstool.ps1
2023-03-13 14:11:00 +00:00
BlueBlock
ab8d83b12d Update find_vstool.ps1
add vs2022 cert thunbprint
fix function which tests if a file is executable
2023-03-13 09:46:27 -04:00
Dimitrij
713a0a6174 more fixes for CA1416 2023-03-11 13:13:19 +00:00
Dimitrij
a3b9695b81 lib replace 2023-03-11 12:38:53 +00:00
Dimitrij
85dd381bd9 lib update 2023-03-11 12:21:17 +00:00
Dimitrij
05b418e9b6 more fix for Warning CA1416 2023-03-11 12:20:19 +00:00
Dimitrij
40af584afe fix for Warning CA1416 2023-03-11 01:46:11 +00:00
Dimitrij
93fe32491b couple of fixes 2023-03-10 23:00:05 +00:00
Dimitrij
4b67fcb0d4 fix splash screen version logo 2023-03-09 09:12:40 +00:00
Dimitrij
0b0c92717d fix format to avoid error on file name 2023-03-08 22:15:25 +00:00
Dimitrij
4564cafa85 more fixes for CA1416 2023-03-08 21:50:23 +00:00
Dimitrij
2e21f8d03a add form 2023-03-08 20:51:58 +00:00
Dimitrij
4336254d56 fix naming after rename 2023-03-08 20:36:00 +00:00
Dimitrij
bc5e9279a5 fix naming to be inline with Others 2023-03-08 20:28:31 +00:00
Dimitrij
1bfbb956c2 fix CA1416: Validate platform compatibility
https://learn.microsoft.com/en-gb/dotnet/fundamentals/code-analysis/quality-rules/ca1416
2023-03-08 20:23:38 +00:00
Dimitrij
b336db773c Merge pull request #2367 from BlueBlock/update_build_scripts
Update build scripts
2023-03-08 19:32:06 +00:00
BlueBlock
30e7d2424c Merge remote-tracking branch 'upstream/v1.77.3-dev' into update_build_scripts 2023-03-08 14:08:46 -05:00
kmscode
cbb8c0234d fixed a few warnings 2023-03-05 20:10:32 -05:00
kmscode
95469107fd don't need to manually editbin for large address aware on x64 targets 2023-03-05 15:37:49 -05:00
Dimitrij
ba0058c0b9 fix installer paths 2023-03-04 22:49:23 +00:00
Dimitrij
8f35afe353 fix for portable version 2023-03-04 21:02:19 +00:00
Dimitrij
aaf219eb90 revert check due set variable explicitly in appveyor 2023-03-04 20:32:22 +00:00
Dimitrij
1167794d58 fix condition to work in appveyor env. 2023-03-04 18:38:12 +00:00
Dimitrij
929babd69a Update change log 2023-03-04 00:35:35 +00:00
Dimitrij
870b7c1ffd update links 2023-03-04 00:07:19 +00:00
Dimitrij
9ed9d835b9 file harvester update 2023-03-04 00:06:55 +00:00
Dimitrij
985feb4b91 name space sync 2023-03-03 23:25:18 +00:00
Dimitrij
c43c85bb21 lib Update
correction for update window
2023-03-03 22:19:38 +00:00
Dimitrij
038074131c add template for build versioning 2023-03-03 20:45:40 +00:00
Dimitrij
ab9156a7d3 Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2023-03-03 15:44:34 +00:00
Dimitrij
f4b76818e8 lib update 2023-03-03 15:42:33 +00:00
Dimitrij
12a399a354 Merge pull request #2364 from BlueBlock/update_sql_usage_instructions
update sql usage doc
2023-03-02 13:55:24 +00:00
BlueBlock
dfdbbea85c sql usage doc updated 2023-03-02 08:32:07 -05:00
BlueBlock
5fad5ec4c7 Merge remote-tracking branch 'upstream/v1.77.3-dev' into update_build_scripts 2023-03-02 08:28:10 -05:00
Dimitrij
fe94da4727 lib update 2023-03-02 09:42:43 +00:00
Dimitrij
361ae3af50 Merge pull request #2362 from BlueBlock/fix_use_of_sql_database
Fix use of sql database
2023-03-02 09:34:06 +00:00
BlueBlock
a2e302ebfa add check for appveyor only actions in scripts 2023-03-01 15:31:54 -05:00
BlueBlock
186ab9c00e Merge remote-tracking branch 'upstream/v1.77.3-dev' into update_build_scripts 2023-03-01 14:50:49 -05:00
BlueBlock
9a2453524a update db to 2.9 2023-03-01 14:19:41 -05:00
BlueBlock
3a2e128e98 add missing columns to serializer 2023-03-01 11:22:18 -05:00
BlueBlock
1bd2dbf9f0 modify schema to be consistent 2023-03-01 11:21:55 -05:00
BlueBlock
b7f880e7f8 modify sql bit columns to be consistent 2023-03-01 11:21:26 -05:00
BlueBlock
13cd926b4f add missing columns 2023-03-01 09:37:10 -05:00
BlueBlock
6fb7b1c6e4 update docs to reflect auto-schema generation 2023-03-01 07:29:31 -05:00
BlueBlock
8f35015a6c fix db upgrade
fix db upgrade 2.8->2.9
2023-03-01 07:11:45 -05:00
BlueBlock
a926bccb97 fix db upgrade
fix db upgrade 2.7->2.8
2023-03-01 07:11:30 -05:00
BlueBlock
b9f2abf5eb add db schema initialization on a new empty database 2023-03-01 07:10:29 -05:00
BlueBlock
8dc5bda171 Update SqlDatabaseVersionVerifier.cs
fix CRLF of file
2023-03-01 07:09:33 -05:00
Dimitrij
d24bdb543b lib update 2023-02-26 21:40:14 +00:00
BlueBlock
dc921161e4 Merge remote-tracking branch 'upstream/v1.77.3-dev' into update_build_scripts 2023-02-25 17:37:58 -05:00
Dimitrij
8e248cb545 lib update 2023-02-21 21:48:20 +00:00
BlueBlock
ae93be4cfb make website update optional 2023-02-20 12:39:29 -05:00
BlueBlock
42949fbe91 update build scripts to create update files 2023-02-20 05:21:11 -05:00
Dimitrij
21dd175456 lib update 2023-02-17 19:49:47 +00:00
BlueBlock
a3df0da26b widen the version label further 2023-02-16 16:55:40 -05:00
BlueBlock
6b9d4fd107 increase label width for version on splashscreen
Widen to support longer version numbers such as 1.77.3.1234
2023-02-16 16:27:17 -05:00
BlueBlock
fac2ae6399 Merge remote-tracking branch 'upstream/v1.77.3-dev' into update_build_scripts 2023-02-16 15:47:57 -05:00
Dimitrij
46732803c3 Merge pull request #2356 from BlueBlock/improve_options_page_speed
improve speed for the display of the options page
2023-02-16 20:16:47 +00:00
BlueBlock
39d205bd4d improve speed for the display of the options page
rather than create the options page on demand, instantiate the options page immediately upon app start though in the background using idle time.
2023-02-16 14:57:11 -05:00
Dimitrij
8e2bd7997e update "Known Issues" regarding how-to update PuttyNG to latest 2023-02-16 11:12:44 +00:00
Dimitrij
76db9a6c62 Merge pull request #2352 from pakass/SSH.NET-update
SSH.NET Update
2023-02-16 09:45:30 +00:00
pakass
c03d5e891d update Changelog 2023-02-15 15:15:55 +01:00
pakass
b21f6e8ce7 update SSH.NET 2023-02-15 15:11:22 +01:00
BlueBlock
4b916f1888 update build scripts 2023-02-14 19:38:08 -05:00
Dimitrij
1f80e5339b Merge pull request #2348 from BlueBlock/remove_mat_kit
Remove mat kit included unintentionally
2023-02-13 09:30:48 +00:00
BlueBlock
dc616f8ea1 remove mat kit included unintentionally
It appears the mat kit config was included along with some .xlf definitions on the csproj

Remove th mat kit and references
2023-02-12 16:39:30 -05:00
Dimitrij
dc6588243f libs update 2023-02-11 21:31:12 +00:00
Dimitrij
690d3b0d4e Merge pull request #2347 from savornicesei/simo/gh-2344-docs-log4net-patching
Documented manual patching of  log4net CVE-2018-1285 vulnerability #2344
2023-02-11 12:41:09 +00:00
Dimitrij
4bc5dffabb Merge pull request #2346 from BlueBlock/add_feature_to_auto_reconnect_without_clicking_check
Modify "auto reconnect" to have the ability to really auto-reconnect
2023-02-11 12:35:02 +00:00
Simona Avornicesei
55a1e4a544 Documented manual patching of log4net CVE-2018-1285 vulnerability #2344 2023-02-10 21:17:26 +02:00
BlueBlock
5cb32dd75a change reconnect timer from 2s to 5s
Change and increase the reconnect timer to avoid too frequent connection attempts.
2023-02-10 04:40:55 -05:00
BlueBlock
f858c9fe48 fix checkbox state 2023-02-10 04:39:13 -05:00
BlueBlock
0ac39af404 modify "auto reconnect" to really auto-reconnect
The auto-reconnect does not automatically reconnect but instead displays a reconnect dialog. A second Options checkbox is added to allow true auto-reconnect.
2023-02-09 13:41:31 -05:00
Dimitrij
fc757b236f lib update 2023-02-04 16:42:12 +00:00
Dimitrij
a921e6e3d4 Merge pull request #2340 from BlueBlock/set_default_theme_on_first_run
set the default theme setting
2023-02-04 16:28:34 +00:00
Dimitrij
8cf0f50565 Merge pull request #2341 from BlueBlock/fix_broken_tests
Fix broken tests
2023-02-04 16:26:34 +00:00
Dimitrij
575ad7664f Merge pull request #2343 from savornicesei/simo/gh-2342-installer-in-debug-mode
Build installer in Debug mode #2342
2023-02-04 16:23:51 +00:00
Dimitrij
be17488070 Merge pull request #2339 from BlueBlock/add_missing_settings
Add 2 missing settings
2023-02-04 16:23:07 +00:00
Simona Avornicesei
f504107928 Build installer in Debug mode #2342 2023-02-04 10:10:51 +02:00
BlueBlock
0113f549c5 fix remaining unit tests 2023-02-03 17:35:09 -05:00
BlueBlock
9ff831faab fix additional tests 2023-02-03 17:05:36 -05:00
BlueBlock
a9ca243c0b fix protocol config unit tests 2023-02-03 16:53:29 -05:00
BlueBlock
553bbef45f set the default theme setting
On the initial start of a fresh install, if the options are opened the theme is then set from an empty string to the default. This causes the user to see the "must restart app" dialog when closing the options.

This only occurs on the initial run of the app. If the app is closed and run again, the theme will have been set.
2023-02-03 14:34:38 -05:00
BlueBlock
8f713f861b Add 2 missing settings
Two settings are missing in the settings but do exist in the Designer class.  These settings are used in code so they do need to exist.
ConDefaultRDGatewayUserViaAPI
ConDefaultRDGatewayExternalCredentialProvider
2023-02-03 14:12:02 -05:00
Dimitrij
324054b9d7 Merge pull request #2337 from BlueBlock/set_language_resx_to_autogen_language_class
set language.resx to auto generate the designer class
2023-02-03 10:10:20 +00:00
BlueBlock
d3fc9404ee set language.resx to auto generate the designer class
This avoids the need to manually regenerate the Language.Designer.cs file.

Note that in VS the file Language.Designer.cs will appear under Language.resx but the actual file locations have not changed.
2023-02-02 10:25:27 -05:00
Dimitrij
5bb780439b add autodate as was suggested by @savornicese 2023-02-01 12:56:43 +00:00
Dimitrij
f0e6008441 lib update 2023-02-01 12:44:37 +00:00
Dimitrij
39dd69f15e Merge pull request #2334 from BlueBlock/fix_build_order
Fix the dependencies for the Installer project build
2023-02-01 12:37:47 +00:00
Dimitrij
7b2e89df26 Merge pull request #2335 from BlueBlock/update_packages
Update project packages
2023-02-01 12:36:14 +00:00
BlueBlock
aa853f8481 Update project packages 2023-01-31 14:44:37 -05:00
BlueBlock
83bba75af6 Fix the dependencies for the Installer project build
The Installer project fails to build without also specifying the ExternalConnectors and mRemoteNG projects as dependencies.
2023-01-31 10:46:51 -05:00
Dimitrij
23889aa5b1 lib update 2023-01-08 11:57:03 +00:00
Dimitrij
5e6094fc42 lib update 2022-11-15 15:16:03 +00:00
Dimitrij
a4181cb6d6 Merge pull request #2301 from bartuszekj/splash_screen_fix
Fix for splash screen to appear on the primary screen.
2022-11-14 09:29:06 +00:00
Jerzy Bartuszek
88c49f0722 Fix for splash screen to appear on the primary screen. 2022-11-11 17:06:50 -06:00
Dimitrij
513d9e199c lib update 2022-11-09 14:34:02 +00:00
Dimitrij
e80975c56e lib update 2022-10-20 11:53:32 +01:00
Dimitrij
9051ac102c Merge pull request #2298 from SOlangsam/patch-2
Update mysql_db_setup.sql
2022-10-19 19:13:44 +01:00
SOlangsam
39a9b2e619 Update mysql_db_setup.sql
Added missing fields
Fix Issue for mysql #2292
2022-10-11 16:40:08 +02:00
Dimitrij
dbc55d248f add version color overwrite 2022-10-06 18:58:52 +01:00
Dimitrij
2b46180bfb clear inherited max-width 2022-10-06 18:41:31 +01:00
Dimitrij
5abe6c7e27 empty css 2022-10-06 18:30:31 +01:00
Dimitrij
4595ebeb9a next try to fix theme 2022-10-06 17:36:45 +01:00
Dimitrij
815c08e6d4 fix colors of sidebar 2022-10-05 21:31:30 +01:00
Dimitrij
a72ad218a0 fix for menu links color 2022-10-05 21:14:46 +01:00
Dimitrij
944ad1f769 Merge pull request #2295 from CrunchyBlue/v1.77.3-dev
*Updates hyperlink style to make links more visible to end users
2022-10-05 19:40:07 +01:00
PRINTABLE\dgagliardi
e17a68f61c *Updates hyperlink style to make links more visible to end users 2022-10-05 11:40:30 -05:00
Dimitrij
0b8196be68 Merge pull request #2285 from tecxx/develop-orig
support extraction of SSH private keys from external cred prov
2022-09-07 13:59:21 +01:00
tecxx
d9c01148b7 support extraction of SSH private keys from external credential provider (DSS)
supported formats: rsa, rsa with passphrase, putty private key
2022-09-07 14:08:29 +02:00
Dimitrij
2b3cfd992f Merge pull request #2275 from tecxx/develop-orig
external connectors improvement
2022-08-25 21:36:27 +01:00
tecxx
7e4bd7a6f3 add UI property selectors for external connectors
add support for external credential provider in remote desktop gateway
rename TSS to DSS
fix tests and property naming issues
2022-08-25 21:58:16 +02:00
Dimitrij
161e0ed637 fix for #2208 2022-08-22 10:31:54 +01:00
Dimitrij
1ee03e863c Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2022-07-29 23:26:59 +01:00
Dimitrij
2411481d8b lib upd 2022-07-29 23:26:14 +01:00
Dimitrij
0314a627ed Merge pull request #2259 from luciusagarthy/patch-1
Update Language.cs-CZ.resx
2022-07-27 23:39:09 +01:00
Dimitrij
4d339a0b09 Merge pull request #2261 from maxshlain/v1.77.3-hide-filemenu
Implement Show/Hide file menu in view menu
2022-07-27 23:38:37 +01:00
Dimitrij
c171e7f94b Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2022-07-17 23:14:21 +01:00
Dimitrij
eac4d966d9 remove not needed reference to old ObjectListView 2.7 2022-07-17 23:11:36 +01:00
Dimitrij
c20868c20c lib update 2022-07-17 22:42:17 +01:00
maxim-shlain
bb74d46f1f Implement Show/Hide file menu in view menu 2022-07-08 19:34:42 +03:00
luciusagarthy
152d48c583 Update Language.cs-CZ.resx
Thank you for a great app. I will contribute more. Here is some translation.
2022-07-07 16:19:48 +02:00
Dimitrij
b7a0155ba4 lib update 2022-06-25 06:39:19 +01:00
Dimitrij
0414724b21 update 2022-06-13 13:34:21 +01:00
Dimitrij
469f21db8d lib update 2022-06-13 10:35:41 +01:00
Dimitrij
b6c9d30195 Merge pull request #2244 from tecxx/develop-orig
save RCG and RestrictedAdmin fields correctly in connections file
2022-06-11 11:30:26 +01:00
tecxx
7b89430317 quickfix to save application settings correctly 2022-06-10 21:31:55 +02:00
tecxx
5bd854116a rcg/restrictedadmin fixes: schema file and defaults to false 2022-06-08 21:22:59 +02:00
tecxx
3fb7575529 save RCG and RestrictedAdmin fields correctly in connections file 2022-06-08 20:55:06 +02:00
Dimitrij
a3a868c2ce Merge pull request #2235 from jafin/fix/topmenu
fix (Toolbar): Remove unused `modeToolStripmenuItem` from Main toolbar
2022-06-03 09:59:26 +01:00
Jason Finch
1eb6fc2235 fix (Toolbar): Remove unused modeToolStripmenuItem from Main toolbar 2022-06-02 17:01:06 +10:00
Dimitrij
5ff4502f0a Merge pull request #2231 from simonai1254/patch-1
Specify "Desktop Runtime" in Requirements
2022-05-23 21:24:38 +01:00
Simon Monai
3126b8d4b0 Specify "Desktop Runtime" in Requirements 2022-05-23 22:15:00 +02:00
Dimitrij
dfb1d34b8a cleaning redundant blocks 2022-05-14 20:39:00 +01:00
Dimitrij
ace62c07be lib update 2022-05-13 01:55:18 +01:00
Dimitrij
ec2f3024b6 implement DPIAware, change splash screen to wpf 2022-05-13 01:39:00 +01:00
Dimitrij
25543f6561 Add acl controls to backupOptions 2022-05-02 00:31:00 +01:00
Dimitrij
3278657da7 fix missing values 2022-05-01 20:44:38 +01:00
Dimitrij
90c4d12688 lib updates 2022-05-01 20:10:22 +01:00
Dimitrij
4ceaea99f7 Merge pull request #2211 from nilsjonsson/new-lang
Add Swedish translation
2022-04-27 13:32:51 +01:00
Nils Jonsson
585de34ef1 Add Swedish translation 2022-04-27 14:18:53 +02:00
Dimitrij
60e1a3ce93 Merge pull request #2208 from Pwnoz0r/MR-887_rdm_import
Add Remote Desktop Manager (Devolutions) Importer
2022-04-23 21:02:32 +01:00
Jonathan Rainier
413d77ff1a Add Comments and Icon Support / Fix Unsorted
- Add Comments
- Add icon support for supported connection types
- Fix empty unsorted folder from being added
2022-04-23 15:53:25 -04:00
Jonathan Rainier
d8bb561063 Add Remote Desktop Manager (Devolutions) Importer
- Add RDM Importer mRemoteNG/mRemoteNG#887
2022-04-23 15:22:08 -04:00
Dimitrij
5dff2c20b2 Merge pull request #2204 from tecxx/develop-orig
fix for otp token renewal in TSS api
2022-04-19 12:53:06 +01:00
tecxx
27803e7787 fix for otp token renewal in TSS api 2022-04-19 11:44:12 +02:00
Dimitrij
58f9c1575f Merge pull request #2203 from tecxx/develop-orig
enable remote credential guard option
2022-04-15 16:45:33 +01:00
tecxx
7b909665b9 enable remote credential guard option
enable restricted admin mode option
2022-04-15 17:08:49 +02:00
Dimitrij
11eee991a1 fix visibility 2022-04-03 16:37:45 +01:00
Dimitrij
7056a859ef add acl for option pages 2022-04-03 16:25:04 +01:00
Dimitrij
2455f0d73e lib update 2022-04-03 15:35:56 +01:00
Dimitrij
943d36e23e add rbac to hide menu page + lang updates 2022-04-03 14:38:06 +01:00
Dimitrij
b563ac6e0c add basic rbac (acl) implementation as example for backup options + Language modification 2022-03-31 19:22:56 +01:00
Dimitrij
ed37a8ca2a fix #1015 2022-03-30 15:27:55 +01:00
Dimitrij
55bee8b0d8 add rbac settings 2022-03-30 12:30:59 +01:00
Dimitrij
6bd18c29e3 lib update 2022-03-29 10:38:27 +01:00
Dimitrij
37fc611767 Merge pull request #2188 from Trellmor/UpdateSessionDisplaySettings
Update session display settings
2022-03-19 12:57:09 +00:00
Daniel Triendl
f95fe351e2 Updated changelog and documentation 2022-03-18 20:27:54 +01:00
Daniel Triendl
2fb0ab1d91 Enable size change without reconnect
Use IMsRdpClient9::UpdateSessionDisplaySettings to dynamically update the
session display settings without reconnecting. RDP Version needs to be
Rdc9 or Highest for this to work.

Fixes #1546
2022-03-18 19:33:10 +01:00
Dimitrij
85f7be1d79 refactoring menu (preparation for RBAC) 2022-03-16 22:33:16 +00:00
Dimitrij
b3e5c1abc2 lib update 2022-03-16 09:55:34 +00:00
Dimitrij
9e216c0020 remove spaces 2022-03-11 17:53:05 +00:00
Dimitrij
a9f00b6a8c remove spaces 2022-03-11 17:04:51 +00:00
Dimitrij
46e7b02da2 lib update 2022-03-11 12:36:52 +00:00
Dimitrij
8db0bc6e6f refactoring options
each settings page from now will have separate options file, a bit later that helps to migrate into modular structure, then admin can control access to options and external modules may add may options independently
2022-03-10 13:48:39 +00:00
Dimitrij
1702b5867a update 2022-02-24 01:17:02 +00:00
Dimitrij
c03a6452ef update link 2022-02-24 01:14:14 +00:00
Dimitrij
69b840fb12 update 2022-02-24 01:11:44 +00:00
Dimitrij
9385ab287f update 2022-02-24 00:58:36 +00:00
Dimitrij
d2b2c39b97 fixing link 2022-02-24 00:54:01 +00:00
Dimitrij
f8ff978738 fix link 2022-02-24 00:49:43 +00:00
Dimitrij
d50b32236c update 2022-02-24 00:43:59 +00:00
Dimitrij
88d1db2840 Add howto for CyberArk psm 2022-02-24 00:35:00 +00:00
Dimitrij
b1dbe562d6 Merge pull request #2176 from AndyX90/sql-fix
Create missing columns
2022-02-23 23:56:56 +00:00
Andy Binder
e95b6f1b5d Update mysql_db_setup.sql 2022-02-23 21:57:24 +01:00
Andy Binder
1cbb7bb5dd Create missing columns 2022-02-23 21:46:10 +01:00
Dimitrij
34f3ffa129 Add version number to splash screen 2022-02-22 23:26:04 +00:00
Dimitrij
ecd25a673e lib updates 2022-02-15 17:27:57 +00:00
Dimitrij
58d0778c3d Merge pull request #2145 from jacko873/Dev
External Connectors, Default Options updated
2022-01-28 13:50:31 +00:00
Dimitrij
81a5422974 Merge pull request #2144 from tecxx/develop-orig
TSS improve refresh token / otp request handling
2022-01-28 13:04:40 +00:00
jacko873
eb859e5ede External Connectors, Default Options updated
Updated options form to fix UI bug where domain was pushout of range,
Added saving of  default User API ID
adjusted RDP and SHH to check for default User API ID,
also adjusted code here for consistancey
2022-01-28 13:50:13 +01:00
tecxx
c8035b5071 TSS improve refresh token / otp request handling 2022-01-28 11:18:44 +01:00
Dimitrij
a98a336e52 Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2022-01-21 23:58:25 +00:00
Dimitrij
d7f6535b85 update putty to v0.76 2022-01-21 23:54:28 +00:00
Faryan Rezagholi
f9d4e3152b Update README.md
Added  installation via winget
2022-01-19 23:49:16 +01:00
Dimitrij
69bd816aeb Merge branch 'v1.77.3-dev' of https://github.com/mRemoteNG/mRemoteNG into v1.77.3-dev 2022-01-19 19:44:19 +00:00
Dimitrij
783810749c change log update 2022-01-19 19:42:43 +00:00
Dimitrij
92c3c967ba Merge pull request #2138 from ThoGoetz/v1.77.3-dev
Improve compatibility with Remote Desktop Connection Manager v2.83
2022-01-19 19:41:05 +00:00
Thomas Götzinger
c632ba4306 Allow importing connections from Remote Desktop Connection Manager v2.83 2022-01-19 16:59:42 +01:00
Thomas Götzinger
e29d2c25ba Fixing Null value exception, when saving imported connections from Remote Desktop Connection Manager 2022-01-19 16:59:16 +01:00
1149 changed files with 320174 additions and 8 deletions

130
.github/workflows/build-x86_64.yml vendored Normal file
View File

@@ -0,0 +1,130 @@
name: Build x86_64
on:
push:
branches:
- v1.77.3-dev
jobs:
Build-Debug-MSI:
runs-on: windows-2022
steps:
- name: 01. Copy repository
uses: actions/checkout@v4
- name: 02. Setup MSBuild.exe
uses: microsoft/setup-msbuild@v1.3.1 # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
with:
vs-version: '17.8.3'
- name: 03. Restore nuget packages for solution
shell: pwsh
run: |
dotnet restore
- name: 04. Compile mRemoteNG
shell: pwsh
run: |
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" -p:Configuration="Debug Installer" -p:Platform=x64 /verbosity:normal
- name: 05. Publish MSI as Artifact
uses: actions/upload-artifact@v4
with:
name: debug-msi-x86_64
path: ${{ github.workspace }}\mRemoteNGInstaller\Installer\bin\x64\Debug\en-US\
if-no-files-found: error
Build-Debug-Portable:
runs-on: windows-2022
steps:
- name: 01. Copy repository
uses: actions/checkout@v4
- name: 02. Setup MSBuild.exe
uses: microsoft/setup-msbuild@v1.3.1 # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
with:
vs-version: '17.8.3'
- name: 03. Restore nuget packages for solution
shell: pwsh
run: |
dotnet restore
- name: 04. Compile mRemoteNG
shell: pwsh
run: |
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" -p:Configuration="Debug Portable" -p:Platform=x64 /verbosity:normal
- name: 05. Publish Portable Binary as Artifact
uses: actions/upload-artifact@v4
with:
name: debug-portable-x86_64
path: ${{ github.workspace }}\mRemoteNG\bin\x64\Debug Portable\
if-no-files-found: error
Build-Release:
runs-on: windows-2022
steps:
- name: 01. Copy repository
uses: actions/checkout@v4
- name: 02. Setup MSBuild.exe
uses: microsoft/setup-msbuild@v1.3.1 # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
with:
vs-version: '17.8.3'
- name: 03. Restore nuget packages for solution
shell: pwsh
run: |
dotnet restore
- name: 04. Compile mRemoteNG
shell: pwsh
run: |
msbuild "$Env:GITHUB_WORKSPACE\mRemoteNG.sln" -p:Configuration="Release Installer and Portable" -p:Platform=x64 /verbosity:normal
- name: 05. Publish MSI Binary as Artifact
uses: actions/upload-artifact@v4
with:
name: release-msi-x86_64
path: ${{ github.workspace }}\mRemoteNGInstaller\Installer\bin\x64\Release\en-US\
if-no-files-found: error
- name: 06. Publish Portable Binary as Artifact
uses: actions/upload-artifact@v4
with:
name: release-portable-x86_64
path: ${{ github.workspace }}\mRemoteNG\bin\x64\Release
if-no-files-found: error
Create-Release:
needs: [Build-Debug-MSI, Build-Debug-Portable, Build-Release]
runs-on: ubuntu-22.04
steps:
- name: 01. Copy repository
uses: actions/checkout@v4
- name: 02. Download Artifacts
uses: actions/download-artifact@v4
- name: 03. Create compressed archives # Needs to be done because "actions/download-artifact@v4" is extracting the zipped Artifacts
shell: bash
run: |
zip -r debug-msi-x86_64.zip debug-msi-x86_64/
zip -r debug-portable-x86_64.zip debug-portable-x86_64/
zip -r release-msi-x86_64.zip release-msi-x86_64/
zip -r release-portable-x86_64.zip release-portable-x86_64/
- name: 04. Create Release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "v1.77.3-dev-${GITHUB_RUN_NUMBER}" \
--title "v1.77.3-dev-${GITHUB_RUN_NUMBER}" \
--prerelease \
--generate-notes \
$GITHUB_WORKSPACE/debug-msi-x86_64.zip \
$GITHUB_WORKSPACE/debug-portable-x86_64.zip \
$GITHUB_WORKSPACE/release-msi-x86_64.zip \
$GITHUB_WORKSPACE/release-portable-x86_64.zip

6
.gitignore vendored
View File

@@ -283,6 +283,6 @@ Installer Projects/Installer/Fragments/HelpFilesFragment.wxs
InstallerProjects/Installer/Fragments/FilesFragment.wxs
InstallerProjects/Installer/Resources/License.rtf
# gh-pages info
runlocal.bat
/_site
# mRemoteNG
**/mRemoteNG/Properties/AssemblyInfo.tt
**/mRemoteNG/Properties/AssemblyInfo.cs

11
Add-ons/README.md Normal file
View File

@@ -0,0 +1,11 @@
## Add-ons library by 3rd party
This is a list of add-ons, plugins and extentions what could be used with mRemoteNG, if you wish to add yours to this list - just drop me a line: <a href="mailto:support@mremoteng.org">support@mremoteng.org</a>
<br>
| &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b>Date&nbsp;added</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<b>Author</b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | <b>Type</b> | <b>Name</b> | <b>Description</b> | <b>Repository</b> |
| :---------------------------------------------------------:|:-------------------------------------------------:| :---------: |-------------|--------------------|:-----------------:|
| 02-08-2022 | <a href="https://github.com/JustBeta"><img align="left" src="https://avatars.githubusercontent.com/u/25150896?v=4" alt="JustBeta" width="30px"/>JustBeta</a> | script | Export-MobaXterm2mRemoteNG | Conversion of MobaXterm's ini file to mRemoteNG format. | [GITHUB Repository](https://github.com/JustBeta/Export-MobaXtern2mRemoteNG/tree/main) |
<br>
For a detailed usage examples and documentation please reach out authors.

View File

@@ -2,10 +2,35 @@
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.1784]
### Fixed
- #2362: Fix use of sql database
- #2356: Improve speed for the display of the options page
- #2352: SSH.NET Update
- #2346: Modify "auto reconnect" to have the ability to really auto-reconnect
- #2340: Set the default theme setting
- #2339: Add 2 missing settings
- #2261: Implement Show/Hide file menu in view menu
- #2244: Save RCG and RestrictedAdmin fields correctly in connections file
- #2195: Fix crafted XML File Code Execution vulnerability
- #304: use pwfile instead of cleartext password for puttyng
### Added
- #2285: Support extraction of SSH private keys from external cred prov
- #2268: Postregsql database support
### Updated
- #2295: Updates hyperlink style to make links more visible to end users
- #2337: Set language.resx to auto generate the designer class
## [1.77.3]
### Added
- # #2123: Thycotic Secret Server - Added 2FA OTP support
- #1736: Update of SSH.NET to 2020.0.2 to allow File Transfer again
- #2138: Improve compatibility with Remote Desktop Connection Manager v2.83
- #2123: Thycotic Secret Server - Added 2FA OTP support
### Changed
- #1546: Enable resize without reconnect for RDP Version Rdc9 or higher
## [1.77.2]
### Added
- #2086: Replace WebClient with async HttpClient for updater.

View File

@@ -0,0 +1,180 @@
namespace ExternalConnectors.AWS
{
partial class AWSConnectionForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.tbAccesKeyID = new System.Windows.Forms.TextBox();
this.tbAccesKey = new System.Windows.Forms.TextBox();
this.btnOK = new System.Windows.Forms.Button();
this.btnCancel = new System.Windows.Forms.Button();
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel();
this.tableLayoutPanel1.SuspendLayout();
this.tableLayoutPanel2.SuspendLayout();
this.SuspendLayout();
//
// tbAccesKeyID
//
this.tbAccesKeyID.Dock = System.Windows.Forms.DockStyle.Fill;
this.tbAccesKeyID.Location = new System.Drawing.Point(152, 4);
this.tbAccesKeyID.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.tbAccesKeyID.Name = "tbAccesKeyID";
this.tbAccesKeyID.Size = new System.Drawing.Size(490, 23);
this.tbAccesKeyID.TabIndex = 0;
//
// tbAccesKey
//
this.tbAccesKey.Dock = System.Windows.Forms.DockStyle.Fill;
this.tbAccesKey.Location = new System.Drawing.Point(152, 43);
this.tbAccesKey.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.tbAccesKey.Name = "tbAccesKey";
this.tbAccesKey.Size = new System.Drawing.Size(490, 23);
this.tbAccesKey.TabIndex = 2;
//
// btnOK
//
this.btnOK.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK;
this.btnOK.Location = new System.Drawing.Point(219, 12);
this.btnOK.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.btnOK.Name = "btnOK";
this.btnOK.Size = new System.Drawing.Size(88, 26);
this.btnOK.TabIndex = 10;
this.btnOK.Text = "OK";
this.btnOK.UseVisualStyleBackColor = true;
//
// btnCancel
//
this.btnCancel.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Location = new System.Drawing.Point(338, 12);
this.btnCancel.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(88, 26);
this.btnCancel.TabIndex = 11;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 2;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 22.92994F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 77.07006F));
this.tableLayoutPanel1.Controls.Add(this.label1, 0, 0);
this.tableLayoutPanel1.Controls.Add(this.label2, 0, 1);
this.tableLayoutPanel1.Controls.Add(this.tbAccesKeyID, 1, 0);
this.tableLayoutPanel1.Controls.Add(this.tbAccesKey, 1, 1);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Top;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 3;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 23F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 23F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 23F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(646, 102);
this.tableLayoutPanel1.TabIndex = 12;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Dock = System.Windows.Forms.DockStyle.Fill;
this.label1.Location = new System.Drawing.Point(4, 0);
this.label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(140, 39);
this.label1.TabIndex = 2;
this.label1.Text = "Access Key ID";
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Dock = System.Windows.Forms.DockStyle.Fill;
this.label2.Location = new System.Drawing.Point(4, 39);
this.label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(140, 39);
this.label2.TabIndex = 4;
this.label2.Text = "Access Key";
this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// tableLayoutPanel2
//
this.tableLayoutPanel2.ColumnCount = 5;
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 93F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 23F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 93F));
this.tableLayoutPanel2.Controls.Add(this.btnOK, 1, 0);
this.tableLayoutPanel2.Controls.Add(this.btnCancel, 3, 0);
this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Bottom;
this.tableLayoutPanel2.Location = new System.Drawing.Point(0, 112);
this.tableLayoutPanel2.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.tableLayoutPanel2.Name = "tableLayoutPanel2";
this.tableLayoutPanel2.RowCount = 1;
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel2.Size = new System.Drawing.Size(646, 50);
this.tableLayoutPanel2.TabIndex = 13;
//
// AWSConnectionForm
//
this.AcceptButton = this.btnOK;
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(646, 162);
this.Controls.Add(this.tableLayoutPanel2);
this.Controls.Add(this.tableLayoutPanel1);
this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.Name = "AWSConnectionForm";
this.Text = "AWS EC2 API Login Data";
this.Activated += new System.EventHandler(this.AWSConnectionForm_Activated);
this.tableLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel1.PerformLayout();
this.tableLayoutPanel2.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
public System.Windows.Forms.TextBox tbAccesKeyID;
public System.Windows.Forms.TextBox tbAccesKey;
private System.Windows.Forms.Button btnOK;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
}
}

View File

@@ -0,0 +1,16 @@
namespace ExternalConnectors.AWS
{
public partial class AWSConnectionForm : Form
{
public AWSConnectionForm()
{
InitializeComponent();
}
private void AWSConnectionForm_Activated(object sender, EventArgs e)
{
tbAccesKeyID.Focus();
}
}
}

View File

@@ -0,0 +1,60 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,119 @@
using Amazon;
using Amazon.EC2;
using Amazon.EC2.Model;
using Microsoft.Win32;
namespace ExternalConnectors.AWS
{
public class EC2FetchDataService
{
private static DateTime lastFetch;
private static List<InstanceInfo>? lastData;
// input must be in format "AWSAPI:instanceid" where instanceid is the ec2 instance id, e.g. i-066f750a76c97583d
public static async Task<string> GetEC2InstanceDataAsync(string input, string region)
{
// get secret id
if (!input.StartsWith("AWSAPI:"))
throw new Exception("calling this function requires AWSAPI: input");
string InstanceID = input[7..];
// init connection credentials, display popup if necessary
AWSConnectionData.Init();
var alldata = await GetEC2IPDataAsync(region);
var found = alldata.Where(x => x.InstanceId == InstanceID).SingleOrDefault();
return (found == null) ? "" : found.PublicIP;
}
private static async Task<List<InstanceInfo>> GetEC2IPDataAsync(string region)
{
// caching
TimeSpan timeSpan = DateTime.Now - lastFetch;
if (timeSpan.TotalMinutes < 1 && lastData != null)
return lastData;
//AWSConfigs.AWSRegion = AWSConnectionData.region;
AWSConfigs.AWSRegion = region;
string awsAccessKeyId = AWSConnectionData.awsKeyID;
string awsSecretAccessKey = AWSConnectionData.awsKey;
var _client = new AmazonEC2Client(awsAccessKeyId, awsSecretAccessKey, RegionEndpoint.EUCentral1);
bool done = false;
List<InstanceInfo> instanceList = new();
var request = new DescribeInstancesRequest();
while (!done)
{
DescribeInstancesResponse response = await _client.DescribeInstancesAsync(request);
foreach (var reservation in response.Reservations)
{
foreach (var instance in reservation.Instances)
{
string vmname = "";
foreach (var tag in instance.Tags)
{
if (tag.Key == "Name")
{
vmname = tag.Value;
}
}
InstanceInfo inf = new(instance, vmname);
instanceList.Add(inf);
}
}
request.NextToken = response.NextToken;
if (response.NextToken == null)
{
done = true;
}
}
lastData = instanceList.OrderBy(x => x.Name).ToList();
lastFetch = DateTime.Now;
return lastData;
}
public static class AWSConnectionData
{
private static readonly RegistryKey key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteAWSInterface");
public static string awsKeyID = "";
public static string awsKey = "";
//public static string _region = "eu-central-1";
public static void Init()
{
if (awsKey != "")
return;
// display gui and ask for data
AWSConnectionForm f = new();
f.tbAccesKeyID.Text = "" + key.GetValue("KeyID");
f.tbAccesKey.Text = "" + key.GetValue("Key");
//f.tbRegion.Text = "" + key.GetValue("Region");
//if (f.tbRegion.Text == null || f.tbRegion.Text.Length < 2)
// f.tbRegion.Text = region;
_ = f.ShowDialog();
if (f.DialogResult != DialogResult.OK)
return;
// store values to memory
awsKeyID = f.tbAccesKeyID.Text;
awsKey = f.tbAccesKey.Text;
//region = f.tbRegion.Text;
// write values to registry
key.SetValue("KeyID", awsKeyID);
key.SetValue("Key", awsKey);
//key.SetValue("Region", region);
key.Close();
}
}
}
}

View File

@@ -0,0 +1,34 @@
using Amazon.EC2.Model;
using System;
namespace ExternalConnectors.AWS
{
public class InstanceInfo
{
public string InstanceId { get; }
public string Name { get; }
public string Status { get; }
public string PublicIP { get; }
public string PrivateIP { get; }
public InstanceInfo(Instance instance, string name)
{
InstanceId = instance.InstanceId;
Name = name;
switch(instance.State.Code)
{
case 0: Status = "Pending"; break;
case 16: Status = "Running"; break;
case 32: Status = "Shutdown"; break;
case 48: Status = "Terminated"; break;
case 64: Status = "Stopping"; break;
case 80: Status = "Stopped"; break;
default: Status = "Unknown"; break;
};
PublicIP = instance.PublicIpAddress ?? "";
PrivateIP = instance.PrivateIpAddress ?? "";
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,241 @@
namespace ExternalConnectors.CPS
{
partial class CPSConnectionForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CPSConnectionForm));
tbServerURL = new TextBox();
label3 = new Label();
tbAPIKey = new TextBox();
btnOK = new Button();
btnCancel = new Button();
tableLayoutPanel1 = new TableLayoutPanel();
label1 = new Label();
label6 = new Label();
tbOTP = new TextBox();
cbUseSSO = new CheckBox();
tableLayoutPanel2 = new TableLayoutPanel();
label4 = new Label();
tableLayoutPanel1.SuspendLayout();
tableLayoutPanel2.SuspendLayout();
SuspendLayout();
//
// tbServerURL
//
tbServerURL.Dock = DockStyle.Fill;
tbServerURL.Location = new Point(298, 5);
tbServerURL.Margin = new Padding(5);
tbServerURL.Name = "tbServerURL";
tbServerURL.Size = new Size(611, 27);
tbServerURL.TabIndex = 0;
//
// label3
//
label3.AutoSize = true;
label3.Dock = DockStyle.Fill;
label3.Location = new Point(5, 84);
label3.Margin = new Padding(5, 0, 5, 0);
label3.Name = "label3";
label3.Size = new Size(283, 42);
label3.TabIndex = 5;
label3.Text = "API Key";
label3.TextAlign = ContentAlignment.MiddleLeft;
//
// tbAPIKey
//
tbAPIKey.Dock = DockStyle.Fill;
tbAPIKey.Location = new Point(298, 89);
tbAPIKey.Margin = new Padding(5);
tbAPIKey.Name = "tbAPIKey";
tbAPIKey.Size = new Size(611, 27);
tbAPIKey.TabIndex = 4;
tbAPIKey.UseSystemPasswordChar = true;
//
// btnOK
//
btnOK.Anchor = AnchorStyles.Right;
btnOK.DialogResult = DialogResult.OK;
btnOK.Location = new Point(337, 16);
btnOK.Margin = new Padding(5);
btnOK.Name = "btnOK";
btnOK.Size = new Size(101, 35);
btnOK.TabIndex = 6;
btnOK.Text = "OK";
btnOK.UseVisualStyleBackColor = true;
//
// btnCancel
//
btnCancel.Anchor = AnchorStyles.Left;
btnCancel.DialogResult = DialogResult.Cancel;
btnCancel.Location = new Point(474, 16);
btnCancel.Margin = new Padding(5);
btnCancel.Name = "btnCancel";
btnCancel.Size = new Size(101, 35);
btnCancel.TabIndex = 11;
btnCancel.Text = "Cancel";
btnCancel.UseVisualStyleBackColor = true;
//
// tableLayoutPanel1
//
tableLayoutPanel1.ColumnCount = 2;
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 32.06997F));
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 67.93003F));
tableLayoutPanel1.Controls.Add(label1, 0, 0);
tableLayoutPanel1.Controls.Add(label3, 0, 2);
tableLayoutPanel1.Controls.Add(tbServerURL, 1, 0);
tableLayoutPanel1.Controls.Add(tbAPIKey, 1, 2);
tableLayoutPanel1.Controls.Add(label6, 0, 3);
tableLayoutPanel1.Controls.Add(tbOTP, 1, 3);
tableLayoutPanel1.Controls.Add(cbUseSSO, 0, 1);
tableLayoutPanel1.Dock = DockStyle.Top;
tableLayoutPanel1.Location = new Point(0, 0);
tableLayoutPanel1.Margin = new Padding(5);
tableLayoutPanel1.Name = "tableLayoutPanel1";
tableLayoutPanel1.RowCount = 5;
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
tableLayoutPanel1.RowStyles.Add(new RowStyle(SizeType.Percent, 20F));
tableLayoutPanel1.Size = new Size(914, 212);
tableLayoutPanel1.TabIndex = 12;
//
// label1
//
label1.AutoSize = true;
label1.Dock = DockStyle.Fill;
label1.Location = new Point(5, 0);
label1.Margin = new Padding(5, 0, 5, 0);
label1.Name = "label1";
label1.Size = new Size(283, 42);
label1.TabIndex = 2;
label1.Text = "Passwordstate URL";
label1.TextAlign = ContentAlignment.MiddleLeft;
//
// label6
//
label6.AutoSize = true;
label6.Dock = DockStyle.Fill;
label6.Location = new Point(3, 126);
label6.Name = "label6";
label6.Size = new Size(287, 42);
label6.TabIndex = 15;
label6.Text = "2FA OTP (Optional)";
//
// tbOTP
//
tbOTP.Dock = DockStyle.Fill;
tbOTP.Location = new Point(298, 131);
tbOTP.Margin = new Padding(5);
tbOTP.Name = "tbOTP";
tbOTP.Size = new Size(611, 27);
tbOTP.TabIndex = 5;
//
// cbUseSSO
//
cbUseSSO.Anchor = AnchorStyles.Left;
cbUseSSO.AutoSize = true;
cbUseSSO.Location = new Point(5, 53);
cbUseSSO.Margin = new Padding(5, 5, 5, 0);
cbUseSSO.Name = "cbUseSSO";
cbUseSSO.Size = new Size(157, 24);
cbUseSSO.TabIndex = 14;
cbUseSSO.Text = "Use SSO / WinAuth";
cbUseSSO.UseVisualStyleBackColor = true;
cbUseSSO.CheckedChanged += cbUseSSO_CheckedChanged;
//
// tableLayoutPanel2
//
tableLayoutPanel2.ColumnCount = 5;
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 106F));
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 26F));
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 107F));
tableLayoutPanel2.Controls.Add(btnOK, 1, 0);
tableLayoutPanel2.Controls.Add(btnCancel, 3, 0);
tableLayoutPanel2.Dock = DockStyle.Bottom;
tableLayoutPanel2.Location = new Point(0, 300);
tableLayoutPanel2.Margin = new Padding(5);
tableLayoutPanel2.Name = "tableLayoutPanel2";
tableLayoutPanel2.RowCount = 1;
tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
tableLayoutPanel2.Size = new Size(914, 67);
tableLayoutPanel2.TabIndex = 13;
//
// label4
//
label4.AutoSize = true;
label4.Dock = DockStyle.Fill;
label4.Location = new Point(0, 212);
label4.Margin = new Padding(5, 0, 5, 0);
label4.Name = "label4";
label4.Size = new Size(345, 20);
label4.TabIndex = 14;
label4.Text = "URL is the base URL, like https://pass.domain.local/";
label4.TextAlign = ContentAlignment.MiddleLeft;
//
// CPSConnectionForm
//
AcceptButton = btnOK;
AutoScaleDimensions = new SizeF(8F, 20F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(914, 367);
Controls.Add(label4);
Controls.Add(tableLayoutPanel2);
Controls.Add(tableLayoutPanel1);
Icon = (Icon)resources.GetObject("$this.Icon");
Margin = new Padding(5);
Name = "CPSConnectionForm";
Text = "Passwordstate API Login Data";
Activated += CPSConnectionForm_Activated;
tableLayoutPanel1.ResumeLayout(false);
tableLayoutPanel1.PerformLayout();
tableLayoutPanel2.ResumeLayout(false);
ResumeLayout(false);
PerformLayout();
}
#endregion
private System.Windows.Forms.Label label3;
public System.Windows.Forms.TextBox tbServerURL;
//public System.Windows.Forms.TextBox tbUsername;
public System.Windows.Forms.TextBox tbAPIKey;
private System.Windows.Forms.Button btnOK;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
public System.Windows.Forms.CheckBox cbUseSSO;
private System.Windows.Forms.Label label4;
private Label label6;
public TextBox tbOTP;
}
}

View File

@@ -0,0 +1,41 @@
namespace ExternalConnectors.CPS
{
public partial class CPSConnectionForm : Form
{
public CPSConnectionForm()
{
InitializeComponent();
}
private void CPSConnectionForm_Activated(object sender, EventArgs e)
{
SetVisibility();
if (cbUseSSO.Checked)
btnOK.Focus();
else
{
if (tbAPIKey.Text.Length == 0)
tbAPIKey.Focus();
else
tbOTP.Focus();
}
tbAPIKey.Focus();
if (!string.IsNullOrEmpty(tbAPIKey.Text) || cbUseSSO.Checked == true)
tbOTP.Focus();
}
private void cbUseSSO_CheckedChanged(object sender, EventArgs e)
{
SetVisibility();
}
private void SetVisibility()
{
bool ch = cbUseSSO.Checked;
tbAPIKey.Enabled = !ch;
//tbUsername.Enabled = !ch;
}
}
}

View File

@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAEAEBAAAAEACABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAA
AAAtLDAA+PDaAP///wD79ugA/PrzAPf39wBGRUkA7NacAM2WAAA3z6kA+Pz/AIKBgwD+//4A4sNtAHXe
xAD8+vIAjuTOANOjHgDV6/4AJZf3APn5+QDw37IAIB8jAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAgICAgICCQ4CAgIWAgIUAgICAgICAgkJAgICFhYWAAIIDwICAgICCQwCAhYWFgYCCAgIBwIC
AgkJAgsWFhYWBQICCAgIAwIJCQIWFhYWAgICAgINCAgCAgICFhYCAgICAgICAgIRAgICAgICAgICAgIC
AgICEwICAgICEhMTExMCAgITExMCAgICAgITExMTAhMTExMCAgICAgICAgICAhMTEwICAgIICAIJCQIC
AgIKAgICAgIICAQCAgkJAgICAgICAgICCAgCAgICCQkCAgICAgICFQgBAgICAgwJAgICAgICAggIAgIC
AgICEAkCAgICAgIIAgICAgICAgICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
</value>
</data>
</root>

View File

@@ -0,0 +1,301 @@
using Microsoft.Win32;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System.Security.Cryptography;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace ExternalConnectors.CPS;
public class PasswordstateInterface
{
private static class CPSConnectionData
{
public static string ssUsername = "";
public static string ssPassword = "";
public static string ssUrl = "";
public static string ssOTP = "";
public static DateTime ssOTPTimeStampExpiration;
public static bool ssSSO = false;
public static bool initdone = false;
//token
//public static string ssTokenBearer = "";
//public static DateTime ssTokenExpiresOn = DateTime.UtcNow;
//public static string ssTokenRefresh = "";
public static void Init()
{
// 2024-05-04 passwordstate currently does not support auth tokens, so we need to re-enter otp codes frequently
if (!string.IsNullOrEmpty(ssOTP) && DateTime.Now > ssOTPTimeStampExpiration)
{
ssOTP = "";
initdone = false;
}
if (initdone == true)
return;
RegistryKey key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteCPSInterface");
try
{
// display gui and ask for data
CPSConnectionForm f = new CPSConnectionForm();
//string? un = key.GetValue("Username") as string;
//f.tbUsername.Text = un ?? "";
f.tbAPIKey.Text = CPSConnectionData.ssPassword; // in OTP refresh cases, this value might already be filled
string? url = key.GetValue("URL") as string;
if (url == null || !url.Contains("://"))
url = "https://cred.domain.local/SecretServer";
f.tbServerURL.Text = url;
var b = key.GetValue("SSO");
if (b == null || (string)b != "True")
ssSSO = false;
else
ssSSO = true;
f.cbUseSSO.Checked = ssSSO;
// show dialog
while (true)
{
_ = f.ShowDialog();
if (f.DialogResult != DialogResult.OK)
return;
// store values to memory
//ssUsername = f.tbUsername.Text;
ssPassword = f.tbAPIKey.Text;
ssUrl = f.tbServerURL.Text;
ssSSO = f.cbUseSSO.Checked;
ssOTP = f.tbOTP.Text;
ssOTPTimeStampExpiration = DateTime.Now.AddSeconds(30);
// check connection first
try
{
if (TestCredentials() == true)
{
initdone = true;
break;
}
}
catch (Exception)
{
MessageBox.Show("Test Credentials failed - please check your credentials");
}
}
// write values to registry
//key.SetValue("Username", ssUsername);
key.SetValue("URL", ssUrl);
key.SetValue("SSO", ssSSO);
}
catch (Exception)
{
throw;
}
finally
{
key.Close();
}
}
}
private static bool TestCredentials()
{
return ConnectionTest();
}
private static bool ConnectionTest()
{
if (CPSConnectionData.ssSSO)
{
string url = $"{CPSConnectionData.ssUrl}/winapi/passwordlists/";
using HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
var json = client.GetStringAsync(url).Result;
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
if (data == null)
return false;
return true;
}
else
{
string url = $"{CPSConnectionData.ssUrl}/api/passwordlists/";
using HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
client.DefaultRequestHeaders.Add("APIKey", CPSConnectionData.ssPassword);
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
var json = client.GetStringAsync(url).Result;
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
if (data == null)
return false;
return true;
}
}
private static JsonNode? FetchDataWinAuth(int secretID)
{
string url = $"{CPSConnectionData.ssUrl}/winapi/passwords/{secretID}";
using HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
var json = client.GetStringAsync(url).Result;
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
if (data == null)
return null;
JsonNode? element = data[0];
return element;
}
private static JsonNode? FetchDataAPIKeyAuth(int secretID)
{
string url = $"{CPSConnectionData.ssUrl}/api/passwords/{secretID}";
using HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add("User-Agent", "mRemote");
client.DefaultRequestHeaders.Add("APIKey", CPSConnectionData.ssPassword);
client.DefaultRequestHeaders.Add("OTP", CPSConnectionData.ssOTP);
var json = client.GetStringAsync(url).Result;
JsonNode? data = JsonSerializer.Deserialize<JsonNode>(json);
if (data == null)
return null;
JsonNode? element = data[0];
return element;
}
private static void FetchSecret(int secretID, out string secretUsername, out string secretPassword, out string secretDomain, out string privatekey)
{
// clear return variables
secretDomain = "";
secretUsername = "";
secretPassword = "";
privatekey = "";
string privatekeypassphrase = "";
JsonNode? element = null;
if (CPSConnectionData.ssSSO)
element = FetchDataWinAuth(secretID);
else
element = FetchDataAPIKeyAuth(secretID);
if (element == null)
return;
var dom = element["Domain"];
if (dom != null) secretDomain = dom.ToString();
var user = element["UserName"];
if (user != null) secretUsername = user.ToString();
var pw = element["Password"];
if (pw != null) secretPassword = pw.ToString();
var privkey = element["GenericField1"];
if (privkey != null) privatekey = privkey.ToString();
var phrase = element["GenericField3"];
if (phrase != null) privatekeypassphrase = phrase.ToString();
// need to decode the private key?
if (!string.IsNullOrEmpty(privatekeypassphrase))
{
try
{
var key = DecodePrivateKey(privatekey, privatekeypassphrase);
privatekey = key;
}
catch(Exception)
{
}
}
// conversion to putty format necessary?
if (!string.IsNullOrEmpty(privatekey) && !privatekey.StartsWith("PuTTY-User-Key-File-2"))
{
try
{
RSACryptoServiceProvider key = ImportPrivateKey(privatekey);
privatekey = PuttyKeyFileGenerator.ToPuttyPrivateKey(key);
}
catch (Exception)
{
}
}
}
#region PUTTY KEY HANDLING
// decode rsa private key with encryption password
private static string DecodePrivateKey(string encryptedPrivateKey, string password)
{
TextReader textReader = new StringReader(encryptedPrivateKey);
PemReader pemReader = new PemReader(textReader, new PasswordFinder(password));
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
TextWriter textWriter = new StringWriter();
var pemWriter = new PemWriter(textWriter);
pemWriter.WriteObject(keyPair.Private);
pemWriter.Writer.Flush();
return ""+textWriter.ToString();
}
private class PasswordFinder : IPasswordFinder
{
private string password;
public PasswordFinder(string password)
{
this.password = password;
}
public char[] GetPassword()
{
return password.ToCharArray();
}
}
// read private key pem string to rsacryptoserviceprovider
public static RSACryptoServiceProvider ImportPrivateKey(string pem)
{
PemReader pr = new PemReader(new StringReader(pem));
AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)KeyPair.Private);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(rsaParams);
return rsa;
}
#endregion
// input: must be the secret id to fetch
public static void FetchSecretFromServer(string secretID, out string username, out string password, out string domain, out string privatekey)
{
// get secret id
int sid = Int32.Parse(secretID);
// init connection credentials, display popup if necessary
CPSConnectionData.Init();
// get the secret
FetchSecret(sid, out username, out password, out domain, out privatekey);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -0,0 +1,281 @@
namespace ExternalConnectors.DSS
{
partial class SSConnectionForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SSConnectionForm));
this.tbSSURL = new System.Windows.Forms.TextBox();
this.tbUsername = new System.Windows.Forms.TextBox();
this.label3 = new System.Windows.Forms.Label();
this.tbPassword = new System.Windows.Forms.TextBox();
this.btnOK = new System.Windows.Forms.Button();
this.btnCancel = new System.Windows.Forms.Button();
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.label5 = new System.Windows.Forms.Label();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.cbUseSSO = new System.Windows.Forms.CheckBox();
this.label6 = new System.Windows.Forms.Label();
this.tbOTP = new System.Windows.Forms.TextBox();
this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel();
this.label4 = new System.Windows.Forms.Label();
this.tableLayoutPanel1.SuspendLayout();
this.tableLayoutPanel2.SuspendLayout();
this.SuspendLayout();
//
// tbSSURL
//
this.tbSSURL.Dock = System.Windows.Forms.DockStyle.Fill;
this.tbSSURL.Location = new System.Drawing.Point(298, 5);
this.tbSSURL.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.tbSSURL.Name = "tbSSURL";
this.tbSSURL.Size = new System.Drawing.Size(611, 27);
this.tbSSURL.TabIndex = 0;
//
// tbUsername
//
this.tbUsername.Dock = System.Windows.Forms.DockStyle.Fill;
this.tbUsername.Location = new System.Drawing.Point(298, 47);
this.tbUsername.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.tbUsername.Name = "tbUsername";
this.tbUsername.Size = new System.Drawing.Size(611, 27);
this.tbUsername.TabIndex = 2;
//
// label3
//
this.label3.AutoSize = true;
this.label3.Dock = System.Windows.Forms.DockStyle.Fill;
this.label3.Location = new System.Drawing.Point(5, 84);
this.label3.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(283, 42);
this.label3.TabIndex = 5;
this.label3.Text = "Password";
this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// tbPassword
//
this.tbPassword.Dock = System.Windows.Forms.DockStyle.Fill;
this.tbPassword.Location = new System.Drawing.Point(298, 89);
this.tbPassword.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.tbPassword.Name = "tbPassword";
this.tbPassword.Size = new System.Drawing.Size(611, 27);
this.tbPassword.TabIndex = 4;
this.tbPassword.UseSystemPasswordChar = true;
//
// btnOK
//
this.btnOK.Anchor = System.Windows.Forms.AnchorStyles.Right;
this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK;
this.btnOK.Location = new System.Drawing.Point(337, 16);
this.btnOK.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.btnOK.Name = "btnOK";
this.btnOK.Size = new System.Drawing.Size(101, 35);
this.btnOK.TabIndex = 6;
this.btnOK.Text = "OK";
this.btnOK.UseVisualStyleBackColor = true;
//
// btnCancel
//
this.btnCancel.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Location = new System.Drawing.Point(474, 16);
this.btnCancel.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(101, 35);
this.btnCancel.TabIndex = 11;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 2;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 32.06997F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 67.93003F));
this.tableLayoutPanel1.Controls.Add(this.label5, 1, 4);
this.tableLayoutPanel1.Controls.Add(this.label1, 0, 0);
this.tableLayoutPanel1.Controls.Add(this.label2, 0, 1);
this.tableLayoutPanel1.Controls.Add(this.label3, 0, 2);
this.tableLayoutPanel1.Controls.Add(this.tbSSURL, 1, 0);
this.tableLayoutPanel1.Controls.Add(this.cbUseSSO, 0, 4);
this.tableLayoutPanel1.Controls.Add(this.tbUsername, 1, 1);
this.tableLayoutPanel1.Controls.Add(this.tbPassword, 1, 2);
this.tableLayoutPanel1.Controls.Add(this.label6, 0, 3);
this.tableLayoutPanel1.Controls.Add(this.tbOTP, 1, 3);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Top;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 5;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(914, 212);
this.tableLayoutPanel1.TabIndex = 12;
//
// label5
//
this.label5.AutoSize = true;
this.label5.Dock = System.Windows.Forms.DockStyle.Fill;
this.label5.Location = new System.Drawing.Point(298, 168);
this.label5.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(611, 44);
this.label5.TabIndex = 15;
this.label5.Text = "For SSO to work, additional IIS configuration is required!";
this.label5.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Dock = System.Windows.Forms.DockStyle.Fill;
this.label1.Location = new System.Drawing.Point(5, 0);
this.label1.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(283, 42);
this.label1.TabIndex = 2;
this.label1.Text = "Secret Server URL";
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// label2
//
this.label2.AutoSize = true;
this.label2.Dock = System.Windows.Forms.DockStyle.Fill;
this.label2.Location = new System.Drawing.Point(5, 42);
this.label2.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(283, 42);
this.label2.TabIndex = 4;
this.label2.Text = "DOMAIN\\Username";
this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// cbUseSSO
//
this.cbUseSSO.AutoSize = true;
this.cbUseSSO.Location = new System.Drawing.Point(5, 173);
this.cbUseSSO.Margin = new System.Windows.Forms.Padding(5, 5, 5, 0);
this.cbUseSSO.Name = "cbUseSSO";
this.cbUseSSO.Size = new System.Drawing.Size(86, 24);
this.cbUseSSO.TabIndex = 14;
this.cbUseSSO.Text = "Use SSO";
this.cbUseSSO.UseVisualStyleBackColor = true;
this.cbUseSSO.CheckedChanged += new System.EventHandler(this.cbUseSSO_CheckedChanged);
//
// label6
//
this.label6.AutoSize = true;
this.label6.Dock = System.Windows.Forms.DockStyle.Fill;
this.label6.Location = new System.Drawing.Point(3, 126);
this.label6.Name = "label6";
this.label6.Size = new System.Drawing.Size(287, 42);
this.label6.TabIndex = 15;
this.label6.Text = "2FA OTP (Optional)";
//
// tbOTP
//
this.tbOTP.Dock = System.Windows.Forms.DockStyle.Fill;
this.tbOTP.Location = new System.Drawing.Point(296, 130);
this.tbOTP.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.tbOTP.Name = "tbOTP";
this.tbOTP.Size = new System.Drawing.Size(615, 27);
this.tbOTP.TabIndex = 5;
//
// tableLayoutPanel2
//
this.tableLayoutPanel2.ColumnCount = 5;
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 106F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 26F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 107F));
this.tableLayoutPanel2.Controls.Add(this.btnOK, 1, 0);
this.tableLayoutPanel2.Controls.Add(this.btnCancel, 3, 0);
this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Bottom;
this.tableLayoutPanel2.Location = new System.Drawing.Point(0, 300);
this.tableLayoutPanel2.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.tableLayoutPanel2.Name = "tableLayoutPanel2";
this.tableLayoutPanel2.RowCount = 1;
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel2.Size = new System.Drawing.Size(914, 67);
this.tableLayoutPanel2.TabIndex = 13;
//
// label4
//
this.label4.AutoSize = true;
this.label4.Dock = System.Windows.Forms.DockStyle.Fill;
this.label4.Location = new System.Drawing.Point(0, 212);
this.label4.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(427, 20);
this.label4.TabIndex = 14;
this.label4.Text = "URL is the base URL, like https://cred.domain.local/SecretServer";
this.label4.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// SSConnectionForm
//
this.AcceptButton = this.btnOK;
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 20F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(914, 367);
this.Controls.Add(this.label4);
this.Controls.Add(this.tableLayoutPanel2);
this.Controls.Add(this.tableLayoutPanel1);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Margin = new System.Windows.Forms.Padding(5, 5, 5, 5);
this.Name = "SSConnectionForm";
this.Text = "Secret Server API Login Data";
this.Activated += new System.EventHandler(this.SSConnectionForm_Activated);
this.tableLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel1.PerformLayout();
this.tableLayoutPanel2.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label label3;
public System.Windows.Forms.TextBox tbSSURL;
public System.Windows.Forms.TextBox tbUsername;
public System.Windows.Forms.TextBox tbPassword;
private System.Windows.Forms.Button btnOK;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
public System.Windows.Forms.CheckBox cbUseSSO;
private System.Windows.Forms.Label label4;
private Label label5;
private Label label6;
public TextBox tbOTP;
}
}

View File

@@ -0,0 +1,35 @@
namespace ExternalConnectors.DSS
{
public partial class SSConnectionForm : Form
{
public SSConnectionForm()
{
InitializeComponent();
}
private void SSConnectionForm_Activated(object sender, EventArgs e)
{
SetVisibility();
if (cbUseSSO.Checked)
btnOK.Focus();
else
{
if (tbPassword.Text.Length == 0)
tbPassword.Focus();
else
tbOTP.Focus();
}
}
private void cbUseSSO_CheckedChanged(object sender, EventArgs e)
{
SetVisibility();
}
private void SetVisibility()
{
bool ch = cbUseSSO.Checked;
tbPassword.Enabled = !ch;
tbUsername.Enabled = !ch;
}
}
}

View File

@@ -0,0 +1,615 @@
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAQAQEAAAAEAIAAoQAAARgAAACAgAAABACAAKBAAAG5AAAAYGAAAAQAgACgJAACWUAAAEBAAAAEA
IAAoBAAAvlkAACgAAABAAAAAgAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD9/f3e/f397vz9
/e79/f3u7ezr7p6Xju5XTEHuNScV7i8hCe4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isf
CO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isf
CO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isf
CO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHAjuKx8I7jYoFu5XTUHumJGO7u3r6+79/f3u/f397v39
/e79/f3e/f399P//////////u7Wy/0M1KP8jEwD/KRgA/ywdBv8tHwj/LiAJ/y4gCf8uIAn/LiAJ/y4g
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8sHAT/KRcA/yAR
AP9DNSX/ubOt/////////////f399Pv8/e7/////m5aK/xoMAP8lFAD/LSAI/y0gCv8uIAr/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cv8uIAr/LB4J/ysfCf8uIQj/JBYA/xoLAP+elYz///////z9/O79/f3uvLWw/xsNAP8rHAX/LiAK/y0f
Cf8tHwj/LR8I/y0fCf8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCf8tHwn/LR8J/y4gCv8pGgP/GgsA/7y3sP/9/f3u6unp7kM0
I/8nFwH/LiEL/yweCP8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAr/LR8J/yIU
AP9DNSL/6+nq7pqUju4fEQD/LiAI/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8uIAf/HxAA/5qTju5YTT7uJRYA/ywgCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/Kx8I/ygXAP9YTkDuOCkV7iweBv8sHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8sHQX/NykW7ioc
CO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8uIAn/Lh8I/yocCO4qHAjuLyEK/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8K/y4gCf8rHAjuKx8I7i4gCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8J/y0fCP8tHwn/LR8J/y0f
CP8tHwn/LR8J/y0fCP8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
CP8tHwj/LR8J/y0fCf8tHwj/LR8J/y0fCf8tHwn/LR8J/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8uIAj/Kx8I7isfCO4uIAn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8I/y8fCP8uHwj/LR8G/yweCP8uHgn/LR4H/yweB/8rHgf/Kx4H/yweCP8sHgj/Kx4I/yse
CP8sHgj/LB4I/yweCP8sHgn/LB4J/yoeCP8qHgj/LB4I/y4fCP8sIQj/LB8H/y0eCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysf
CO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tIAn/LiAK/y0WAf8sFgH/LRsG/ywbBv8uGwX/MR0B/y8cAv8uGwX/LRkG/ysZ
Bf8qGQX/KhkF/ygaBP8oGgX/JxoF/ycaBf8pGgX/KRkG/ykZBv8nGQT/JxkF/ygZCv8jFAb/IBUG/ywe
Cf8tHwf/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/Lh0I/y0XA/84Qyf/P31P/0aSXP9KkVv/UpJT/2OV
Sv93mUf/jJ9A/6CjPP+vpjv/rac4/6ilN/+ppDn/qaQ4/62jOP+uozj/sKI2/7KiNv+yojX/s6Az/7Wi
NP+0oDP/mIUs/1pIFv8lFQb/Kh4I/y4gCf8sHgn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR4I/ygWA/83b0X/SsOE/0zC
f/9QvHf/Ubp0/1e4b/9YtGz/WrJl/2KwZP96tWD/lL9X/7fNUf/b2E//3NhP/9nWTP/X00v/29NL/9vS
Sv/d0Ur/39FJ/9/RSP/j0Ej/5tBD/+3VRP/r0EX/jHYl/yMUBv8sHwb/LB4I/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysfCO4rHwjuLiAJ/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/ysV
AP8zWTb/ScmH/0e8ef9Ltnb/TrRx/1Cyb/9VsGr/Wa1m/1mrYf9dqV3/XqRc/1yfWf9ooVL/g6tQ/7XD
Tf/R0Ur/ys1H/87LR//Oykb/0MlG/9PKRf/Uy0X/18pF/9jJQf/byEH/4MtD/+7VRv9rWxz/HxMC/y8h
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4g
Cf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/Kx8I/y0cBv8vHQj/QKFs/0fEgP9IuHX/TLZ0/0+0cP9UsW3/Vq9o/1isZP9cqWD/X6hc/2Ok
Wf9loVX/aKBT/2iZUP9um0j/obZJ/87RTP/SzUz/yspI/8zLSP/Qykj/0stH/9XJR//VyUT/2clE/9fH
RP/izkn/vao2/ywdBv8rHAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tGwX/MCoV/0azfP9Fvnv/Sbh2/0y2c/9PtG//VbFs/1av
Z/9YrGT/XKlg/2KnXP9kpVr/ZaJX/2mhU/9unkz/bZtK/2qVR/+asUj/zdJM/8nOSf/JzUv/zcxJ/8/L
R//Rykf/0cpE/9XKRP/WykX/3c5G/9DBQv86Kw//KRoH/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LiAI/ysfCO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwr/LRoE/ywsGP9Dt3//R7x6/0q3
dv9OtHL/ULNu/1Wwa/9Xrmb/Watj/12pXv9hplv/ZaRY/2eiVP9qoFH/bp5M/3OcSf9ymUb/b5BC/6m5
SP/L0k7/yM5M/8vNS//LzEn/z8xJ/87LR//Rykf/08tH/9nNSP/RxkT/PS8R/ygZBv8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4g
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LB8K/y4a
A/8uLRj/RLZ9/0e8ev9LtnX/ULNy/1Gybf9VsGr/Wa5l/1mrYv9dqVz/YKZa/2WkVv9oolL/aqBQ/2+e
S/9zm0n/dJpE/3SUQ/+Dmj7/wMxM/8fPTP/IzUz/yc5L/8zNS//LzEr/z8tK/8/LSP/UzUj/0MZG/z0u
EP8oGgX/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8uIAj/Kx8I7isfCO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/ywfCv8uGgP/Li0Y/0W2fP9Iu3n/TLZ0/1Czcf9Rsm3/VbBq/1muZf9aq2L/Xqlc/2Gm
Wv9lpFb/aKJS/2ugUP9wnkv/c5tI/3WaQ/95l0H/do07/6m6Rf/F007/xM5N/8XOTP/IzUz/yM5L/8zN
S//MzEn/0c9J/83HR/89LhD/KBoG/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysfCO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwr/LRsE/zAsGP9HtHr/S7p3/061cv9Qs2//U7Fr/1Wv
af9arGP/XKlh/2GnW/9lpFn/ZaNV/2igUf9unk7/cpxK/3WbRv91mEH/e5Y//3mPOP+YrEH/wtVT/7/O
Uf/CzlD/xc1Q/8TOTP/IzUz/yc5L/87QTP/HyEn/PS8P/ykZBv8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8K/y0bBP8wLBj/R7R6/0u6
d/9OtXL/ULNv/1Oxa/9Vr2n/Wqxj/1ypYf9hp1v/ZaRZ/2WjVf9ooFH/bp5O/3KcSv91m0b/dZhB/3yW
P/97kDj/j6I9/7/VV/+90VH/wNBR/8PPUf/Bz0z/xM5M/8XOS//K0Uz/xclJ/z0vD/8pGQb/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isf
CO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tGwT/MCwa/0qzef9MuXb/TrRx/1Kybv9Tr2n/V61k/12qYP9fqF3/YKZZ/2SkVv9qoVL/bKBP/26d
TP9ym0b/dJpE/3iWQf98lD7/fY43/42gPf+111v/t9RU/7nSU/+/z1L/vtBQ/8HPUP/Cz03/xtFN/8HJ
TP88MA//KhsF/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LiAJ/ysfCO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LRsE/zAsGv9Ks3n/TLl2/060cf9Ssm7/U69p/1etZP9dqmD/X6hd/2Cm
Wf9kpFb/aqFS/2ygT/9unUz/cptG/3SaRP94lkH/fJQ+/36NNv+LpkP/qNxh/6zYWv+v1Ff/ttFV/7zS
Uf/A0VL/wNFP/8TUT//Ayk3/PDAP/yobBP8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4cA/8wLBn/SrN2/024dP9Ps3D/VLFs/1eu
Z/9Zq2T/XKlf/2KnXf9jpVj/ZaNV/2qgUf9unk7/cJ1K/3KbRf92mUP/e5U+/3mUPP9/jDP/ibRM/5zi
aP+g2mD/pdhe/6zVW/+01Ff/utNU/7vSUv/D1VH/vctN/zswEf8pGgb/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8uHQL/MCwY/0qz
df9NuHP/T7Nv/1Wwa/9arWb/Wqtj/1qpX/9jplz/ZKRY/2WiVf9qoFH/b55N/3CdSf9ym0T/d5hC/3yW
Pf98kjn/g441/4nRZP+N4mv/ld5n/5rcZf+g22D/p9hd/63XWf+y1Fb/utZU/7jMTv86MBH/KBoG/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LiAI/ysf
CO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/Lh0D/zAsF/9LsnT/Trdz/1Gybv9WsGr/WK1l/1uqYv9dqV3/YqZb/2WkVv9nolT/aqBQ/2+e
TP9ynEn/c5pD/3aXQf97lT3/gokz/3uvT/986nr/g+Rw/4nibf+O32v/lN5n/5rcY/+h22D/ptdd/6/Y
Wv+xzlX/Oi8R/ygZBv8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4cBf8wLRb/TbFz/1C2cv9TsWz/V7Bo/1etZP9cqmH/Yahb/2Gl
Wv9mo1T/aKFS/2ugTv9wnUr/dJpI/3WZQ/98kz3/gYo3/3WsUP9s7ID/but8/3fneP995XT/geJy/4fh
bf+O32r/lN5m/5rbZP+j3GL/pdJd/zkvEv8pFwX/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHAX/MC0W/02xc/9QtnL/U7Fs/1ev
aP9YrWT/XKpg/1+oW/9hpVr/ZqNV/2mhUv9sn03/cJxJ/3WVRP95kT3/eJZA/2TAYf9Y8Yj/XPOI/2fs
gv9r637/cel7/3bneP995XT/g+Nw/4nibP+P32r/l+Bo/5jXYf85MBL/KhgG/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysfCO4rHwjuLiAJ/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/KhsE/zAs
FP9NsXH/UbVv/1awav9arWf/Wqxi/12oXv9jo1v/Y6FU/2mcUP9tm0r/bptI/2+cSv9rp1P/YMNo/1Do
hf9G/ZX/SfaO/1PyjP9b8Ir/X+6F/2btgv9s637/cul6/3fmeP995XT/g+Nw/4vlbv+O22n/NjAU/yca
BP8uHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4g
Cf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/yobBP8wLBT/TbJy/1G1b/9WsGr/Wq1n/1uoX/9dp13/YKlf/2CvXv9gt2X/XcBq/1XQ
dv9P4YP/SPCO/0T3lP9J85L/R/SR/0n1kf9L9ZD/TfSP/1Tzi/9b8Yj/Yu+E/2ftgP9r637/cel6/3jn
dv+A6XT/geBs/zgxFf8pGwX/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y8fCf8rHAL/LisU/02ucv9Vs23/VLBp/1ipYP9XuGr/TtOA/0fg
if9H5Ir/RuqO/0Xukf9G7pL/Ru2Q/0Ttjf9J7I7/R+6N/0nwj/9L8ZD/S/KR/0nzkP9I85D/TPSN/1X0
if9d8If/Ye6D/2fsf/9s6nz/cux7/3Xic/81MRb/KxkE/ywfCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysfCO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8sHwn/Lh0D/y4gCP9PnWP/Vbdx/1eq
ZP9TwXP/RuSM/0fmjv9F4ov/Q+SJ/0bliv9F5or/RuaM/0bojP9G6or/R+uM/0jtjP9J7o3/SO+O/0nw
j/9I8o//RvKP/0f0j/9K947/S/WN/1Tyif9b8YT/YO6D/2n2hP9kz27/KyEM/y4dBv8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4g
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LB8J/y4e
B/8sFgH/PGU2/1i9dP9Utm3/RN6J/0Leif9J3on/SN+K/0Xgif9H4ov/R+KL/0fjjP9I5Yz/R+eL/0jo
jP9I6o3/SeuO/0jsjf9J7Y7/SO+O/0jwjv9J8Y//S/OQ/0r0kf9M9ZD/T/SO/1Pziv9h/5X/SIJE/yoP
Af8sHwf/LR8K/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8uIAn/Kx8I7isfCO4uIAj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwn/LBwH/ywdCf9IfEf/Tsx8/0Ddif9F14X/RduH/0bdiP9E3on/Rd+K/0fl
jf9I6JD/SOqQ/0jsj/9J7ZD/SO6R/0nvkv9H8JD/SPKR/0n0kf9K84//SPCP/0fxkP9J8pH/SvWS/0r3
lP9M/pn/RKxh/y0YBv8sHAf/LR8J/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LiAI/ysfCO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8sGgX/KhUE/0GsbP9E4Y7/QteG/0TZ
h/9F2oj/RdyI/0Teiv9Ez3//RMp7/0XNe/9Eznr/RM97/0TQff9F0H3/Q9J7/0PTfP9E0n3/SNyC/0zq
iv9M6Yr/TeqL/0zsiv9P8Iz/S7Vp/ywWB/8sGgT/LiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8sHwn/Lh8G/ywR
BP87m2b/ReCR/0PUiP9E1oj/RtiJ/0bZh/9D5o//Nlk1/ywZB/8vJg3/LyYM/y8mDP8vJQ3/LyUN/ywm
DP8sJgz/KhgH/z9sO/9c2Hr/Vslx/1fJcf9YyXL/XdF3/0yRUv8pEwT/LyEJ/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isf
CO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LB8J/y0eBv8sEwX/PJ1n/0Tej/9D0of/Q9SG/0TWiP9F2Ib/QuWO/zVPLv8sDgD/MBsI/y8b
B/8vGwb/LhsE/y4bBP8sGgb/KxoG/ywKAP87Zjr/U+CD/1HQe/9R0Hv/UtB7/1Pcg/9En17/KxMD/y4g
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LiAJ/ysfCO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4fCf8sHwj/LhUD/0CbZP9F2o7/RM+H/0TQhv9F0of/Q9WG/0bk
kf8yVzL/KxEA/y4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tDgD/O3ZF/0zslP9I2ob/SdqG/0na
hv9L5o7/QaNm/ywVAv8uHgf/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8sHwj/LiAJ/ywUAf89jlf/Q9qO/0LN
hf9Dz4T/RNCG/0LRg/9H4I//O4dQ/yoOAP8tIAj/LR8J/y0fCf8tHwn/LR8J/y0fCP8tHgj/LBMC/z+n
ZP9H6pH/RN+K/0Tfiv9E34r/RuyT/z2bXv8uEwH/LR8H/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LB8J/y0f
Cf8rDwD/NXNF/0PZjP9DyYX/RcuF/0XNhv9EzYT/RdSJ/0TFfv8uKRH/LBYC/ywfCP8tHwn/LR8J/ywf
Cf8sHwj/KBAA/zZAIf9H34n/R+GL/0Xdif9F3Yn/Rd2I/0nvlv86gU3/Kg4A/y0eCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysf
CO4rHwjuLiAI/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCf8rHwj/LRMB/zZRLv9H0ov/QsmE/0LKhP9Ey4X/Q8uE/0XMhP9H3I7/Pppj/ysa
Bv8sEQD/Lh0I/y0fB/8tGgX/Kg4A/y0lD/9AuXH/R+qQ/0Lci/9D34n/Rd6J/0PdiP9C65H/M1wx/y8U
Av8uIAf/LR8J/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y4gCP8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LB4I/y4bBv8uJQ//QLV4/0LNh/9Bx4P/Q8iE/0PJ
g/9Ey4P/RMuG/0Xajv9EpGn/MEoo/ysfC/8uFgr/LiUN/zNULf8+vHf/SOWQ/0TYh/9F3Ij/Rt6J/0fe
i/9E44z/Rc9//y0qDv8vGQX/LSAH/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCf8tHwn/KxEA/zt2
Sv9F047/QcOC/0PFg/9Cx4L/RMmD/0PKgv9EzIP/RdaL/0fUif8/t3P/Pqpv/0C+dv9H3I//Rt6O/0HW
hv9E1oj/RdiI/0Tah/9G3In/R+6U/zuET/8sDwD/LR8J/y0fCf8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LiAI/ysfCO4rHwjuLiAJ/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0aBP8wKhH/QLV4/0HKhv9BxIP/QsWB/0LHgv9CyIL/QsmD/0XKg/9FzoT/Q9KH/0LY
iP9G14r/Q9CH/0XRhf9F0oX/RNSI/0LXiP9C14X/ReGM/0XOgv8uKxT/KxgF/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4g
Cf8rHwjuKx8I7i4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwf/LRYA/zRTL/9Dy4v/QcWG/0HCgf9CxIL/QsSC/0PE
g/9CxoP/Q8iC/0TKhP9Dy4T/RsyF/0PNhf9Cz4b/RNCH/0XSiP9H0oj/QtiJ/0jikP8yWjX/KxAA/ywg
B/8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isfCO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LSAH/y8gBv8vEgD/OWlC/0PL
if8+xYP/P8KA/0DCgf9Bw4L/QsaC/0LHgf9DyIL/QsqC/0PLg/9CzIT/Q82F/0TOhf9Fz4X/RdaK/0fg
lf85dkf/KhEA/yseB/8uHwj/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysfCO4rHwjuLiAJ/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8vHwn/Kh4I/y8WAP82Yzj/P7+C/0LLiP9CwoL/QMGC/0HFhP9BxIH/Q8WD/0PGhP9Ex4X/RMmE/0bK
hf9DzYT/QtyN/0nTiP86aj3/LRIA/y4eCP8sIAr/Lh8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuKx8I7i4g
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8rIAj/KhQA/zU/IP9Bl2X/RseI/0LLiv8/x4b/QMaD/0DF
g/9CxoT/Q8mG/0LOh/9G1o3/RtSK/0WjaP8yQiD/LBEA/y8eCP8sIAn/LR8I/y0fCP8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8uIAn/Kx8I7isfCO4uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8sHwn/LR4I/y4gCv8sFQD/KxoE/zRJ
Kf9Cg1P/Q6tv/0O6ev9EwoL/QsOC/0a+fv9FsHD/QoZS/zZJJ/8sGgT/KxMB/y4fCf8wIAj/Lh8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LiAJ/ysfCO4rHwjuLiAI/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8J/y0f
Cf8tHwj/LR8J/y0dCP8rEwL/LBQA/zEjCf8yLxP/Njce/zQ3H/8yMBT/MiMJ/y8UAP8rEgD/LB0H/y4g
Cf8tHwn/LB8J/y0fCf8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y4gCP8rHwjuKx8I7i4gCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAI/yseB/8rHQj/LBoF/ysXAv8sGAP/KxsF/ygb
Bv8wHgf/LR8J/y4fCv8uIAr/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/Kx8I7isf
CO4uIAj/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8I/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LB8J/y0fCf8tHwj/LR8I/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LiAJ/ysfCO4rHwjuLyEJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHwjuLh8J7i8hCP8tIAj/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwj/LyEJ7jgqFu4sHgX/LR8H/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/LB4G/zUn
FO5YTT7uJhUA/y4gCv8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LSAL/yoZAP9XTD7um5SO7iMRAP8vIAn/LR8J/y0fCf8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwn/LR8I/ywfBv8iEgD/npeO7urq6e5ENSX/JBQA/y0hCP8tIAf/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/Kx0H/y4gCv8lFQD/QzYm/+vr6e79/f3uvLax/xoM
AP8pGwL/LiEI/y0fCf8tHwj/LR8J/y0fCf8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0f
CP8tHwj/LR8I/y0fCP8tHwj/LR8I/y0fCP8tHwj/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHQX/Gw0A/723
tP/9/f3u+vv87v////+ak4n/GQsA/yQUAP8uHwn/LR8J/y0fCP8uIAn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0eCf8sHgj/LB8J/y4g
B/8mFwD/Gw0A/5qViv//////+/z87v39/fT//////////7q1sP9DNCb/IxEA/ycWAP8rHgX/LiAH/y8h
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/zAh
Cv8uHwj/LB0G/yQWAP8fEQD/QzQm/7q0r/////////////39/fT9/f3e/f397vz9/e79/f3u6+rp7puU
ju5YTj/uOCkW7i4fCu4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isf
CO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isf
CO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isfCO4rHwjuKx8I7isf
CO4rHwjuKx8I7isfCO4rHwjuKyAI7jcpFu5YTj7umpOO7uvr6u79/f3u/Pz97v39/e79/f3eKAAAACAA
AABAAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP39/dzt6+vtdGte7TQmFu0rHQjtLR8I7S0f
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0f
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7SsdBu00Jhbtc2le7e3r6e39/f3c6ubl81ZKOP8dDgD/LB4G/y4g
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LyAK/ywdBf8dDgD/VUk4/+rn5vN0a13tHg4A/y4g
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8dDQD/dWtd7TQm
Eu0rHQb/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/ywd
Bf80JhLtKx0G7S8gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LiAJ/ysdBu0tHwjtLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwj/LBcC/ysT
AP8pFAH/JhMC/yISA/8hEQP/IBID/yASA/8gEgT/HxIE/x8RBP8jFgb/LB4I/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR4I/ywY
BP81QiP/Olw1/0RbLP9WXSb/bGMj/3RnIP9vZCD/bmIg/3FiIP9yYh7/dWMf/1ZFFv8lFwb/LB4I/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8rFgL/N2c//0vDgf9SwHr/W7pv/2a2Zf9+t13/p8RT/9bYT//g20z/3tZL/+LWSv/p10f/69RF/39r
Iv8iFQT/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/y0fCO0tHwjtLiAJ/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LBwG/y4lD/9Esnb/Sr55/1Kybv9XrWf/Xahe/1+iWP9imlH/faJJ/8DHSv/Pz0r/z8tI/9PK
Rf/f0Uf/z75A/zQmC/8pGwj/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8sGgX/Ly0W/0S0ef9NuHb/U7Fs/1itZP9ep1z/ZqNW/2yfTv9slUb/hqFE/8fP
TP/Lzkv/zcxJ/9TOSf/Ow0T/PS4P/ygaB/8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4g
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/ywbBf8wLBX/RrF1/0+4dP9UsGv/Wqxj/2CmWv9molT/b55N/3WZ
RP93kTz/r8BI/8fST//Hzkz/zdFM/8bERv87LQ7/KRoH/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/y0f
CO0tHwjtLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LBsF/zAsFf9Jr3T/ULdz/1WvaP9dqmD/YqVZ/2mg
Uv9wnEv/dplD/3mOOf+iuEn/wNZV/8HPT//H0k7/v8ZJ/zstDv8pGgf/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8sGwX/MCwV/0mvc/9RtnH/Vq1m/16p
Xv9jpFf/bKBQ/3CcSP93mEH/fIw2/5bCU/+q3F//tdNW/8HWUv+7yEv/Oi4O/ykaB/8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/ywbBf8wLRT/S65w/1O1
bv9ZrGT/YKdc/2ajVv9sn0//cpxG/3uPOf+AmT7/hd5t/5Hhav+e22L/rdtc/6/KUf85LQ//KRoH/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LiAJ/y0fCO0tHwjtLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LBsG/zAs
E/9NrW7/VrRt/1qrYf9ipFj/aZ5R/3GXR/94kj//dKFI/2bddv9t74H/eeZ2/4Xib/+U4mr/mdFe/zgt
EP8qGQb/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8sGwb/LiwU/06ubf9ZsGn/Xahf/2CqXv9jr13/YLtj/1jRdP9K8o7/SvqT/1byi/9j7oP/b+p8/3zq
d/+D22r/NjAS/yoaBv8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/ywcBv8vJw7/Ualp/1e3bP9N0n//R+GJ/0bojf9E75H/RPKR/0jwj/9J8ZD/R/OR/0z1
jv9W8on/ZfWF/2vhd/8yKg//KxoG/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/y0fCO0tHwjtLiAJ/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8I/ysXAv9CaTr/TNSC/0PijP9F34n/RueP/0n0lv9J95b/SvqY/0n9
mP9J+5b/R/OR/0j4lP9Q/pb/RI5N/ysTAv8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/LR8I7S0f
CO0uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LRwG/ywbB/9Aunf/RN6M/0bijf9At3D/OXJC/zp7
Rf86fEb/NnhD/z+SUv9S3H//U+KD/068bf8sGgj/LRsH/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4g
Cf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHQf/LRwI/0CydP9E2oz/RuWR/zqO
Vv8qAAD/LA8A/ywOAP8pAgD/Mzkb/0/bhP9Q24T/SLRs/y0dCP8tHQf/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LiAJ/y0fCO0tHwjtLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0eCP8tGQT/PqZp/0TW
jP9F1or/Qbh0/ywbBv8sFQL/LBwG/ykGAP86eEX/Ru6U/0Xpkf9At3H/LRkE/y0eB/8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/ysR
AP86fk//RNeP/0TJg/9G14z/PI1Y/y4pEP8tHQn/M1ky/0TWhv9F4Yz/R/CV/zqOU/8sEAD/LR8I/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LBcD/zE5HP9CxoX/QsqF/0PIgv9F1ov/QsR+/0C7d/9F2oz/RNmK/0Xdiv9G34z/MT0d/ywW
A/8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/y0fCO0tHwjtLiAJ/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LBQA/zdmPf9C0Iz/QcmG/0LFgv9DzIX/RNGI/0POhv9F14v/R+WU/zdv
Qv8qEQD/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHgj/LBUB/zVeNv9Ct3v/Q8+M/0LRjP9E1I7/RteO/0TE
fv84ZDr/LRQB/y0eCP8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4g
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHgj/LBMA/y8qEf84Xjb/O3lM/zt6
Tf86YDf/MCoR/ysSAP8tHgj/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LiAJ/y0f
CO0tHwjtLiAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LBoF/ysT
AP8sEQD/KxEA/ysSAP8sGgX/LSAJ/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8uIAn/LR8I7SweBu0vIAn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y4gCf8rHQbtNCYT7SwdBv8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LB4G/zQmEu11a17tHQ4A/y4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0gCf8dDgD/dmxe7ern5vNVSTf/Hg4A/ywdBv8uIAn/LiAJ/y4g
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8rHQX/Hg4A/1ZKOP/q5+fz/f393Ozr6e10aV7tNSYU7Swe
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0f
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLB4I7TQmFO1zaV7t7evp7f39/dwoAAAAGAAAADAA
AAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/f393JSPhO00JhbtKx0G7S0fCO0tHwjtLR8I7S0f
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0rHQbtMyUW7ZSO
g+39/f3ckouC8yESAP8qHAT/LyAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8vIAn/KhwE/yERAP+Si4LzMyUR7SocBP8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/yocBP8zJRLtKx0F7S4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0eCP8sGgX/KxoG/yka
B/8oGgf/KBoH/ygaB/8nGgf/KBoH/yweCP8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8rHQXtLR8I7S4g
Cf8tHwn/LR8J/y0fCf8tHwn/LRwH/y0aBf8wLRP/Ni8Q/0ExDv9FMw3/PjAN/z4vDv8/MA3/PS4N/ycZ
B/8qHAj/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHQf/LB8K/z6E
VP9NsnL/Wapi/3GoVv+bskv/x8VG/87FRP/PwkL/2cZB/6GMLf8tHwj/Kx0I/y0fCf8tHwn/LR8J/y4g
Cf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8sFgH/MkYn/0nIhP9Rt3L/WK9m/16nXf9jnlP/iKlK/8nO
S//T0Er/2M9H/+jYSv9XSRf/IxUF/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0f
Cf8sFQH/M0os/0vBfv9Tsm3/Wqxi/2OlWP9sn07/bJJD/5uuRP/M0k3/y81L/9nWTP9cTxv/IhQF/y0f
Cf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8rFQH/NEkq/02+e/9UsGr/Xalg/2Wj
Vv9unkz/dJRB/4ufPv+/01P/xM9O/8/XT/9YThr/IxQE/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4g
Cf8tHwn/LR8J/y0fCf8rFgH/NUkp/068ef9Xrmf/X6dc/2iiVP9wnUn/eI86/4ilRP+k3mL/tNVX/8ba
U/9WTxv/JBQE/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8rFgH/Nkgo/1K7
dv9ZrWT/YqRZ/2ycTf93kj//epU+/3bVa/+D53T/lN5n/63hYv9RTx//JRMD/y0fCf8tHwn/LR8J/y4g
Cf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8qFQL/NUop/1a6c/9cqWH/Yatd/2SwW/9gv2b/UeeE/1L4
kP9k7oL/dOh5/4vvdf9JViX/JxIC/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0f
Cf8rFwP/NT4e/1a5cP9N0H3/R+GI/0fwkv9F+5n/SPuY/0j7lv9J95H/VfaM/2f6iP88SyH/KhQC/y0f
Cf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHgj/LBUC/z6ETv9E6JL/Rd+L/0LG
ef9DwHP/QsR1/0LHdP9L4oX/T/aS/0KaV/8rFgT/LR4I/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4g
Cf8tHwn/LR8J/y0fCf8tHwn/Kw8A/zVgOv9F4pL/Rd+N/zJGJP8tEAD/LRoF/ywaBv9Lum3/VOeL/zle
M/8qDwD/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHwn/KxMA/zVX
Mf9F2I7/RtuO/zl3R/8qBQD/KQQA/zJDIv9F4Iv/RvCW/zVfNf8rEQD/LR8J/y0fCf8tHwn/LR8J/y4g
Cf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHwn/LBoF/zAtE/9Cv3//RNCJ/0TNhf86g1D/N29C/0LF
fP9G6JH/RNeF/y8wFP8sGQT/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/ywTAP82Yzv/QtKN/0PPif9F2I3/Rd2Q/0XekP9H5ZP/N2w//ysRAP8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0eCP8sFQH/NVs1/0Cu
dP9CxoT/RMqG/0O3dv83YTj/LBQB/y0eCP8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtLR8I7S4g
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHgj/LBMA/y4iC/8zPB7/Mj0e/y8hCv8rEgD/LR4I/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4gCf8tHwjtKx0F7S4gCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/ywcB/8sFwP/LBcD/ywcB/8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y4g
Cf8rHQbtNCUS7SocBP8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0f
Cf8tHwn/LR8J/y0fCf8tHwn/LR8J/y0fCf8tHwn/LR8J/yocBP80JhLtkouD8yERAP8qHAT/LyAJ/y4g
Cf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4gCf8uIAn/LiAJ/y4g
Cf8vIAn/KhwE/yERAP+VjITz/f393JSOhO00JhbtKx0G7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0f
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0rHQbtMyUW7ZSOg+39/f3cKAAAABAA
AAAgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL65stw/MR/tKBkF7S0fCO0tHwjtLR8I7S0f
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0oGQXtPzEf7b65stw8MR3zJxgA/y8gCf8uIAn/LiAJ/y4g
Cf8uHwn/Lh8J/y4fCf8uHwn/LiAJ/y4gCf8uIAn/LyAJ/ycYAP88MR3zKBoE7S8gCf8tHwn/LSAJ/ywa
Bf8rFAH/LBcD/ysXBP8kFQT/IxUF/yATBP8oGgf/LiAJ/y0fCf8vIQn/KBkE7S0fCO0uIAn/LR8J/ywa
Bf8wMxj/QYNR/1OFSP94iTr/npg0/6aYM/+hji7/RDQP/ycaB/8tHwn/LiAJ/y0fCO0tHwjtLiAJ/y0f
Cf8rEQD/OW9F/1LNhP9dtGj/aahY/5u2TP/Z2U3/7+ZP/4V2KP8eEAT/LR8J/y4gCf8tHwjtLR8I7S4g
Cf8tHwn/KxIA/ztvRf9Tv3f/Xqhd/2ufT/91kj7/tMNK/9fgU/+AeSr/IBAD/y0fCf8uIAn/LR8I7S0f
CO0uIAn/LR8J/ysTAP89bUL/Vrxz/2OjWP9ylkX/fZQ7/5XTX/+952D/d3ot/yEQA/8tHwn/LiAJ/y0f
CO0tHwjtLiAJ/y0fCf8qEwD/P29C/1y6bv9iqlv/Z7Fa/1zad/9l8oX/ifZ6/2OGPP8lDgH/LR8J/y4g
Cf8tHwjtLR8I7S4gCf8tHwn/KhQB/zxULf9P0oD/SOSL/0b1lf9F/Jn/SPaS/1j7kP9DbTb/KRAB/y0f
Cf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0cB/8uIwz/QcuC/0HCef8zRSL/MD0e/0SmYP9M0n7/LSMN/ywc
Bv8tHwn/LiAJ/y0fCO0tHwjtLiAJ/y0fCf8tHQj/LRsG/0Cxc/9Ez4b/MDca/yweCf9Cxnr/Qsd6/y0b
Bv8tHQj/LR8J/y4gCf8tHwjtLR8I7S4gCf8tHwn/LR8J/ywUAf81XTf/RNiR/0TTiv9E1In/R+2Y/zZn
Ov8rEgH/LR8J/y0fCf8uIAn/LR8I7S0fCO0uIAn/LR8J/y0fCf8tHgj/LBcC/zRWMv8+nWb/P6Jo/zZb
NP8sFgL/LR4I/y0fCf8tHwn/LiAJ/y0fCO0oGgTtLyEJ/y0fCf8tHwn/LR8J/y0fCP8sFAH/LBYC/ywW
Av8rEwD/LR4I/y0fCf8tHwn/LR8J/y8hCf8oGQTtPTEd8ycYAP8vIAn/LiAJ/y4gCf8uIAn/LiAJ/y4f
CP8uHwn/LiAJ/y4gCf8uIAn/LiAJ/y8gCf8nGAD/PTEd87u5stw/MR/tKBkF7S0fCO0tHwjtLR8I7S0f
CO0tHwjtLR8I7S0fCO0tHwjtLR8I7S0fCO0oGQXtPzEf7b65stwAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
</value>
</data>
</root>

View File

@@ -0,0 +1,383 @@
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v13.14.8.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended."
#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword."
#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?'
#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ...
#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..."
#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'"
#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant"
namespace SecretServerAuthentication.DSS
{
using System = global::System;
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.14.8.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")]
public partial class OAuth2ServiceClient
{
private string _baseUrl = "";
private System.Net.Http.HttpClient _httpClient;
private System.Lazy<Newtonsoft.Json.JsonSerializerSettings> _settings;
public OAuth2ServiceClient(string baseUrl, System.Net.Http.HttpClient httpClient)
{
BaseUrl = baseUrl;
_httpClient = httpClient;
_settings = new System.Lazy<Newtonsoft.Json.JsonSerializerSettings>(CreateSerializerSettings);
}
private Newtonsoft.Json.JsonSerializerSettings CreateSerializerSettings()
{
var settings = new Newtonsoft.Json.JsonSerializerSettings();
UpdateJsonSerializerSettings(settings);
return settings;
}
public string BaseUrl
{
get { return _baseUrl; }
set { _baseUrl = value; }
}
protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _settings.Value; } }
partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings);
partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url);
partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder);
partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response);
/// <summary>Retrieve or Refresh Access Token</summary>
/// <param name="grant_type">Authentication grant type. Use 'password' when authenticating, and 'refresh_token' when refreshing a token.</param>
/// <param name="username">Secret Server authentication username. Required when authenticating.</param>
/// <param name="password">Secret Server authentication password. Required when authenticating.</param>
/// <param name="refresh_token">The refresh token. Required when refreshing a token.</param>
/// <returns>Successful retrieval of an access token</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public System.Threading.Tasks.Task<TokenResponse> AuthorizeAsync(Grant_type grant_type, string username, string password, string refresh_token, string OTP)
{
return AuthorizeAsync(grant_type, username, password, refresh_token, System.Threading.CancellationToken.None, OTP);
}
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <summary>Retrieve or Refresh Access Token</summary>
/// <param name="grant_type">Authentication grant type. Use 'password' when authenticating, and 'refresh_token' when refreshing a token.</param>
/// <param name="username">Secret Server authentication username. Required when authenticating.</param>
/// <param name="password">Secret Server authentication password. Required when authenticating.</param>
/// <param name="refresh_token">The refresh token. Required when refreshing a token.</param>
/// <returns>Successful retrieval of an access token</returns>
/// <exception cref="ApiException">A server side error occurred.</exception>
public async System.Threading.Tasks.Task<TokenResponse> AuthorizeAsync(Grant_type grant_type, string username, string password, string refresh_token, System.Threading.CancellationToken cancellationToken, string OTP)
{
var urlBuilder_ = new System.Text.StringBuilder();
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/oauth2/token");
var client_ = _httpClient;
var disposeClient_ = false;
try
{
using (var request_ = new System.Net.Http.HttpRequestMessage())
{
var keyValues_ = new System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string, string>>();
if (grant_type == null)
throw new System.ArgumentNullException("grant_type");
else
keyValues_.Add(new System.Collections.Generic.KeyValuePair<string, string>("grant_type", ConvertToString(grant_type, System.Globalization.CultureInfo.InvariantCulture)));
if (username != null)
keyValues_.Add(new System.Collections.Generic.KeyValuePair<string, string>("username", ConvertToString(username, System.Globalization.CultureInfo.InvariantCulture)));
if (password != null)
keyValues_.Add(new System.Collections.Generic.KeyValuePair<string, string>("password", ConvertToString(password, System.Globalization.CultureInfo.InvariantCulture)));
if (refresh_token != null)
keyValues_.Add(new System.Collections.Generic.KeyValuePair<string, string>("refresh_token", ConvertToString(refresh_token, System.Globalization.CultureInfo.InvariantCulture)));
if (OTP != null)
request_.Headers.Add("OTP", ConvertToString(OTP, System.Globalization.CultureInfo.InvariantCulture));
request_.Content = new System.Net.Http.FormUrlEncodedContent(keyValues_);
request_.Method = new System.Net.Http.HttpMethod("POST");
request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
PrepareRequest(client_, request_, urlBuilder_);
var url_ = urlBuilder_.ToString();
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
PrepareRequest(client_, request_, url_);
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
var disposeResponse_ = true;
try
{
var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
if (response_.Content != null && response_.Content.Headers != null)
{
foreach (var item_ in response_.Content.Headers)
headers_[item_.Key] = item_.Value;
}
ProcessResponse(client_, response_);
var status_ = (int)response_.StatusCode;
if (status_ == 200)
{
var objectResponse_ = await ReadObjectResponseAsync<TokenResponse>(response_, headers_, cancellationToken).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
return objectResponse_.Object;
}
else
if (status_ == 400)
{
var objectResponse_ = await ReadObjectResponseAsync<TokenErrorResponse>(response_, headers_, cancellationToken).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
throw new ApiException<TokenErrorResponse>("An error occurred", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
}
else
{
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
}
}
finally
{
if (disposeResponse_)
response_.Dispose();
}
}
}
finally
{
if (disposeClient_)
client_.Dispose();
}
}
protected struct ObjectResponseResult<T>
{
public ObjectResponseResult(T responseObject, string responseText)
{
this.Object = responseObject;
this.Text = responseText;
}
public T Object { get; }
public string Text { get; }
}
public bool ReadResponseAsString { get; set; }
protected virtual async System.Threading.Tasks.Task<ObjectResponseResult<T>> ReadObjectResponseAsync<T>(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> headers, System.Threading.CancellationToken cancellationToken)
{
if (response == null || response.Content == null)
{
return new ObjectResponseResult<T>(default(T), string.Empty);
}
if (ReadResponseAsString)
{
var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
try
{
var typedBody = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(responseText, JsonSerializerSettings);
return new ObjectResponseResult<T>(typedBody, responseText);
}
catch (Newtonsoft.Json.JsonException exception)
{
var message = "Could not deserialize the response body string as " + typeof(T).FullName + ".";
throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception);
}
}
else
{
try
{
using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
using (var streamReader = new System.IO.StreamReader(responseStream))
using (var jsonTextReader = new Newtonsoft.Json.JsonTextReader(streamReader))
{
var serializer = Newtonsoft.Json.JsonSerializer.Create(JsonSerializerSettings);
var typedBody = serializer.Deserialize<T>(jsonTextReader);
return new ObjectResponseResult<T>(typedBody, string.Empty);
}
}
catch (Newtonsoft.Json.JsonException exception)
{
var message = "Could not deserialize the response body stream as " + typeof(T).FullName + ".";
throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception);
}
}
}
private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value == null)
{
return "";
}
if (value is System.Enum)
{
var name = System.Enum.GetName(value.GetType(), value);
if (name != null)
{
var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name);
if (field != null)
{
var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute))
as System.Runtime.Serialization.EnumMemberAttribute;
if (attribute != null)
{
return attribute.Value != null ? attribute.Value : name;
}
}
var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo));
return converted == null ? string.Empty : converted;
}
}
else if (value is bool)
{
return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant();
}
else if (value is byte[])
{
return System.Convert.ToBase64String((byte[])value);
}
else if (value.GetType().IsArray)
{
var array = System.Linq.Enumerable.OfType<object>((System.Array)value);
return string.Join(",", System.Linq.Enumerable.Select(array, o => ConvertToString(o, cultureInfo)));
}
var result = System.Convert.ToString(value, cultureInfo);
return result == null ? "" : result;
}
}
/// <summary>API access token response</summary>
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
public partial class TokenResponse
{
/// <summary>Authentication token</summary>
[Newtonsoft.Json.JsonProperty("access_token", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public string Access_token { get; set; }
/// <summary>Authentication token type</summary>
[Newtonsoft.Json.JsonProperty("token_type", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public TokenResponseToken_type Token_type { get; set; }
private string _Expires_in;
/// <summary>Authentication token expiration time, in seconds</summary>
[Newtonsoft.Json.JsonProperty("expires_in", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
// public string Expires_in { get; set; }
public string Expires_in
{
get { return _Expires_in; }
set
{
_Expires_in = value;
Expires_on = DateTime.UtcNow.AddSeconds(Double.Parse(value) - 60);
}
}
/// <summary>Authentication token expiration time in UTC</summary>
public DateTime Expires_on { get; set; }
/// <summary>Refresh token. This is only provided when the server is set to allow refresh tokens for web services and when the session timeout duration is not set to Unlimited.</summary>
[Newtonsoft.Json.JsonProperty("refresh_token", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Refresh_token { get; set; }
}
/// <summary>API access token error response</summary>
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
public partial class TokenErrorResponse
{
/// <summary>Authentication token</summary>
[Newtonsoft.Json.JsonProperty("message", Required = Newtonsoft.Json.Required.Always)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
public string Message { get; set; }
}
/// <summary>Authentication grant type. Use 'password' when authenticating, and 'refresh_token' when refreshing a token.</summary>
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
public enum Grant_type
{
[System.Runtime.Serialization.EnumMember(Value = @"password")]
Password = 0,
[System.Runtime.Serialization.EnumMember(Value = @"refresh_token")]
Refresh_token = 1,
}
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v11.0.0.0)")]
public enum TokenResponseToken_type
{
[System.Runtime.Serialization.EnumMember(Value = @"bearer")]
Bearer = 0,
}
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.14.8.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")]
public partial class ApiException : System.Exception
{
public int StatusCode { get; private set; }
public string Response { get; private set; }
public System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> Headers { get; private set; }
public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> headers, System.Exception innerException)
: base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + ((response == null) ? "(null)" : response.Substring(0, response.Length >= 512 ? 512 : response.Length)), innerException)
{
StatusCode = statusCode;
Response = response;
Headers = headers;
}
public override string ToString()
{
return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString());
}
}
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.14.8.0 (NJsonSchema v10.5.2.0 (Newtonsoft.Json v11.0.0.0))")]
public partial class ApiException<TResult> : ApiException
{
public TResult Result { get; private set; }
public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary<string, System.Collections.Generic.IEnumerable<string>> headers, TResult result, System.Exception innerException)
: base(message, statusCode, response, headers, innerException)
{
Result = result;
}
}
}
#pragma warning restore 1591
#pragma warning restore 1573
#pragma warning restore 472
#pragma warning restore 114
#pragma warning restore 108
#pragma warning restore 3016

View File

@@ -0,0 +1,341 @@
using Microsoft.Win32;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using SecretServerAuthentication.DSS;
using SecretServerRestClient.DSS;
using System.Security.Cryptography;
namespace ExternalConnectors.DSS;
public class SecretServerInterface
{
private static class SSConnectionData
{
public static string ssUsername = "";
public static string ssPassword = "";
public static string ssUrl = "";
public static string ssOTP = "";
public static bool ssSSO = false;
public static bool initdone = false;
//token
public static string ssTokenBearer = "";
public static DateTime ssTokenExpiresOn = DateTime.UtcNow;
public static string ssTokenRefresh = "";
public static void Init()
{
if (initdone == true)
return;
RegistryKey key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\mRemoteSSInterface");
try
{
// display gui and ask for data
SSConnectionForm f = new();
string? un = key.GetValue("Username") as string;
f.tbUsername.Text = un ?? "";
f.tbPassword.Text = SSConnectionData.ssPassword; // in OTP refresh cases, this value might already be filled
string? url = key.GetValue("URL") as string;
if (url == null || !url.Contains("://"))
url = "https://cred.domain.local/SecretServer";
f.tbSSURL.Text = url;
var b = key.GetValue("SSO");
if (b == null || (string)b != "True")
ssSSO = false;
else
ssSSO = true;
f.cbUseSSO.Checked = ssSSO;
// show dialog
while (true)
{
_ = f.ShowDialog();
if (f.DialogResult != DialogResult.OK)
return;
// store values to memory
ssUsername = f.tbUsername.Text;
ssPassword = f.tbPassword.Text;
ssUrl = f.tbSSURL.Text;
ssSSO = f.cbUseSSO.Checked;
ssOTP = f.tbOTP.Text;
// check connection first
try
{
if (TestCredentials() == true)
{
initdone = true;
break;
}
}
catch (Exception)
{
MessageBox.Show("Test Credentials failed - please check your credentials");
}
}
// write values to registry
key.SetValue("Username", ssUsername);
key.SetValue("URL", ssUrl);
key.SetValue("SSO", ssSSO);
}
catch (Exception)
{
throw;
}
finally
{
key.Close();
}
}
}
private static bool TestCredentials()
{
if (SSConnectionData.ssSSO)
{
// checking creds doesn't really make sense here, as we can't modify them anyway if something is wrong
return true;
}
else
{
if (!String.IsNullOrEmpty(GetToken()))
{
return true;
}
else
{
return false;
}
}
}
private static SecretsServiceClient ConstructSecretsServiceClient()
{
string baseURL = SSConnectionData.ssUrl;
if (SSConnectionData.ssSSO)
{
// REQUIRES IIS CONFIG! https://docs.thycotic.com/ss/11.0.0/api-scripting/webservice-iwa-powershell
var handler = new HttpClientHandler() { UseDefaultCredentials = true };
var httpClient = new HttpClient(handler);
{
// Call REST API:
return new SecretsServiceClient($"{baseURL}/winauthwebservices/api", httpClient);
}
}
else
{
var httpClient = new HttpClient();
{
var token = GetToken();
// Set credentials (token):
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
// Call REST API:
return new SecretsServiceClient($"{baseURL}/api", httpClient);
}
}
}
private static void FetchSecret(int secretID, out string secretUsername, out string secretPassword, out string secretDomain, out string privatekey)
{
var client = ConstructSecretsServiceClient();
SecretModel secret = client.GetSecretAsync(false, true, secretID, null).Result;
// clear return variables
secretDomain = "";
secretUsername = "";
secretPassword = "";
privatekey = "";
string privatekeypassphrase = "";
// parse data and extract what we need
foreach (var item in secret.Items)
{
if (item.FieldName.ToLower().Equals("domain"))
secretDomain = item.ItemValue;
else if (item.FieldName.ToLower().Equals("username"))
secretUsername = item.ItemValue;
else if (item.FieldName.ToLower().Equals("password"))
secretPassword = item.ItemValue;
else if (item.FieldName.ToLower().Equals("private key"))
{
client.ReadResponseNoJSONConvert = true;
privatekey = client.GetFieldAsync(false, false, secretID, "private-key").Result;
client.ReadResponseNoJSONConvert = false;
}
else if (item.FieldName.ToLower().Equals("private key passphrase"))
privatekeypassphrase = item.ItemValue;
}
// need to decode the private key?
if (!string.IsNullOrEmpty(privatekeypassphrase))
{
try
{
var key = DecodePrivateKey(privatekey, privatekeypassphrase);
privatekey = key;
}
catch(Exception)
{
}
}
// conversion to putty format necessary?
if (!string.IsNullOrEmpty(privatekey) && !privatekey.StartsWith("PuTTY-User-Key-File-2"))
{
try
{
RSACryptoServiceProvider key = ImportPrivateKey(privatekey);
privatekey = PuttyKeyFileGenerator.ToPuttyPrivateKey(key);
}
catch (Exception)
{
}
}
}
#region PUTTY KEY HANDLING
// decode rsa private key with encryption password
private static string DecodePrivateKey(string encryptedPrivateKey, string password)
{
TextReader textReader = new StringReader(encryptedPrivateKey);
PemReader pemReader = new(textReader, new PasswordFinder(password));
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
TextWriter textWriter = new StringWriter();
var pemWriter = new PemWriter(textWriter);
pemWriter.WriteObject(keyPair.Private);
pemWriter.Writer.Flush();
return ""+textWriter.ToString();
}
private class PasswordFinder : IPasswordFinder
{
private string password;
public PasswordFinder(string password)
{
this.password = password;
}
public char[] GetPassword()
{
return password.ToCharArray();
}
}
// read private key pem string to rsacryptoserviceprovider
public static RSACryptoServiceProvider ImportPrivateKey(string pem)
{
PemReader pr = new(new StringReader(pem));
AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr.ReadObject();
RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)KeyPair.Private);
RSACryptoServiceProvider rsa = new();
rsa.ImportParameters(rsaParams);
return rsa;
}
#endregion
#region TOKEN
private static string GetToken()
{
// if there is no token, fetch a fresh one
if (String.IsNullOrEmpty(SSConnectionData.ssTokenBearer))
{
return GetTokenFresh();
}
// if there is a token, check if it is valid
if (SSConnectionData.ssTokenExpiresOn >= DateTime.UtcNow)
{
return SSConnectionData.ssTokenBearer;
}
else
{
// try using refresh token
using (var httpClient = new HttpClient())
{
var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
TokenResponse token = new();
try
{
token = tokenClient.AuthorizeAsync(Grant_type.Refresh_token, null, null, SSConnectionData.ssTokenRefresh, null).Result;
var tokenResult = token.Access_token;
SSConnectionData.ssTokenBearer = tokenResult;
SSConnectionData.ssTokenRefresh = token.Refresh_token;
SSConnectionData.ssTokenExpiresOn = token.Expires_on;
return tokenResult;
}
catch (Exception)
{
// refresh token failed. clean memory and start fresh
SSConnectionData.ssTokenBearer = "";
SSConnectionData.ssTokenRefresh = "";
SSConnectionData.ssTokenExpiresOn = DateTime.Now;
// if OTP is required we need to ask user for a new OTP
if (!String.IsNullOrEmpty(SSConnectionData.ssOTP))
{
SSConnectionData.initdone = false;
// the call below executes a connection test, which fetches a valid token
SSConnectionData.Init();
// we now have a fresh token in memory. return it to caller
return SSConnectionData.ssTokenBearer;
}
else
{
// no user interaction required. get a fresh token and return it to caller
return GetTokenFresh();
}
}
}
}
}
static string GetTokenFresh()
{
using (var httpClient = new HttpClient())
{
// Authenticate:
var tokenClient = new OAuth2ServiceClient(SSConnectionData.ssUrl, httpClient);
// call below will throw an exception if the creds are invalid
var token = tokenClient.AuthorizeAsync(Grant_type.Password, SSConnectionData.ssUsername, SSConnectionData.ssPassword, null, SSConnectionData.ssOTP).Result;
// here we can be sure the creds are ok - return success state
var tokenResult = token.Access_token;
SSConnectionData.ssTokenBearer = tokenResult;
SSConnectionData.ssTokenRefresh = token.Refresh_token;
SSConnectionData.ssTokenExpiresOn = token.Expires_on;
return tokenResult;
}
}
#endregion
// input must be the secret id to fetch
public static void FetchSecretFromServer(string input, out string username, out string password, out string domain, out string privatekey)
{
// get secret id
int secretID = Int32.Parse(input);
// init connection credentials, display popup if necessary
SSConnectionData.Init();
// get the secret
FetchSecret(secretID, out username, out password, out domain, out privatekey);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>
<UseWindowsForms>True</UseWindowsForms>
<Platforms>x64</Platforms>
<Configurations>Debug;Release;Debug Portable;Release Portable;Deploy to github</Configurations>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release Portable|x64'">
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AWSSDK.Core" Version="3.7.401.4" />
<PackageReference Include="AWSSDK.EC2" Version="3.7.428" />
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<Compile Update="AWS\AWSConnectionForm.cs" />
<Compile Update="CPS\CPSConnectionForm.cs" />
<Compile Update="DSS\SSConnectionForm.cs" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,111 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
namespace ExternalConnectors;
public class PuttyKeyFileGenerator
{
private const int prefixSize = 4;
private const int paddedPrefixSize = prefixSize + 1;
private const int lineLength = 64;
private const string keyType = "ssh-rsa";
private const string encryptionType = "none";
public static string ToPuttyPrivateKey(RSACryptoServiceProvider cryptoServiceProvider, string Comment = "imported-openssh-key")
{
var publicParameters = cryptoServiceProvider.ExportParameters(false);
byte[] publicBuffer = new byte[3 + keyType.Length + GetPrefixSize(publicParameters.Exponent) + publicParameters.Exponent!.Length + GetPrefixSize(publicParameters.Modulus) + publicParameters.Modulus!.Length + 1];
using (var bw = new BinaryWriter(new MemoryStream(publicBuffer)))
{
bw.Write(new byte[] { 0x00, 0x00, 0x00 });
bw.Write(Encoding.ASCII.GetBytes(keyType));
PutPrefixed(bw, publicParameters.Exponent, CheckIsNeddPadding(publicParameters.Exponent));
PutPrefixed(bw, publicParameters.Modulus, CheckIsNeddPadding(publicParameters.Modulus));
}
var publicBlob = System.Convert.ToBase64String(publicBuffer);
var privateParameters = cryptoServiceProvider.ExportParameters(true);
byte[] privateBuffer = new byte[paddedPrefixSize + privateParameters.D!.Length + paddedPrefixSize + privateParameters.P!.Length + paddedPrefixSize + privateParameters.Q!.Length + paddedPrefixSize + privateParameters.InverseQ!.Length];
using (var bw = new BinaryWriter(new MemoryStream(privateBuffer)))
{
PutPrefixed(bw, privateParameters.D, true);
PutPrefixed(bw, privateParameters.P, true);
PutPrefixed(bw, privateParameters.Q, true);
PutPrefixed(bw, privateParameters.InverseQ, true);
}
var privateBlob = System.Convert.ToBase64String(privateBuffer);
HMACSHA1 hmacSha1 = new(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes("putty-private-key-file-mac-key")));
byte[] bytesToHash = new byte[prefixSize + keyType.Length + prefixSize + encryptionType.Length + prefixSize + Comment.Length + prefixSize + publicBuffer.Length + prefixSize + privateBuffer.Length];
using (var bw = new BinaryWriter(new MemoryStream(bytesToHash)))
{
PutPrefixed(bw, Encoding.ASCII.GetBytes(keyType));
PutPrefixed(bw, Encoding.ASCII.GetBytes(encryptionType));
PutPrefixed(bw, Encoding.ASCII.GetBytes(Comment));
PutPrefixed(bw, publicBuffer);
PutPrefixed(bw, privateBuffer);
}
var hash = string.Join("", hmacSha1.ComputeHash(bytesToHash).Select(x => $"{x:x2}"));
var sb = new StringBuilder();
sb.AppendLine("PuTTY-User-Key-File-2: " + keyType);
sb.AppendLine("Encryption: " + encryptionType);
sb.AppendLine("Comment: " + Comment);
var publicLines = SpliceText(publicBlob, lineLength);
sb.AppendLine("Public-Lines: " + publicLines.Length);
foreach (var line in publicLines)
{
sb.AppendLine(line);
}
var privateLines = SpliceText(privateBlob, lineLength);
sb.AppendLine("Private-Lines: " + privateLines.Length);
foreach (var line in privateLines)
{
sb.AppendLine(line);
}
sb.AppendLine("Private-MAC: " + hash);
return sb.ToString();
}
private static void PutPrefixed(BinaryWriter bw, byte[] bytes, bool addLeadingNull = false)
{
bw.Write(BitConverter.GetBytes(bytes.Length + (addLeadingNull ? 1 : 0)).Reverse().ToArray());
if (addLeadingNull)
bw.Write(new byte[] { 0x00 });
bw.Write(bytes);
}
private static string[] SpliceText(string text, int lineLength)
{
return Regex.Matches(text, ".{1," + lineLength + "}").Cast<Match>().Select(m => m.Value).ToArray();
}
private static int GetPrefixSize(byte[]? bytes)
{
if (bytes is null)
return 0;
return CheckIsNeddPadding(bytes) ? paddedPrefixSize : prefixSize;
}
private static bool CheckIsNeddPadding(byte[] bytes)
{
if (bytes is null || bytes.Length == 0)
return false;
// 128 == 10000000
// This means that the number of bits can be divided by 8.
// According to the algorithm in putty, you need to add a padding.
return bytes[0] >= 128;
}
}

View File

@@ -20,8 +20,8 @@
<a href="https://twitter.com/mremoteng">
<img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/mremoteng?color=%231DA1F2&label=Twitter&logo=Twitter&style=flat-square">
</a>
<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 href="https://app.element.io/#/room/#mremoteng:matrix.org">
<img alt="Element" src="https://img.shields.io/matrix/mremoteng:matrix.org?label=Join%20to%20chat%20about%20mRemoteNG&logo=element&style=social&link=https://app.element.io/#/room/#mremoteng:matrix.org">
</a>
</p>
<p align="center">
@@ -43,6 +43,9 @@
<a href='https://mremoteng.readthedocs.io/en/latest/?badge=latest'>
<img src='https://readthedocs.org/projects/mremoteng/badge/?version=latest' alt='Documentation Status' />
</a>
<a href="https://gurubase.io/g/mremoteng">
<img alt="Gurubase" src="https://img.shields.io/badge/Gurubase-Ask%20mRemoteNG%20Guru-006BFF?style=flat-square">
</a>
</p>
---
@@ -51,7 +54,7 @@
| ---------------|--------------|-----------|
| 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) |
| 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/2023.03.03-v1.77.3-nb/total.svg)](https://github.com/mRemoteNG/mRemoteNG/releases/tag/2023.03.03-v1.77.3-nb) |
## Features
@@ -88,12 +91,14 @@ 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 .NET 6.0 Desktop Runtime](https://dotnet.microsoft.com/download/dotnet/6.0)
* Microsoft Terminal Service Client 6.0 or later
* Needed if you use RDP. mstscax.dll and/or msrdp.ocx be registered.
### Download
> :star: Starting Windows 11 you can use winget to install mRemoteNG. Just run `winget install -e --id mRemoteNG.mRemoteNG`
mRemoteNG is available as a redistributable MSI package or as a portable ZIP package and can be downloaded from the following locations:
* [GitHub](https://github.com/mRemoteNG/mRemoteNG/releases)
* [Project Website](https://mremoteng.org/download)

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

View File

@@ -0,0 +1,38 @@
U2FsdGVkX1/uEeToOEIrunpoPNl7NUYNQfI+ixMzGgKX0DlHZxa/PonAVd9NoAE+
dqWegkN8fa/M2HcW8moN5sN0yS88amG8cfwRZNRSgRB4IxDdYPjM/iX7y8FjUh9R
OZnGhq1rAkqcf7ASAdZfQDSFOAEDPQgByN4IB2j/G3ueqV3jf5sWWiM0ielcorWX
jXuEL7uIk9diI3Qh3BVqmrotzErzqTTLB4DWoL1aWqRRYH+exKahfcMT3C9r+tul
ETH6o1im3kdYzFgHl58FmihbHa+h1+c+DWVGGXRxJPw1DZfg8/+ldIy8J75aWimz
2MX+PiBVQj+wYlSSkQLj2EdbpSERlinE1O55ldwpbnMPAlYCgFRdO3/hiB6LiLLt
n9s8f32HLNG20Mk1oxXdN9VPOw+RwQpUf6Zfcyps6e/9EFKBLnwq4cYwM/dHRuez
w48EJX5wKzpukHn2UFg5aCRGYU/4NyUn+AlIjjCMBWY1R0uBhC340cl6YqoPFN12
clzbS6JdQS+ktusCeaKcsj2iknVqQrGY81LKrTaQZdfehGwAn9pMWE3iWRX80yzO
s08lpmHfPK4uhtlyIbdReLn4n7hvB8KRVa7Foms+4wpEwKrL8KF/8CYc89Tm3PSD
LCrLr7rzCHh51ncJXHAwZqY2ZRLnQBVwhIRsFYyZ595b89tuDY00sYpWnTKzWubK
UD993CmYKg1cA+mj6NR6iSmecawsUz68nI/nmHM2APE5cCCHSK3lz40e9Z9Hmy5Y
kVS6c86a+gwNyn4F9t3iISdj2vIOwGMVWQLeY+nwpiKnnOuI0XPtIMjNbhoaDToT
kph2ZVjqbpmYHgTSX71v8Y16Stl+p/dZn0dE2d4JhxOJxDN//H6y7mOwg8DYX9GP
rFBMIEWyEQyEFYTmWgztQ5KU89w0lV1qiWpaiWR/IDkGbUzHR8ELp4NHYPbZnuYa
FFXooJGPsQ02ioXPm18Lkhloo63lANgyBE0jc1x9hPrI0oVn1wTkKdeW13IbHzrn
VsGXeZz06bB46QChXUPmQ49sNkAXcMDnfNFM9ayUa8eVmNx9fWabtBy8qs7rpdv0
1y/2ud4Da9t7SAUvxwI65+b2Ytk05pLFLmQYb01g4BpBDxbJW3yw3CqKgMnLoZ3t
s2kiUpn7FEQU8jbpbFV+UB4DdJgm8S7o1gbTEWYTscZ7l+oLCmQgMYoi/EJDDIa2
cCSdGy4p7kLlLUoO438Mz36+FDf6qX2B86ezVdNnXQb3UPljjDxiOFM6NkI3HTpl
RqxjBg8JdrwoQ1UMha6ucDKhPXq1xkg0dpO9QyjxywssG3krgQ5Py64s5Xu7IgQm
AzmwGTZ6gFZlDTr9SpJkiucF9vexCo6JHHkF8OjhS49FanqB8otJvg5JclVugP51
LcqqvuMkAsFago261SNcOhgtR6nV5B9QgHyr66c6YnTlwt07T1Qq3S1lw2x0Eccs
PKbJDVU5rAHiM71QYmwsuoC8qkYPTtVPIoUTs+5u5aLywVoLejr1dE5twNXy5PYY
fDwubg0YG3kchvv85N3epZ1h50ADq3W3msU9bWDKWwdwIbpGq+dwjkLssBQmjVtI
R8rGbt1DgL7xtRNF9ESnWVkfHJvJu/5oD6wGAU/oIfBxVON0VYb1evc8wRdQTbDH
Dnt+aKwcSPYdyGVRKfRtBGvEZ8rB5hzCXQnS795L0imdfXjBSJn7Pnl9VwpcB3Pr
aZ6s0GcB8kYDEXzjv+o7JF6k5i2I+sVGwvFVGIoVd/Igq2ysrk/GfWVov0SUu78A
JeHYdtRuKwXOdZw2cjZQ72bvFaHOuoXrQnyKyZDWRyu0NB5HLW75v/YEbr4msIm9
E+3HFwRvKSTfUx/M4NgVKrgsHDeBRD4tLNx/SerQvqaplunM7OfAtULucveUhwSo
vT6uNK3URe1qDgX554cz8c6+KrkglTLFMuKfNWj3q/uSM0BxTTD9QgorNdlMmErH
TfV/ZpZACpuMFRbC5xQRkCG1x4U12pdPbtIkGtVBJROEXhP1aw3BDWwrIN7zSgfj
8I4OF4fbj/rSuvI4klzi1zlUMQQnenlRURE+7DKRxtWipJhW9vtDI0LXN7gGfmbR
73uR3YUny6zUJ0svqaWj4Eo6t3g99nmk4D2hm/622dRksv2HqEQiq29jJxlcbdZB
PU96wOp54s/BzgodyI5dh+xL06Obr9AltLV9vw3iK0VBZqquj9FuhvWC1tYlZQJF
AOgVDOGUZzmAJXftI7gaohFBwsT5tAHQtBuY7tB3b1hrnfrFb9FTNxGZJKcIH3p4
dOAvedsfuq+2/lU9iM4tX9fjSzfnGRZRuKCGDSdhE6EDik9/f2kSCoY9z0zJwdZH
324TpGbZNbgcwgHDL9i5Nsnaua5yxtr0/Fr1We1tvn0=

View File

@@ -0,0 +1,168 @@
#Requires -Version 4.0
param (
[string]
[Parameter(Mandatory=$true)]
$WebsiteTargetOwner,
[string]
[Parameter(Mandatory=$true)]
$WebsiteTargetRepository,
[string]
[Parameter(Mandatory=$false)]
$PreTagName = "",
[string]
[Parameter(Mandatory=$true)]
$TagName,
[string]
[Parameter(Mandatory=$true)]
$ProjectName
)
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 | ForEach-Object { $_.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 | ForEach-Object { $_.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","Preview","Nightly")]
$UpdateChannel,
[string]
[Parameter(Mandatory=$true)]
[ValidateSet("Normal","Portable")]
$Type
)
$fileName = ""
if ($UpdateChannel -eq "Preview") { $fileName += "preview-" }
elseif ($UpdateChannel -eq "Nightly") { $fileName += "nightly-" }
$fileName += "update"
if ($Type -eq "Portable") { $fileName += "-portable" }
$fileName += ".txt"
Write-Output $fileName
}
Write-Output ""
Write-Output "===== Begin $($PSCmdlet.MyInvocation.MyCommand) ====="
# determine update channel
if ($env:APPVEYOR_PROJECT_NAME -match "(Nightly)") {
Write-Output "UpdateChannel = Nightly"
$UpdateChannel = "Nightly"
$ModifiedTagName = "$PreTagName-$TagName-NB"
} elseif ($env:APPVEYOR_PROJECT_NAME -match "(Preview)") {
Write-Output "UpdateChannel = Preview"
$UpdateChannel = "Preview"
$ModifiedTagName = "v$TagName-PB"
} elseif ($env:APPVEYOR_PROJECT_NAME -match "(Stable)") {
Write-Output "UpdateChannel = Stable"
$UpdateChannel = "Stable"
$ModifiedTagName = "v" + $TagName.Split("-")[0]
} else {
$UpdateChannel = ""
}
#$buildFolder = Join-Path -Path $PSScriptRoot -ChildPath "..\mRemoteNG\bin\x64\Release" -Resolve -ErrorAction Ignore
$ReleaseFolder = Join-Path -Path $PSScriptRoot -ChildPath "..\Release" -Resolve
if ($UpdateChannel -ne "" -and $ReleaseFolder -ne "" -and $WebsiteTargetOwner -and $WebsiteTargetRepository) {
$msiFile = Get-ChildItem -Path "$ReleaseFolder\*.msi" -Exclude "*-symbols-*.zip" | Sort-Object LastWriteTime | Select-Object -last 1
if (![string]::IsNullOrEmpty($msiFile)) {
$msiUpdateContents = New-MsiUpdateFileContent -MsiFile $msiFile -TagName $ModifiedTagName
$msiUpdateFileName = Resolve-UpdateCheckFileName -UpdateChannel $UpdateChannel -Type Normal
Write-Output "`n`nMSI Update Check File Contents ($msiUpdateFileName)`n------------------------------"
Tee-Object -InputObject $msiUpdateContents -FilePath "$ReleaseFolder\$msiUpdateFileName"
# commit msi update txt file
if ($env:WEBSITE_UPDATE_ENABLED.ToLower() -eq "true") {
if ((Test-Path -Path "$ReleaseFolder\$msiUpdateFileName") -and (-not [string]::IsNullOrEmpty($WebsiteTargetRepository))) {
Write-Output "Publish Update File $msiUpdateFileName to $WebsiteTargetRepository"
$update_file_content_string = Get-Content "$ReleaseFolder\$msiUpdateFileName" | Out-String
Set-GitHubContent -OwnerName $WebsiteTargetOwner -RepositoryName $WebsiteTargetRepository -Path $msiUpdateFileName -CommitMessage "Build $ModifiedTagName" -Content $update_file_content_string -BranchName main
} else {
Write-Warning "WARNING: Update file does not exist: $ReleaseFolder\$msiUpdateFileName"
}
}
}
# build zip update file
$zipFile = Get-ChildItem -Path "$ReleaseFolder\*.zip" -Exclude "*-symbols-*.zip" | Sort-Object LastWriteTime | Select-Object -last 1
if (![string]::IsNullOrEmpty($zipFile)) {
$zipUpdateContents = New-ZipUpdateFileContent -ZipFile $zipFile -TagName $ModifiedTagName
$zipUpdateFileName = Resolve-UpdateCheckFileName -UpdateChannel $UpdateChannel -Type Portable
Write-Output "`n`nZip Update Check File Contents ($zipUpdateFileName)`n------------------------------"
Tee-Object -InputObject $zipUpdateContents -FilePath "$ReleaseFolder\$zipUpdateFileName"
# commit zip update txt file
if ($env:WEBSITE_UPDATE_ENABLED.ToLower() -eq "true") {
if ((Test-Path -Path "$ReleaseFolder\$zipUpdateFileName") -and (-not [string]::IsNullOrEmpty($WebsiteTargetRepository))) {
Write-Output "Publish Update File $zipUpdateFileName to $WebsiteTargetRepository"
$update_file_content_string = Get-Content "$ReleaseFolder\$zipUpdateFileName" | Out-String
Set-GitHubContent -OwnerName $WebsiteTargetOwner -RepositoryName $WebsiteTargetRepository -Path $zipUpdateFileName -CommitMessage "Build $ModifiedTagName" -Content $update_file_content_string -BranchName main
} else {
Write-Warning "WARNING: Update file does not exist: $ReleaseFolder\$zipUpdateFileName"
}
}
}
} else {
Write-Output "ReleaseFolder not found"
}
Write-Output "End $($PSCmdlet.MyInvocation.MyCommand)"
Write-Output ""

2
Tools/decrypt.bat Normal file
View File

@@ -0,0 +1,2 @@
openssl enc -base64 -aes-256-cbc -md sha512 -pbkdf2 -iter 100000 -d -in cert/CodeSigning_Cert_mRemoteNG_Certum.enc -out cert/CodeSigning_Cert_mRemoteNG_Certum.cer
pause

2
Tools/encrypt.bat Normal file
View File

@@ -0,0 +1,2 @@
openssl enc -base64 -aes-256-cbc -md sha512 -pbkdf2 -iter 100000 -e -in cert/CodeSigning_Cert_mRemoteNG_Certum.cer -out cert/CodeSigning_Cert_mRemoteNG_Certum.enc
pause

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.

63
Tools/find_vstool.ps1 Normal file
View File

@@ -0,0 +1,63 @@
[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",
"97221B97098F37A135DCC212E2B41E452BCE51F2"
)
$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 $($file_signature.SignerCertificate.Thumbprint)"
Write-Output "file_signature.SignerCertificate.Thumbprint: $($file_signature.SignerCertificate.Thumbprint)"
return $false
} else {
return $true
}
}
function ToolCanBeExecuted {
param (
[string]
$Path
)
$env:PATHEXT.Contains((Get-Item $Path).Extension.ToUpper())
}
$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)) {
if (ToolCanBeExecuted -Path $matchingExe) {
return $matchingExe
}
}
}
}
Write-Error "Could not find any valid file by the name $FileName." -ErrorAction Stop

261
Tools/github_functions.ps1 Normal file
View File

@@ -0,0 +1,261 @@
$githubUrl = 'https://api.github.com'
# GitHub doesn't support the default powershell protocol (TLS 1.0)
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$IsAppVeyor = !([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER))
if ($IsAppVeyor) {
#$CURRENT_GITHUB_USER = $env:APPVEYOR_REPO_NAME.Split("/")[0]
Install-Module -Name PowerShellForGitHub -Scope CurrentUser
Set-GitHubConfiguration -DisableTelemetry
$PSDefaultParameterValues["*-GitHub*:AccessToken"] = "$env:ACCESS_TOKEN"
#New-Item -Path "$Env:APPVEYOR_BUILD_FOLDER\Release" -ItemType Directory -Force
}
function New-TemporaryDirectory {
$parent = [System.IO.Path]::GetTempPath()
[string] $name = [System.Guid]::NewGuid()
$fullTempPath = (Join-Path $parent $name)
New-Item -ItemType Directory -Path $fullTempPath
return $fullTempPath
}
Function ConvertFrom-Base64($base64) {
return [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($base64))
}
Function ConvertTo-Base64($plain) {
return [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($plain))
}
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
}

76
Tools/postbuild.ps1 Normal file
View File

@@ -0,0 +1,76 @@
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
)
. "$PSScriptRoot\github_functions.ps1"
Write-Output ""
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
}
if ( $ConfigurationName -match "Debug" -and ([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER)) ) { return; } #skip when Debug local developer build
if ( $env:APPVEYOR_PROJECT_NAME -match "(CI)" -and -not ([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER)) ) { return; } #skip when AppVeyor (CI) build
$dstPath = "$($SolutionDir)Release"
New-Item -Path $dstPath -ItemType Directory -Force
# $RunInstaller = $TargetDir -match "\\mRemoteNGInstaller\\Installer\\bin\\"
# $RunPortable = ( ($Targetdir -match "\\mRemoteNG\\bin\\") -and -not ($TargetDir -match "\\mRemoteNGInstaller\\Installer\\bin\\") )
if ( ($ConfigurationName -match "Release") -and ($env:APPVEYOR_PROJECT_NAME -notmatch "(CI)") -and -not ([string]::IsNullOrEmpty($env:WEBSITE_TARGET_OWNER)) -and -not ([string]::IsNullOrEmpty($env:WEBSITE_TARGET_REPOSITORY)) ) {
Write-Output "-Begin Release Portable"
& "$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
& "$PSScriptRoot\create_upg_chk_files.ps1" -WebsiteTargetOwner $env:WEBSITE_TARGET_OWNER -WebsiteTargetRepository $env:WEBSITE_TARGET_REPOSITORY -PreTagName $env:NightlyBuildTagName -TagName $env:APPVEYOR_BUILD_VERSION -ProjectName $env:APPVEYOR_PROJECT_NAME
& "$PSScriptRoot\update_and_upload_website_release_json_file.ps1" -WebsiteTargetOwner $env:WEBSITE_TARGET_OWNER -WebsiteTargetRepository $env:WEBSITE_TARGET_REPOSITORY -PreTagName $env:NightlyBuildTagName -TagName $env:APPVEYOR_BUILD_VERSION -ProjectName $env:APPVEYOR_PROJECT_NAME
Write-Output "-End Release Portable"
}
Write-Output "End mRemoteNG Post Build"
Write-Output ""

View File

@@ -0,0 +1,63 @@
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
)
. "$PSScriptRoot\github_functions.ps1"
Write-Output ""
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
}
$IsAppVeyor = !([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER))
if ( $IsAppVeyor -and ($ConfigurationName.ToUpper() -match "RELEASE") -and (($env:APPVEYOR_PROJECT_NAME).ToUpper() -notmatch "(CI)") ) {
& "$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_and_copy_installer.ps1" -SolutionDir $SolutionDir -BuildConfiguration $ConfigurationName.Trim()
& "$PSScriptRoot\create_upg_chk_files.ps1" -WebsiteTargetOwner $env:WEBSITE_TARGET_OWNER -WebsiteTargetRepository $env:WEBSITE_TARGET_REPOSITORY -PreTagName $env:NightlyBuildTagName -TagName $env:APPVEYOR_BUILD_VERSION -ProjectName $env:APPVEYOR_PROJECT_NAME
& "$PSScriptRoot\update_and_upload_website_release_json_file.ps1" -WebsiteTargetOwner $env:WEBSITE_TARGET_OWNER -WebsiteTargetRepository $env:WEBSITE_TARGET_REPOSITORY -PreTagName $env:NightlyBuildTagName -TagName $env:APPVEYOR_BUILD_VERSION -ProjectName $env:APPVEYOR_PROJECT_NAME
& "$PSScriptRoot\update_and_upload_assemblyinfocs.ps1"
}
Write-Output "End mRemoteNG Installer Post Build"
Write-Output ""

View File

@@ -0,0 +1,91 @@
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
)
. "$PSScriptRoot\github_functions.ps1"
Write-Output ""
Write-Output "+===========================================================================================+"
Write-Output "| Beginning mRemoteNG Portable Post Build |"
Write-Output "+===========================================================================================+"
Format-Table -AutoSize -Wrap -InputObject @{
"SolutionDir" = $SolutionDir
"TargetDir" = $TargetDir
"TargetFileName" = $TargetFileName
"ConfigurationName" = $ConfigurationName
"CertificatePath" = $CertificatePath
"ExcludeFromSigning" = $ExcludeFromSigning
}
# Move dlls resurses into folder
#Remove-Item -Path "$TargetDir\libs" -Recurse -ErrorAction Ignore
#New-Item -ItemType "directory" -Force -Path $TargetDir -Name "libs"
#Move-Item -Path "$TargetDir\*.dll" -Destination "$TargetDir\libs" -force
###
# Move lang resurses into folder
#Remove-Item -Path "$TargetDir\languages" -Recurse -ErrorAction Ignore
#New-Item -ItemType "directory" -Force -Path $TargetDir -Name "languages"
#"cs-CZ,de,el,en-US,es-AR,es,fr,hu,it,lt,ja-JP,ko-KR,nb-NO,nl,pt,pt-BR,pl,ru,uk,tr-TR,zh-CN,zh-TW,fi-FI".Split(",") | ForEach {
# Move-Item -Path "$TargetDir\$_" -Destination "$TargetDir\languages" -force
# }
###
# Currently targeting x64, shouldn't need to manually set LargeAddressAware...
#& "$PSScriptRoot\set_LargeAddressAware.ps1" -TargetDir $TargetDir -TargetFileName $TargetFileName
#& "$PSScriptRoot\verify_LargeAddressAware.ps1" -TargetDir $TargetDir -TargetFileName $TargetFileName
if (!([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER))) {
$postbuild_installer_executed = Get-ItemPropertyValue -Path 'HKLM:\SOFTWARE\AppVeyor_mRemoteNG' -Name postbuild_installer_executed
} else {
$postbuild_installer_executed = ""
}
write-host "-SolutionDir $SolutionDir -TargetDir $TargetDir -ConfigurationName $ConfigurationName "
& "$PSScriptRoot\tidy_files_for_release.ps1" -TargetDir $TargetDir -ConfigurationName $ConfigurationName
if ($postbuild_installer_executed -ne "true" -or $env:postbuild_installer_executed -ne "true") {
& "$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
if ( ![string]::IsNullOrEmpty($env:WEBSITE_TARGET_OWNER) -and ![string]::IsNullOrEmpty($env:WEBSITE_TARGET_REPOSITORY) ) {
& "$PSScriptRoot\create_upg_chk_files.ps1" -WebsiteTargetOwner $env:WEBSITE_TARGET_OWNER -WebsiteTargetRepository $env:WEBSITE_TARGET_REPOSITORY -PreTagName $env:NightlyBuildTagName -TagName $env:APPVEYOR_BUILD_VERSION -ProjectName $env:APPVEYOR_PROJECT_NAME
& "$PSScriptRoot\update_and_upload_website_release_json_file.ps1" -WebsiteTargetOwner $env:WEBSITE_TARGET_OWNER -WebsiteTargetRepository $env:WEBSITE_TARGET_REPOSITORY -PreTagName $env:NightlyBuildTagName -TagName $env:APPVEYOR_BUILD_VERSION -ProjectName $env:APPVEYOR_PROJECT_NAME
}
Write-Output "End mRemoteNG Portable Post Build"
Write-Output ""

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,61 @@
param (
[string]
$SolutionDir,
[string]
$BuildConfiguration
)
$ErrorActionPreference = "Stop"
Write-Output ""
Write-Output " /===== Begin rename_and_copy_installer =====/"
$targetVersionedFile = "$SolutionDir\mRemoteNG\bin\x64\$BuildConfiguration\mRemoteNG.exe"
#$fileversion = &"$SolutionDir\Tools\exes\sigcheck.exe" /accepteula -q -n $targetVersionedFile
#$prodversion = ((Get-Item -Path $targetVersionedFile).VersionInfo | Select-Object -Property ProductVersion)."ProductVersion"
$fileversion = ((Get-Item -Path $targetVersionedFile).VersionInfo | Select-Object -Property FileVersion)."FileVersion"
Write-Output "fileversion: $fileversion"
$msiversion = $fileversion
# determine update channel
if ($env:APPVEYOR_PROJECT_NAME -match "(Nightly)") {
Write-Output " UpdateChannel = Nightly"
$msiversion = "$msiversion-NB"
} elseif ($env:APPVEYOR_PROJECT_NAME -match "(Preview)") {
Write-Output " UpdateChannel = Preview"
$msiversion = "$msiversion-PB"
} elseif ($env:APPVEYOR_PROJECT_NAME -match "(Stable)") {
Write-Output " UpdateChannel = Stable"
} else {
}
$dstPath = "$($SolutionDir)Release"
New-Item -Path $dstPath -ItemType Directory -Force
$srcMsi = $SolutionDir + "mRemoteNGInstaller\Installer\bin\x64\$BuildConfiguration\en-US\mRemoteNG-Installer.msi"
$dstMsi = $dstPath + "\mRemoteNG-Installer-" + $msiversion + ".msi"
#$srcSymbols = $SolutionDir + "mRemoteNGInstaller\Installer\bin\x64\$BuildConfiguration\en-US\mRemoteNG-Installer-Symbols*.zip"
#$dstSymbols = $SolutionDir + "Release\mRemoteNG-Installer-Symbols-" + $msiversion + ".zip"
Write-Output " Copy Installer file:"
Write-Output " From: $srcMsi"
Write-Output " To: $dstMsi"
Write-Output ""
# Copy file
try
{
Copy-Item $srcMsi -Destination $dstMsi -Force -ErrorAction Stop
#Copy-Item $srcSymbols -Destination $dstSymbols -Force -ErrorAction Stop
Write-Host " [Success!]" -ForegroundColor green
}
catch
{
Write-Host " [Failure!]" -ForegroundColor red
Write-Output $Error[0]
$PSCmdlet.ThrowTerminatingError()
}
Write-Output ""
Write-Output " /===== End rename_and_copy_installer.ps1 =====/"
Write-Output ""

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

103
Tools/sign_binaries.ps1 Normal file
View File

@@ -0,0 +1,103 @@
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 ""
Write-Output "===== Begin $($PSCmdlet.MyInvocation.MyCommand) ====="
$timeserver = "http://timestamp.verisign.com/scripts/timstamp.dll"
$IsAppVeyor = !([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER))
try {
# validate release versions and if the certificate value was passed
if ($ConfigurationName -match "Release" -And ($CertificatePath)) {
if($IsAppVeyor) {
$CertificatePath = Join-Path -Path $SolutionDir -ChildPath $CertificatePath
Write-Output "Decrypt Cert"
& appveyor-tools\secure-file -decrypt "$($Env:cert_path).enc" -secret "$Env:cert_decrypt_pwd"
if(-Not (Test-Path $Env:cert_path)) {
Write-Output "decrypt cert does not exist..."
Throw "Could not decrypt cert"
}
Write-Output "Restoring NuGets"
}
# 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 ($null -ne $cert) {
$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)"
}
}
catch {
Write-Output $Error[0]
}
Write-Output "End $($PSCmdlet.MyInvocation.MyCommand)"
Write-Output ""

33
Tools/signfiles.ps1 Normal file
View File

@@ -0,0 +1,33 @@
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
}
Write-Output ""

View File

@@ -0,0 +1,46 @@
param (
[string]
[Parameter(Mandatory=$true)]
$TargetDir,
[string]
[Parameter(Mandatory=$true)]
$ConfigurationName
)
Write-Output ""
Write-Output "===== Begin $($PSCmdlet.MyInvocation.MyCommand) ====="
# Remove unnecessary files from Release versions
if ($ConfigurationName -match "Release") {
$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"
)
if ($filesToDelete)
{
Write-Output "Unnecessary files are detected and will be removed"
Remove-Item -Path $filesToDelete.FullName
Write-Output $filesToDelete.FullName
} else {
Write-Output " No unnecessary files are detected"
}
} else {
Write-Output "We will not remove anything - this is not a release build."
}
Write-Output "End $($PSCmdlet.MyInvocation.MyCommand)"
Write-Output ""

View File

@@ -0,0 +1,53 @@
#Requires -Version 4.0
Write-Output ""
Write-Output "===== Begin $($PSCmdlet.MyInvocation.MyCommand) ====="
$MainRepository = $Env:APPVEYOR_REPO_NAME.Split("/\")[1]
$IsAppVeyor = !([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER))
Write-Output "MainRepository: $MainRepository"
if ($IsAppVeyor) {
# determine update channel
if ($env:APPVEYOR_PROJECT_NAME -match "(Nightly)") {
Write-Output "UpdateChannel = Nightly"
$UpdateChannel = "Nightly"
$ModifiedTagName = "$PreTagName-$TagName-NB"
} elseif ($env:APPVEYOR_PROJECT_NAME -match "(Preview)") {
Write-Output "UpdateChannel = Preview"
$UpdateChannel = "Preview"
$ModifiedTagName = "v$TagName-PB"
} elseif ($env:APPVEYOR_PROJECT_NAME -match "(Stable)") {
Write-Output "UpdateChannel = Stable"
$UpdateChannel = "Stable"
$ModifiedTagName = "v" + $TagName.Split("-")[0]
} else {
$UpdateChannel = ""
}
if ($UpdateChannel -ne "" -and $MainRepository -ne "" ) {
# commit AssemblyInfo.cs change
Write-Output "publish AssemblyInfo.cs"
$buildFolder = Join-Path -Path $PSScriptRoot -ChildPath "..\" -Resolve -ErrorAction Ignore
if (Test-Path -Path "$buildFolder\mRemoteNG\Properties\AssemblyInfo.cs") {
$assemblyinfocs_content = [System.String]::Join("`r`n", (Get-Content "$buildFolder\mRemoteNG\Properties\AssemblyInfo.cs"))
Set-GitHubContent -OwnerName $MainRepository -RepositoryName $MainRepository -Path "mRemoteNG\Properties\AssemblyInfo.cs" -CommitMessage "AssemblyInfo.cs updated for $UpdateChannel $ModifiedTagName" -Content $assemblyinfocs_content -BranchName main
Write-Output "publish completed"
}
} else {
Write-Output "Source folder not found"
}
}
Write-Output "End $($PSCmdlet.MyInvocation.MyCommand)"
Write-Output ""

View File

@@ -0,0 +1,178 @@
#Requires -Version 4.0
param (
[string]
[Parameter(Mandatory=$true)]
$WebsiteTargetOwner,
[string]
[Parameter(Mandatory=$true)]
$WebsiteTargetRepository,
[string]
[Parameter(Mandatory=$false)]
$PreTagName = "",
[string]
[Parameter(Mandatory=$true)]
$TagName,
[string]
[Parameter(Mandatory=$true)]
$ProjectName
)
Write-Output ""
Write-Output "===== Begin $($PSCmdlet.MyInvocation.MyCommand) ====="
$MainRepository = $Env:APPVEYOR_REPO_NAME.Split("/\")[1]
# determine update channel
if ($env:APPVEYOR_PROJECT_NAME -match "(Nightly)") {
Write-Output "UpdateChannel = Nightly"
$UpdateChannel = "Nightly"
$ModifiedTagName = "$PreTagName-$TagName-NB"
} elseif ($env:APPVEYOR_PROJECT_NAME -match "(Preview)") {
Write-Output "UpdateChannel = Preview"
$UpdateChannel = "Preview"
$ModifiedTagName = "v$TagName-PB"
} elseif ($env:APPVEYOR_PROJECT_NAME -match "(Stable)") {
Write-Output "UpdateChannel = Stable"
$UpdateChannel = "Stable"
$ModifiedTagName = "v" + $TagName.Split("-")[0]
} else {
$UpdateChannel = ""
}
$IsAppVeyor = !([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER))
if ($IsAppVeyor) {
#$buildFolder = Join-Path -Path $PSScriptRoot -ChildPath "..\mRemoteNG\bin\x64\Release" -Resolve -ErrorAction Ignore
$ReleaseFolder = Join-Path -Path $PSScriptRoot -ChildPath "..\Release" -Resolve
if ($UpdateChannel -ne "" -and $ReleaseFolder -ne "" -and $MainRepository -ne "" -and $WebsiteTargetOwner -ne "" -and $WebsiteTargetRepository -ne "" ) {
$published_at = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
# get releases.json from github
$releases_json = Get-GitHubContent -OwnerName $WebsiteTargetOwner -RepositoryName $WebsiteTargetRepository -Path _data\releases.json
ConvertFrom-Base64($releases_json.content) | Out-File -FilePath "$ReleaseFolder\releases.json"
$websiteJsonReleaseFile = Get-ChildItem -Path "$ReleaseFolder\releases.json"
# installer
$msiFile = Get-ChildItem -Path "$ReleaseFolder\*.msi" | Sort-Object LastWriteTime | Select-Object -last 1
if (![string]::IsNullOrEmpty($msiFile)) {
Write-Output "UpdateChannel: $UpdateChannel"
Write-Output "msiFile: $msiFile"
$checksum = (Get-FileHash $msiFile -Algorithm SHA512).Hash
$file_size = (Get-ChildItem $msiFile).Length
$a = Get-Content $websiteJsonReleaseFile | ConvertFrom-Json
switch ($UpdateChannel) {
"Nightly" {
$GithubTag = "$((Get-Date).ToUniversalTime().ToString("yyyyMMdd"))-$TagName-NB"
$html_url = "https://github.com/$WebsiteTargetOwner/$MainRepository/releases/tag/$GithubTag"
$browser_download_url = "https://github.com/$WebsiteTargetOwner/$MainRepository/releases/download/$GithubTag/$($msiFile.Name)"
$a.nightlybuild.name = "v$TagName-NB"
$a.nightlybuild.published_at = $published_at
$a.nightlybuild.html_url = $html_url
$a.nightlybuild.assets.installer.browser_download_url = $browser_download_url
$a.nightlybuild.assets.installer.checksum = $checksum
$a.nightlybuild.assets.installer.size = $file_size
break
}
"Preview" {
$GithubTag = "$TagName-PB"
$html_url = "https://github.com/$WebsiteTargetOwner/$MainRepository/releases/tag/$GithubTag"
$browser_download_url = "https://github.com/$WebsiteTargetOwner/$MainRepository/releases/download/$GithubTag/$($msiFile.Name)"
$a.prerelease.name = "v$TagName-PB"
$a.prerelease.published_at = $published_at
$a.prerelease.html_url = $html_url
$a.prerelease.assets.installer.browser_download_url = $browser_download_url
$a.prerelease.assets.installer.checksum = $checksum
$a.prerelease.assets.installer.size = $file_size
break
}
"Stable" {
$GithubTag = "$TagName"
$html_url = "https://github.com/$WebsiteTargetOwner/$MainRepository/releases/tag/$GithubTag"
$browser_download_url = "https://github.com/$WebsiteTargetOwner/$MainRepository/releases/download/$GithubTag/$($msiFile.Name)"
$a.stable.name = "v$TagName"
$a.stable.published_at = $published_at
$a.stable.html_url = $html_url
$a.stable.assets.installer.browser_download_url = $browser_download_url
$a.stable.assets.installer.checksum = $checksum
$a.stable.assets.installer.size = $file_size
break
}
}
}
# portable
$zipFile = Get-ChildItem -Path "$ReleaseFolder\*.zip" -Exclude "*-symbols-*.zip" | Sort-Object LastWriteTime | Select-Object -last 1
if (![string]::IsNullOrEmpty($zipFile)) {
Write-Output "UpdateChannel: $UpdateChannel"
Write-Output "zipFile: $zipFile"
$checksum = (Get-FileHash $zipFile -Algorithm SHA512).Hash
$file_size = (Get-ChildItem $zipFile).Length
$a = Get-Content $websiteJsonReleaseFile | ConvertFrom-Json
switch ($UpdateChannel) {
"Nightly" {
$GithubTag = "$((Get-Date).ToUniversalTime().ToString("yyyyMMdd"))-$TagName-NB"
$html_url = "https://github.com/$WebsiteTargetOwner/$MainRepository/releases/tag/$GithubTag"
$browser_download_url = "https://github.com/$WebsiteTargetOwner/$MainRepository/releases/download/$GithubTag/$($zipFile.Name)"
$a.nightlybuild.name = "v$TagName-NB"
$a.nightlybuild.published_at = $published_at
$a.nightlybuild.html_url = $html_url
$a.nightlybuild.assets.portable.browser_download_url = $browser_download_url
$a.nightlybuild.assets.portable.checksum = $checksum
$a.nightlybuild.assets.portable.size = $file_size
break
}
"Preview" {
$GithubTag = "$TagName-PB"
$html_url = "https://github.com/$WebsiteTargetOwner/$MainRepository/releases/tag/$GithubTag"
$browser_download_url = "https://github.com/$WebsiteTargetOwner/$MainRepository/releases/download/$GithubTag/$($zipFile.Name)"
$a.prerelease.name = "v$TagName-PB"
$a.prerelease.published_at = $published_at
$a.prerelease.html_url = $html_url
$a.prerelease.assets.portable.browser_download_url = $browser_download_url
$a.prerelease.assets.portable.checksum = $checksum
$a.prerelease.assets.portable.size = $file_size
break
}
"Stable" {
$GithubTag = "$TagName"
$html_url = "https://github.com/$WebsiteTargetOwner/$MainRepository/releases/tag/$GithubTag"
$browser_download_url = "https://github.com/$WebsiteTargetOwner/$MainRepository/releases/download/$GithubTag/$($zipFile.Name)"
$a.stable.name = "v$TagName"
$a.stable.published_at = $published_at
$a.stable.html_url = $html_url
$a.stable.assets.portable.browser_download_url = $browser_download_url
$a.stable.assets.portable.checksum = $checksum
$a.stable.assets.portable.size = $file_size
break
}
}
}
$a | ConvertTo-Json -Depth 10 | Set-Content $websiteJsonReleaseFile
# commit releases.json change
if ($env:WEBSITE_UPDATE_ENABLED.ToLower() -eq "true") {
Write-Output "publish releases.json"
if (Test-Path -Path "$ReleaseFolder\releases.json") {
$releases_json_string = Get-Content "$ReleaseFolder\releases.json" | Out-String
Set-GitHubContent -OwnerName $WebsiteTargetOwner -RepositoryName $WebsiteTargetRepository -Path _data\releases.json -CommitMessage "Updated for $UpdateChannel $ModifiedTagName" -Content $releases_json_string -BranchName main
Write-Output "publish completed"
}
}
} else {
Write-Output "ReleaseFolder not found"
}
}
Write-Output "End $($PSCmdlet.MyInvocation.MyCommand)"
Write-Output ""

View File

@@ -0,0 +1,23 @@
# $FullPath Full path to the Microsoft executable to validate
param (
[string]
[Parameter(Mandatory=$true)]
$FullPath
)
$validMSCertThumbprints = @(
"3BDA323E552DB1FDE5F4FBEE75D6D5B2B187EEDC",
"108E2BA23632620C427C570B6D9DB51AC31387FE",
"98ED99A67886D020C564923B7DF25E9AC019DF26",
"5EAD300DC7E4D637948ECB0ED829A072BD152E17",
"97221B97098F37A135DCC212E2B41E452BCE51F2"
)
$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,32 @@
[CmdletBinding()]
param (
[string]
[Parameter(Mandatory=$true)]
$TargetDir,
[string]
[Parameter(Mandatory=$true)]
$TargetFileName
)
Write-Output ""
Write-Output "===== Begin $($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 ($null -eq $output)
{
Write-Warning "Could not validate LargeAddressAware"
}
else
{
Write-Output $output.ToString().TrimStart(" ")
}
Write-Output "End $($PSCmdlet.MyInvocation.MyCommand)"
Write-Output ""

View File

@@ -0,0 +1,65 @@
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 ""
Write-Output "===== Begin $($PSCmdlet.MyInvocation.MyCommand) ====="
$IsAppVeyor = !([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER))
# validate release versions and if the certificate value was passed
if ($ConfigurationName -match "Release" -And ($CertificatePath)) {
if($IsAppVeyor) {
$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 | Where-Object {$_.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 "End $($PSCmdlet.MyInvocation.MyCommand)"
Write-Output ""

93
Tools/zip_files.ps1 Normal file
View File

@@ -0,0 +1,93 @@
param (
[string]
[Parameter(Mandatory=$true)]
$SolutionDir,
[string]
[Parameter(Mandatory=$true)]
$TargetDir,
[string]
[Parameter(Mandatory=$true)]
$ConfigurationName
)
Write-Output ""
Write-Output "===== Begin $($PSCmdlet.MyInvocation.MyCommand) ====="
$IsAppVeyor = !([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER))
$ConfigurationName = $ConfigurationName.Trim()
$exe = Join-Path -Path $TargetDir -ChildPath $TargetFileName
#$version = ((Get-Item -Path $exe).VersionInfo | Select-Object -Property ProductVersion)."ProductVersion"
$version = $(Get-Item -Path $exe).VersionInfo.FileVersion
Write-Output "FileVersion: $version"
# determine update channel
if ($env:APPVEYOR_PROJECT_NAME -match "(Nightly)") {
Write-Output "UpdateChannel = Nightly"
$ModifiedVersion = "$version-NB"
} elseif ($env:APPVEYOR_PROJECT_NAME -match "(Preview)") {
Write-Output "UpdateChannel = Preview"
$ModifiedVersion = "$version-PB"
} elseif ($env:APPVEYOR_PROJECT_NAME -match "(Stable)") {
Write-Output "UpdateChannel = Stable"
$ModifiedVersion = $version
} else {
}
# Fix for AppVeyor
if($IsAppVeyor) {
if(!(Test-Path "Release")) {
New-Item -ItemType Directory -Force -Path "Release" | Out-Null
}
}
# Package debug symbols zip file
Write-Output "Packaging debug symbols"
$zipFilePrefix = "mRemoteNG-symbols"
$pdbFiles = Get-ChildItem -Path $SolutionDir -Filter *.pdb -Recurse
$tempPdbPath = (New-TemporaryDirectory)[0]
foreach ($pdbFile in $pdbFiles) {
if (($pdbFile.FullName).Contains("\$ConfigurationName\")) {
Copy-Item $pdbFile.FullName -Destination $tempPdbPath -Force
}
}
if ($IsAppVeyor) {
# AppVeyor build
$outputZipPath = Join-Path -Path $SolutionDir -ChildPath "Release\$zipFilePrefix-$($ModifiedVersion).zip"
Write-Output "outputZipPath: $outputZipPath"
7z a $outputZipPath "$tempPdbPath\*.pdb"
}
# else {
# # Local build
# $outputZipPath = "$($SolutionDir)Release\$zipFilePrefix-$($ModifiedVersion).zip"
# Write-Output "outputZipPath: $outputZipPath"
# Compress-Archive -Path $tempPdbPath -DestinationPath $outputZipPath -Force
# }
# Package portable release zip file
Write-Output "Packaging portable ZIP file"
# AppVeyor build
if ($IsAppVeyor) {
$outputZipPath = Join-Path -Path $SolutionDir -ChildPath "Release\mRemoteNG-Portable-$($ModifiedVersion).zip"
7z a -bt -bd -bb1 -mx=9 -tzip -y -r $outputZipPath $TargetDir\*
Write-Output "Portable ZIP: $outputZipPath"
}
# Local build
else {
if ($Source)
{
$outputZipPath="$($SolutionDir)\Release\mRemoteNG-Portable-$($ModifiedVersion).zip"
Compress-Archive $Source $outputZipPath -Force
} else {
Write-Output "Files do not exist:" $Source", nothing to compress"
}
}
Write-Output "End $($PSCmdlet.MyInvocation.MyCommand)"
Write-Output ""

Binary file not shown.

6
mRemoteNG.lutconfig Normal file
View File

@@ -0,0 +1,6 @@
<LUTConfig Version="1.0">
<Repository />
<ParallelBuilds>true</ParallelBuilds>
<ParallelTestRuns>true</ParallelTestRuns>
<TestCaseTimeout>180000</TestCaseTimeout>
</LUTConfig>

73
mRemoteNG.sln Normal file
View File

@@ -0,0 +1,73 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31912.275
MinimumVisualStudioVersion = 14.0.25420.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mRemoteNG", "mRemoteNG\mRemoteNG.csproj", "{4934A491-40BC-4E5B-9166-EA1169A220F6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "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
{4934A491-40BC-4E5B-9166-EA1169A220F6} = {4934A491-40BC-4E5B-9166-EA1169A220F6}
{5423D985-CB48-4344-B47F-E8C6D60C8B04} = {5423D985-CB48-4344-B47F-E8C6D60C8B04}
{A56A2029-79B8-492A-ABE5-D2BFE05801BD} = {A56A2029-79B8-492A-ABE5-D2BFE05801BD}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mRemoteNGSpecs", "mRemoteNGSpecs\mRemoteNGSpecs.csproj", "{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExternalConnectors", "ExternalConnectors\ExternalConnectors.csproj", "{A56A2029-79B8-492A-ABE5-D2BFE05801BD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release Installer and Portable|x64 = Release Installer and Portable|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|x64.ActiveCfg = Debug|x64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|x64.Build.0 = Debug|x64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer and Portable|x64.ActiveCfg = Release|x64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer and Portable|x64.Build.0 = Release|x64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|x64.ActiveCfg = Release|x64
{4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|x64.Build.0 = Release|x64
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug|x64.ActiveCfg = Debug|x64
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug|x64.Build.0 = Debug|x64
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Installer and Portable|x64.ActiveCfg = Release|x64
{1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release|x64.ActiveCfg = Release|x64
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug|x64.ActiveCfg = Debug|x64
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug|x64.Build.0 = Debug|x64
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release Installer and Portable|x64.ActiveCfg = Release|x64
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release Installer and Portable|x64.Build.0 = Release|x64
{5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release|x64.ActiveCfg = Release|x64
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Debug|x64.ActiveCfg = Debug|x64
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Debug|x64.Build.0 = Debug|x64
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Installer and Portable|x64.ActiveCfg = Release|x64
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Installer and Portable|x64.Build.0 = Release|x64
{F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release|x64.ActiveCfg = Release|x64
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Debug|x64.ActiveCfg = Debug|x64
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Debug|x64.Build.0 = Debug|x64
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Release Installer and Portable|x64.ActiveCfg = Release|x64
{16AA21E2-D6B7-427D-AB7D-AA8C611B724E}.Release|x64.ActiveCfg = Release Installer|x64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Debug|x64.ActiveCfg = Debug|x64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Debug|x64.Build.0 = Debug|x64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Installer and Portable|x64.ActiveCfg = Release|x64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release Installer and Portable|x64.Build.0 = Release|x64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release|x64.ActiveCfg = Release|x64
{A56A2029-79B8-492A-ABE5-D2BFE05801BD}.Release|x64.Build.0 = Release|x64
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

11
mRemoteNG/App.config Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="LanguageFolder" value="Language"/>
</appSettings>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Languages"/>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -0,0 +1,108 @@
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;
using mRemoteNG.Resources.Language;
using System.Runtime.Versioning;
namespace mRemoteNG.App
{
[SupportedOSPlatform("windows")]
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;
string errorText = string.Format(Language.ErrorFipsPolicyIncompatible, GeneralAppInfo.ProductName);
messageCollector.AddMessage(MessageClass.ErrorMsg, errorText, true);
//About to pop up a message, let's not block it...
FrmSplashScreenNew.GetInstance().Close();
DialogResult 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()
{
RegistryKey regKey = Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Control\Lsa");
if (!(regKey?.GetValue("FIPSAlgorithmPolicy") is int fipsPolicy))
return false;
return fipsPolicy != 0;
}
private static bool FipsPolicyEnabledForServer2008AndNewer()
{
RegistryKey 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;
Process[] 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;
}
}
}

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

@@ -0,0 +1,119 @@
using System;
using System.Linq;
using System.Runtime.Versioning;
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
{
[SupportedOSPlatform("windows")]
public static class Export
{
public static void ExportToFile(ConnectionInfo selectedNode, ConnectionTreeModel connectionTreeModel)
{
try
{
SaveFilter saveFilter = new();
using (FrmExport exportForm = new())
{
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:
ICryptographyProvider cryptographyProvider = new CryptoProviderFactoryFromSettings().Build();
RootNodeInfo rootNode = exportTarget.GetRootParent() as RootNodeInfo;
XmlConnectionNodeSerializer28 connectionNodeSerializer = new(
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.CredentialProviderCatalog);
break;
default:
throw new ArgumentOutOfRangeException(nameof(saveFormat), saveFormat, null);
}
string serializedData = serializer.Serialize(exportTarget);
FileDataProvider fileDataProvider = new(fileName);
fileDataProvider.Save(serializedData);
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionStackTrace($"Export.SaveExportFile(\"{fileName}\") failed.", ex);
}
finally
{
Runtime.ConnectionsService.RemoteConnectionsSyncronizer?.Enable();
}
}
}
}

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

@@ -0,0 +1,185 @@
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;
using mRemoteNG.Resources.Language;
using System.Runtime.Versioning;
namespace mRemoteNG.App
{
[SupportedOSPlatform("windows")]
public static class Import
{
public static void ImportFromFile(ContainerInfo importDestinationContainer)
{
try
{
using (OpenFileDialog openFileDialog = new())
{
openFileDialog.CheckFileExists = true;
openFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
openFileDialog.Multiselect = true;
List<string> fileTypes = new();
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, "*.*"});
fileTypes.AddRange(new[] { Language.FilterSecureCRT, "*.crt" });
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 ImportFromRemoteDesktopManagerCsv(ContainerInfo importDestinationContainer)
{
try
{
using (Runtime.ConnectionsService.BatchedSavingContext())
{
using (OpenFileDialog openFileDialog = new())
{
openFileDialog.CheckFileExists = true;
openFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
openFileDialog.Multiselect = false;
List<string> fileTypes = new();
fileTypes.AddRange(new[] {Language.FiltermRemoteRemoteDesktopManagerCSV, "*.csv"});
openFileDialog.Filter = string.Join("|", fileTypes.ToArray());
if (openFileDialog.ShowDialog() != DialogResult.OK)
return;
RemoteDesktopManagerImporter importer = new();
importer.Import(openFileDialog.FileName, importDestinationContainer);
}
}
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionMessage("App.Import.ImportFromRemoteDesktopManagerCsv() failed.", ex);
}
}
public static void HeadlessFileImport(
IEnumerable<string> filePaths,
ContainerInfo importDestinationContainer,
ConnectionsService connectionsService,
Action<string> exceptionAction = null)
{
using (connectionsService.BatchedSavingContext())
{
foreach (string fileName in filePaths)
{
try
{
IConnectionImporter<string> 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())
{
PortScanImporter importer = new(protocol);
importer.Import(hosts, importDestinationContainer);
}
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionMessage("App.Import.ImportFromPortScan() failed.", ex);
}
}
internal static void ImportFromPutty(ContainerInfo selectedNodeAsContainer)
{
try
{
using (Runtime.ConnectionsService.BatchedSavingContext())
{
RegistryImporter.Import("Software\\SimonTatham\\PuTTY\\Sessions", selectedNodeAsContainer);
}
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionMessage("App.Import.ImportFromPutty() failed.", ex);
}
}
private static IConnectionImporter<string> BuildConnectionImporterFromFileExtension(string fileName)
{
// TODO: Use the file contents to determine the file type instead of trusting the extension
string 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();
case ".crt":
return new SecureCRTImporter();
default:
throw new FileFormatException("Unrecognized file format.");
}
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Runtime.Versioning;
namespace mRemoteNG.App.Info
{
[SupportedOSPlatform("windows")]
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 Version ConnectionFileVersion = new(3, 0);
}
}

View File

@@ -0,0 +1,13 @@
using System.Runtime.Versioning;
namespace mRemoteNG.App.Info
{
[SupportedOSPlatform("windows")]
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,61 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Versioning;
using System.Threading;
using System.Windows.Forms;
using static System.Environment;
namespace mRemoteNG.App.Info
{
[SupportedOSPlatform("windows")]
public static class GeneralAppInfo
{
public const string UrlHome = "https://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 readonly 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 = "";
private static readonly string puttyPath = HomePath + "\\PuTTYNG.exe";
public static string UserAgent
{
get
{
List<string> details =
[
"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}");
string detailsString = string.Join("; ", [.. details]);
return $"Mozilla/5.0 ({detailsString}) {ProductName}/{ApplicationVersion}";
}
}
public static string PuttyPath => puttyPath;
public static Version GetApplicationVersion()
{
_ = System.Version.TryParse(ApplicationVersion, out Version v);
return v;
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.IO;
using System.Reflection;
using System.Runtime.Versioning;
using System.Windows.Forms;
using mRemoteNG.Connection;
namespace mRemoteNG.App.Info
{
[SupportedOSPlatform("windows")]
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 LocalConnectionProperties { get; } = "LocalConnectionProperties.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,79 @@
using System;
using System.Runtime.Versioning;
using mRemoteNG.Properties;
// ReSharper disable InconsistentNaming
namespace mRemoteNG.App.Info
{
[SupportedOSPlatform("windows")]
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()
{
string channel = IsValidChannel(Properties.OptionsUpdatesPage.Default.UpdateChannel) ? Properties.OptionsUpdatesPage.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(Properties.OptionsUpdatesPage.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,87 @@
using Microsoft.Win32;
using System.Runtime.Versioning;
namespace mRemoteNG.App.Info
{
[SupportedOSPlatform("windows")]
public static class WindowsRegistryInfo
{
#region General Parameters
public const RegistryHive Hive = RegistryHive.LocalMachine;
public const string RootKey = "SOFTWARE\\mRemoteNG";
private const string OptionsSubKey = "Options";
#endregion
#region Key Locations
// StartupExit
// Registry subkey for general application startup and exit settings
// Registry subkey for startup and exit options page settings
public const string StartupExit = RootKey + "\\StartupExit";
public const string StartupExitOptions = StartupExit + "\\" + OptionsSubKey;
// Appearance
// Registry subkey for general application appearance settings
// Registry subkey for appearance options page settings
public const string Appearance = RootKey + "\\Appearance";
public const string AppearanceOptions = Appearance + "\\" + OptionsSubKey;
// Connections
// Registry subkey for general application connection settings
// Registry subkey for connections options page settings
public const string Connection = RootKey + "\\Connections";
public const string ConnectionOptions = Connection + "\\" + OptionsSubKey;
// Tabs & Panels
// Registry subkey for general application tabs and panels settings
// Registry subkey for tabs and panels options page settings
public const string TabsAndPanels = RootKey + "\\TabsAndPanels";
public const string TabsAndPanelsOptions = TabsAndPanels + "\\" + OptionsSubKey;
// Notifications
// Registry subkey for general application notifications settings
// Registry subkey for notifications options page settings
public const string Notification = RootKey + "\\Notifications";
public const string NotificationOptions = Notification + "\\" + OptionsSubKey;
// Credential
// Registry subkey for general application credentials settings
// Registry subkey for credentials options page settings
public const string Credential = RootKey + "\\Credentials";
public const string CredentialOptions = Credential + "\\" + OptionsSubKey;
// SQL Server
// Registry subkey for general application SQL server settings
// Registry subkey for SQL server options page settings
public const string SQLServer = RootKey + "\\SQLServer";
public const string SQLServerOptions = SQLServer + "\\" + OptionsSubKey;
// Updates
// Registry subkey for general application update settings
// Registry subkey for updates options page settings
public const string Update = RootKey + "\\Updates";
public const string UpdateOptions = Update + "\\" + OptionsSubKey;
// Security
// Registry subkey for general application security settings
// Registry subkey for security options page settings
public const string Security = RootKey + "\\Security";
public const string SecurityOptions = Security + "\\" + OptionsSubKey;
// Advanced
// Registry subkey for general application advanced settings
// Registry subkey for advanced options page settings
public const string Advanced = RootKey + "\\Advanced";
public const string AdvancedOptions = Advanced + "\\" + OptionsSubKey;
// Backup
// Registry subkey for general application backup settings
// Registry subkey for backup options page settings
public const string Backup = RootKey + "\\Backup";
public const string BackupOptions = Backup + "\\" + OptionsSubKey;
#endregion
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.IO;
using System.Runtime.Versioning;
using mRemoteNG.Connection;
namespace mRemoteNG.App.Initialization
{
[SupportedOSPlatform("windows")]
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 (string f in Directory.GetFiles(_path, "*.ico", SearchOption.AllDirectories))
{
FileInfo fInfo = new(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,21 @@
using System.IO;
using System.Runtime.Versioning;
using mRemoteNG.Config.Connections;
using mRemoteNG.Properties;
namespace mRemoteNG.App.Initialization
{
[SupportedOSPlatform("windows")]
public class CredsAndConsSetup
{
public void LoadCredsAndCons()
{
new SaveConnectionsOnEdit(Runtime.ConnectionsService);
if (Properties.App.Default.FirstStart && !Properties.OptionsBackupPage.Default.LoadConsFromCustomLocation && !File.Exists(Runtime.ConnectionsService.GetStartupConnectionFileName()))
Runtime.ConnectionsService.NewConnectionsFile(Runtime.ConnectionsService.GetStartupConnectionFileName());
Runtime.LoadConnections();
}
}
}

View File

@@ -0,0 +1,76 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Versioning;
using mRemoteNG.Messages;
using mRemoteNG.Messages.MessageFilteringOptions;
using mRemoteNG.Messages.MessageWriters;
using mRemoteNG.Messages.WriterDecorators;
namespace mRemoteNG.App.Initialization
{
[SupportedOSPlatform("windows")]
public class MessageCollectorSetup
{
public static void SetupMessageCollector(MessageCollector messageCollector, IList<IMessageWriter> messageWriterList)
{
messageCollector.CollectionChanged += (o, args) =>
{
if (args.NewItems == null) return;
IMessage[] messages = args.NewItems.Cast<IMessage>().ToArray();
foreach (IMessageWriter printer in messageWriterList)
{
foreach (IMessage 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,121 @@
using System;
using System.Management;
using System.Runtime.Versioning;
using System.Threading;
using System.Windows.Forms;
using mRemoteNG.Messages;
using mRemoteNG.Resources.Language;
namespace mRemoteNG.App.Initialization
{
[SupportedOSPlatform("windows")]
public class StartupDataLogger
{
private readonly MessageCollector _messageCollector;
public StartupDataLogger(MessageCollector messageCollector)
{
_messageCollector = messageCollector ?? throw new ArgumentNullException(nameof(messageCollector));
}
public void LogStartupData()
{
LogApplicationData();
LogCmdLineArgs();
LogSystemData();
LogClrData();
LogCultureData();
}
private void LogSystemData()
{
string osData = GetOperatingSystemData();
string architecture = GetArchitectureData();
string[] nonEmptyData = Array.FindAll(new[] {osData, architecture}, s => !string.IsNullOrEmpty(s));
string data = string.Join(" ", nonEmptyData);
_messageCollector.AddMessage(MessageClass.InformationMsg, data, true);
}
private string GetOperatingSystemData()
{
string osVersion = string.Empty;
string servicePack = string.Empty;
try
{
foreach (ManagementBaseObject o in new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem WHERE Primary=True")
.Get())
{
ManagementObject 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);
}
string osData = string.Join(" ", osVersion, servicePack);
return osData;
}
private string GetOSServicePack(string servicePack, ManagementObject managementObject)
{
int servicePackNumber = Convert.ToInt32(managementObject.GetPropertyValue("ServicePackMajorVersion"));
if (servicePackNumber != 0)
{
servicePack = $"Service Pack {servicePackNumber}";
}
return servicePack;
}
private string GetArchitectureData()
{
string architecture = string.Empty;
try
{
foreach (ManagementBaseObject o in new ManagementObjectSearcher("SELECT AddressWidth FROM Win32_Processor WHERE DeviceID=\'CPU0\'").Get())
{
ManagementObject managementObject = (ManagementObject)o;
int 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()
{
string data = $"{Application.ProductName} {Application.ProductVersion}";
if (Runtime.IsPortableEdition)
data += $" {Language.PortableEdition}";
data += " starting.";
_messageCollector.AddMessage(MessageClass.InformationMsg, data, true);
}
private void LogCmdLineArgs()
{
string data = $"Command Line: {string.Join(" ", Environment.GetCommandLineArgs())}";
_messageCollector.AddMessage(MessageClass.InformationMsg, data, true);
}
private void LogClrData()
{
string data = $"Microsoft .NET CLR {Environment.Version}";
_messageCollector.AddMessage(MessageClass.InformationMsg, data, true);
}
private void LogCultureData()
{
string data = $"System Culture: {Thread.CurrentThread.CurrentUICulture.Name}/{Thread.CurrentThread.CurrentUICulture.NativeName}";
_messageCollector.AddMessage(MessageClass.InformationMsg, data, true);
}
}
}

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

@@ -0,0 +1,81 @@
using System;
using System.IO;
using System.Runtime.Versioning;
using System.Windows.Forms;
using log4net;
using log4net.Appender;
using log4net.Config;
using log4net.Repository;
namespace mRemoteNG.App
{
[SupportedOSPlatform("windows")]
public class Logger
{
public static readonly Logger Instance = new();
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(Properties.OptionsNotificationsPage.Default.LogFilePath))
{
Properties.OptionsNotificationsPage.Default.LogFilePath = BuildLogFilePath();
}
SetLogPath(Properties.OptionsNotificationsPage.Default.LogToApplicationDirectory ? DefaultLogPath : Properties.OptionsNotificationsPage.Default.LogFilePath);
}
public void SetLogPath(string path)
{
ILoggerRepository repository = LogManager.GetRepository("mRemoteNG");
XmlConfigurator.Configure(repository, new FileInfo("log4net.config"));
IAppender[] appenders = repository.GetAppenders();
foreach (IAppender appender in appenders)
{
RollingFileAppender fileAppender = (RollingFileAppender)appender;
if (fileAppender is not { Name: "LogFileAppender" }) continue;
fileAppender.File = path;
fileAppender.ActivateOptions();
}
Log = LogManager.GetLogger("mRemoteNG", "Logger");
}
private static string BuildLogFilePath()
{
string logFilePath = Runtime.IsPortableEdition ? GetLogPathPortableEdition() : GetLogPathNormalEdition();
string logFileName = Path.ChangeExtension(Application.ProductName, ".log");
if (logFileName == null) return "mRemoteNG.log";
string 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,554 @@
using System;
using System.Drawing;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
#pragma warning disable 649
#pragma warning disable 169
namespace mRemoteNG.App
{
[SupportedOSPlatform("windows")]
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", CharSet = CharSet.Auto)]
internal static extern bool ChangeClipboardChain(
IntPtr hWndRemove, // handle to window to remove
IntPtr hWndNewNext // handle to next window
);
[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)]
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,175 @@
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Reflection;
using System.Runtime.Versioning;
using System.Threading;
using System.Windows.Forms;
using mRemoteNG.Config.Settings;
using mRemoteNG.UI.Forms;
namespace mRemoteNG.App
{
[SupportedOSPlatform("windows")]
public static class ProgramRoot
{
private static Mutex _mutex;
private static FrmSplashScreenNew _frmSplashScreen = null;
private static string customResourcePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Languages");
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static void Main(string[] args)
{
Trace.WriteLine("!!!!!!=============== TEST ==================!!!!!!!!!!!!!");
// Forcing to load System.Configuration.ConfigurationManager before any other assembly to be able to check settings
try
{
string assemblyFile = "System.Configuration.ConfigurationManager" + ".dll";
string assemblyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assemblies", assemblyFile);
if (File.Exists(assemblyPath))
{
Assembly.LoadFrom(assemblyPath);
}
}
catch (FileNotFoundException ex)
{
Trace.WriteLine("Error occured: " + ex.Message);
}
//Subscribe to AssemblyResolve event
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
//Check if local settings DB exist or accessible
CheckLockalDB();
Lazy<bool> singleInstanceOption = new Lazy<bool>(() => Properties.OptionsStartupExitPage.Default.SingleInstance);
if (singleInstanceOption.Value)
{
StartApplicationAsSingleInstance();
}
else
{
StartApplication();
}
}
private static void CheckLockalDB()
{
LocalSettingsDBManager settingsManager = new LocalSettingsDBManager(dbPath: "mRemoteNG.appSettings", useEncryption: false, schemaFilePath: "");
}
private static Assembly OnAssemblyResolve(object sender, ResolveEventArgs resolveArgs)
{
string assemblyName = new AssemblyName(resolveArgs.Name).Name.Replace(".resources", string.Empty);
string assemblyFile = assemblyName + ".dll";
string assemblyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assemblies", assemblyFile);
if (File.Exists(assemblyPath))
{
return Assembly.LoadFrom(assemblyPath);
}
return null;
}
private static void StartApplication()
{
CatchAllUnhandledExceptions();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
_frmSplashScreen = FrmSplashScreenNew.GetInstance();
Screen targetScreen = Screen.PrimaryScreen;
Rectangle viewport = targetScreen.WorkingArea;
_frmSplashScreen.Top = viewport.Top;
_frmSplashScreen.Left = viewport.Left;
// normally it should be screens[1] however due DPI apply 1 size "same" as default with 100%
_frmSplashScreen.Left = viewport.Left + (targetScreen.Bounds.Size.Width - _frmSplashScreen.Width) / 2;
_frmSplashScreen.Top = viewport.Top + (targetScreen.Bounds.Size.Height - _frmSplashScreen.Height) / 2;
_frmSplashScreen.ShowInTaskbar = false;
_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 bool newInstanceCreated);
if (!newInstanceCreated)
{
SwitchToCurrentInstance();
return;
}
StartApplication();
GC.KeepAlive(_mutex);
}
private static void SwitchToCurrentInstance()
{
IntPtr 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()
{
IntPtr windowHandle = IntPtr.Zero;
Process currentProcess = Process.GetCurrentProcess();
foreach (Process 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()
{
System.Windows.Forms.Application.ThreadException += ApplicationOnThreadException;
System.Windows.Forms.Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
}
private static void ApplicationOnThreadException(object sender, ThreadExceptionEventArgs e)
{
// if (PresentationSource.FromVisual(FrmSplashScreenNew))
FrmSplashScreenNew.GetInstance().Close();
if (FrmMain.Default.IsDisposed) return;
FrmUnhandledException window = new(e.Exception, false);
window.ShowDialog(FrmMain.Default);
}
private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
//TODO: Check if splash closed properly
//if (!FrmSplashScreenNew.GetInstance().IsDisposed)
// FrmSplashScreenNew.GetInstance().Close();
FrmUnhandledException window = new(e.ExceptionObject as Exception, e.IsTerminating);
window.ShowDialog(FrmMain.Default);
}
}
}

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

@@ -0,0 +1,209 @@
using mRemoteNG.App.Info;
using mRemoteNG.Config.Putty;
using mRemoteNG.Connection;
using mRemoteNG.Credential;
using mRemoteNG.Credential.Repositories;
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;
using mRemoteNG.Resources.Language;
using System.Runtime.Versioning;
namespace mRemoteNG.App
{
[SupportedOSPlatform("windows")]
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 => false;
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 ICredentialRepositoryList CredentialProviderCatalog { get; } = new CredentialRepositoryList();
public static ConnectionInitiator ConnectionInitiator { get; set; } = new ConnectionInitiator();
public static ConnectionsService ConnectionsService { get; } = new ConnectionsService(PuttySessionsManager.Instance);
#region Connections Loading/Saving
public static void LoadConnectionsAsync()
{
Thread t = new(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)
{
string connectionFileName = "";
try
{
// disable sql update checking while we are loading updates
ConnectionsService.RemoteConnectionsSyncronizer?.Disable();
if (withDialog)
{
OpenFileDialog loadDialog = DialogFactory.BuildLoadConnectionsDialog();
if (loadDialog.ShowDialog() != DialogResult.OK)
return;
connectionFileName = loadDialog.FileName;
Properties.OptionsDBsPage.Default.UseSQLServer = false;
Properties.OptionsDBsPage.Default.Save();
}
else if (!Properties.OptionsDBsPage.Default.UseSQLServer)
{
connectionFileName = ConnectionsService.GetStartupConnectionFileName();
}
ConnectionsService.LoadConnections(Properties.OptionsDBsPage.Default.UseSQLServer, false, connectionFileName);
if (Properties.OptionsDBsPage.Default.UseSQLServer)
{
ConnectionsService.LastSqlUpdate = DateTime.Now.ToUniversalTime();
}
else
{
ConnectionsService.LastFileUpdate = System.IO.File.GetLastWriteTime(connectionFileName);
}
// re-enable sql update checking after updates are loaded
ConnectionsService.RemoteConnectionsSyncronizer?.Enable();
}
catch (Exception ex)
{
FrmSplashScreenNew.GetInstance().Close();
if (Properties.OptionsDBsPage.Default.UseSQLServer)
{
MessageCollector.AddExceptionMessage(Language.LoadFromSqlFailed, ex);
string 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:
Properties.OptionsDBsPage.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
};
bool 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
}
}

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

@@ -0,0 +1,38 @@
using System.Runtime.Versioning;
using System.Windows.Forms;
using mRemoteNG.UI.Forms;
using WeifenLuo.WinFormsUI.Docking;
namespace mRemoteNG.App
{
public static class Screens
{
[SupportedOSPlatform("windows")]
public static void SendFormToScreen(Screen screen)
{
FrmMain frmMain = FrmMain.Default;
bool 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;
}
}
}

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

@@ -0,0 +1,127 @@
using mRemoteNG.Tools;
using System;
using System.Diagnostics;
using System.Windows.Forms;
using mRemoteNG.Config.Connections;
using mRemoteNG.Config.Putty;
using mRemoteNG.Properties;
using mRemoteNG.UI.Controls;
using mRemoteNG.UI.Forms;
using mRemoteNG.Resources.Language;
using System.Runtime.Versioning;
// ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.App
{
[SupportedOSPlatform("windows")]
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()
{
DateTime lastUpdate;
DateTime updateDate;
DateTime currentDate = DateTime.Now;
if ((Properties.OptionsBackupPage.Default.SaveConnectionsFrequency == (int)ConnectionsBackupFrequencyEnum.OnExit))
{
Runtime.ConnectionsService.SaveConnections();
return;
}
lastUpdate = Runtime.ConnectionsService.UsingDatabase ? Runtime.ConnectionsService.LastSqlUpdate : Runtime.ConnectionsService.LastFileUpdate;
switch (Properties.OptionsBackupPage.Default.SaveConnectionsFrequency)
{
case (int)ConnectionsBackupFrequencyEnum.Daily:
updateDate = lastUpdate.AddDays(1);
break;
case (int)ConnectionsBackupFrequencyEnum.Weekly:
updateDate = lastUpdate.AddDays(7);
break;
default:
return;
}
if (currentDate >= updateDate)
{
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(new ProcessStartInfo(_updateFilePath) { UseShellExecute = true });
}
}
}

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

@@ -0,0 +1,98 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using mRemoteNG.App.Info;
using mRemoteNG.App.Initialization;
using mRemoteNG.App.Update;
using mRemoteNG.Config.Connections.Multiuser;
using mRemoteNG.Config.Settings.Registry;
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
{
[SupportedOSPlatform("windows")]
public class Startup
{
private RegistryLoader _RegistryLoader;
private AppUpdater _appUpdate;
private readonly ConnectionIconLoader _connectionIconLoader;
private readonly FrmMain _frmMain = FrmMain.Default;
public static Startup Instance { get; } = new Startup();
private Startup()
{
_RegistryLoader = RegistryLoader.Instance; //created instance
_appUpdate = new AppUpdater();
_connectionIconLoader = new ConnectionIconLoader(GeneralAppInfo.HomePath + "\\Icons\\");
}
public void InitializeProgram(MessageCollector messageCollector)
{
Debug.Print("---------------------------" + Environment.NewLine + "[START] - " + Convert.ToString(DateTime.Now, CultureInfo.InvariantCulture));
StartupDataLogger startupLogger = new(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)
{
StartupArgumentsInterpreter interpreter = new(messageCollector);
interpreter.ParseArguments(Environment.GetCommandLineArgs());
}
public void CreateConnectionsProvider(MessageCollector messageCollector)
{
messageCollector.AddMessage(MessageClass.DebugMsg, "Determining if we need a database syncronizer");
if (!Properties.OptionsDBsPage.Default.UseSQLServer) return;
messageCollector.AddMessage(MessageClass.DebugMsg, "Creating database syncronizer");
Runtime.ConnectionsService.RemoteConnectionsSyncronizer = new RemoteConnectionsSyncronizer(new SqlConnectionsUpdateChecker());
Runtime.ConnectionsService.RemoteConnectionsSyncronizer.Enable();
}
public async Task CheckForUpdate()
{
if (_appUpdate == null)
{
_appUpdate = new AppUpdater();
}
else if (_appUpdate.IsGetUpdateInfoRunning)
{
return;
}
DateTime nextUpdateCheck = Convert.ToDateTime(Properties.OptionsUpdatesPage.Default.CheckForUpdatesLastCheck.Add(TimeSpan.FromDays(Convert.ToDouble(Properties.OptionsUpdatesPage.Default.CheckForUpdatesFrequencyDays))));
if (!Properties.OptionsUpdatesPage.Default.UpdatePending && DateTime.UtcNow < nextUpdateCheck)
{
return;
}
try
{
await _appUpdate.GetUpdateInfoAsync();
if (_appUpdate.IsUpdateAvailable())
{
Windows.Show(WindowType.Update);
}
}
catch (Exception ex)
{
Runtime.MessageCollector.AddExceptionMessage("CheckForUpdate() failed.", ex);
}
}
}
}

View File

@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using mRemoteNG.Properties;
// ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.App
{
[SupportedOSPlatform("windows")]
[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 (string CultureName in Properties.AppUI.Default.SupportedUICultures.Split(','))
{
try
{
CultureInfo CultureInfo = new(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)
{
string[] Names = new string[SingletonInstance.Count + 1];
string[] NativeNames = new string[SingletonInstance.Count + 1];
SingletonInstance.Keys.CopyTo(Names, 0);
SingletonInstance.Values.CopyTo(NativeNames, 0);
for (int 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
{
List<string> ValueList = new();
foreach (string Value in SingletonInstance.Values)
{
ValueList.Add(Value);
}
return ValueList;
}
}
}
}

View File

@@ -0,0 +1,266 @@
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using mRemoteNG.App.Info;
using mRemoteNG.Security.SymmetricEncryption;
using System.Security.Cryptography;
using System.Threading.Tasks;
using mRemoteNG.Properties;
using System.Runtime.Versioning;
#if !PORTABLE
using mRemoteNG.Tools;
#else
using System.Windows.Forms;
#endif
// ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.App.Update
{
[SupportedOSPlatform("windows")]
public class AppUpdater
{
private const int _bufferLength = 8192;
private WebProxy _webProxy;
private HttpClient _httpClient;
private CancellationTokenSource _changeLogCancelToken;
private CancellationTokenSource _getUpdateInfoCancelToken;
#region Public Properties
public UpdateInfo CurrentUpdateInfo { get; private set; }
public bool IsGetUpdateInfoRunning
{
get
{
return _getUpdateInfoCancelToken != null;
}
}
private bool IsGetChangeLogRunning
{
get
{
return _changeLogCancelToken != null;
}
}
#endregion
#region Public Methods
public AppUpdater()
{
SetDefaultProxySettings();
}
private void SetDefaultProxySettings()
{
bool shouldWeUseProxy = Properties.OptionsUpdatesPage.Default.UpdateUseProxy;
string proxyAddress = Properties.OptionsUpdatesPage.Default.UpdateProxyAddress;
int port = Properties.OptionsUpdatesPage.Default.UpdateProxyPort;
bool useAuthentication = Properties.OptionsUpdatesPage.Default.UpdateProxyUseAuthentication;
string username = Properties.OptionsUpdatesPage.Default.UpdateProxyAuthUser;
LegacyRijndaelCryptographyProvider cryptographyProvider = new();
string password = cryptographyProvider.Decrypt(Properties.OptionsUpdatesPage.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;
}
UpdateHttpClient();
}
public bool IsUpdateAvailable()
{
if (CurrentUpdateInfo == null || !CurrentUpdateInfo.IsValid)
{
return false;
}
return CurrentUpdateInfo.Version > GeneralAppInfo.GetApplicationVersion();
}
public async Task DownloadUpdateAsync(IProgress<int> progress)
{
if (IsGetUpdateInfoRunning)
{
_getUpdateInfoCancelToken.Cancel();
_getUpdateInfoCancelToken.Dispose();
_getUpdateInfoCancelToken = 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
try
{
_getUpdateInfoCancelToken = new CancellationTokenSource();
using HttpResponseMessage response = await _httpClient.GetAsync(CurrentUpdateInfo.DownloadAddress, HttpCompletionOption.ResponseHeadersRead, _getUpdateInfoCancelToken.Token);
byte[] buffer = new byte[_bufferLength];
long totalBytes = response.Content.Headers.ContentLength ?? 0;
long readBytes = 0L;
await using (Stream httpStream = await response.Content.ReadAsStreamAsync(_getUpdateInfoCancelToken.Token))
{
await using FileStream fileStream = new(CurrentUpdateInfo.UpdateFilePath, FileMode.Create,
FileAccess.Write, FileShare.None, _bufferLength, true);
while (readBytes <= totalBytes || !_getUpdateInfoCancelToken.IsCancellationRequested)
{
int bytesRead =
await httpStream.ReadAsync(buffer.AsMemory(0, _bufferLength), _getUpdateInfoCancelToken.Token);
if (bytesRead == 0)
{
progress.Report(100);
break;
}
await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead), _getUpdateInfoCancelToken.Token);
readBytes += bytesRead;
int percentComplete = (int)(readBytes * 100 / totalBytes);
progress.Report(percentComplete);
}
}
#if !PORTABLE
Authenticode updateAuthenticode = new(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 SHA512 checksum = SHA512.Create();
await using FileStream stream = File.OpenRead(CurrentUpdateInfo.UpdateFilePath);
byte[] hash = await checksum.ComputeHashAsync(stream);
string hashString = BitConverter.ToString(hash).Replace("-", "").ToUpperInvariant();
if (!hashString.Equals(CurrentUpdateInfo.Checksum))
throw new Exception("SHA512 Hashes didn't match!");
} finally{
_getUpdateInfoCancelToken?.Dispose();
_getUpdateInfoCancelToken = null;
}
}
#endregion
#region Private Methods
private void UpdateHttpClient()
{
if (_httpClient != null)
{
_httpClient.Dispose();
}
HttpClientHandler httpClientHandler = new();
if (_webProxy != null)
{
httpClientHandler.UseProxy = true;
httpClientHandler.Proxy = _webProxy;
}
_httpClient = new HttpClient(httpClientHandler);
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(GeneralAppInfo.UserAgent);
}
public async Task GetUpdateInfoAsync()
{
if (IsGetUpdateInfoRunning)
{
_getUpdateInfoCancelToken.Cancel();
_getUpdateInfoCancelToken.Dispose();
_getUpdateInfoCancelToken = null;
}
try
{
_getUpdateInfoCancelToken = new CancellationTokenSource();
string updateInfo = await _httpClient.GetStringAsync(UpdateChannelInfo.GetUpdateChannelInfo(), _getUpdateInfoCancelToken.Token);
CurrentUpdateInfo = UpdateInfo.FromString(updateInfo);
Properties.OptionsUpdatesPage.Default.CheckForUpdatesLastCheck = DateTime.UtcNow;
if (!Properties.OptionsUpdatesPage.Default.UpdatePending)
{
Properties.OptionsUpdatesPage.Default.UpdatePending = IsUpdateAvailable();
}
}
finally
{
_getUpdateInfoCancelToken?.Dispose();
_getUpdateInfoCancelToken = null;
}
}
public async Task<string> GetChangeLogAsync()
{
if (IsGetChangeLogRunning)
{
_changeLogCancelToken.Cancel();
_changeLogCancelToken.Dispose();
_changeLogCancelToken = null;
}
try
{
_changeLogCancelToken = new CancellationTokenSource();
return await _httpClient.GetStringAsync(CurrentUpdateInfo.ChangeLogAddress, _changeLogCancelToken.Token);
}
finally
{
_changeLogCancelToken?.Dispose();
_changeLogCancelToken = null;
}
}
#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 (StringReader sr = new(content))
{
string line;
while ((line = sr.ReadLine()) != null)
{
string trimmedLine = line.Trim();
if (trimmedLine.Length == 0)
continue;
if (trimmedLine.Substring(0, 1).IndexOfAny(commentCharacters) != -1)
continue;
string[] 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")
{
string value = GetString(key);
return string.IsNullOrEmpty(value) ? null : new Version(value);
}
public Uri GetUri(string key)
{
string 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()
{
string value = GetString("dURL");
string[] 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)
{
UpdateInfo newInfo = new();
if (string.IsNullOrEmpty(input))
{
newInfo.IsValid = false;
}
else
{
UpdateFile updateFile = new(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;
}
}
}

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

@@ -0,0 +1,82 @@
#region Usings
using System;
using System.Runtime.Versioning;
using mRemoteNG.Resources.Language;
using mRemoteNG.UI;
using mRemoteNG.UI.Forms;
using mRemoteNG.UI.Window;
#endregion
namespace mRemoteNG.App
{
[SupportedOSPlatform("windows")]
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
{
WeifenLuo.WinFormsUI.Docking.DockPanel 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:
FrmMain.OptionsForm.SetActivatedPage(Language.StartupExit);
FrmMain.OptionsForm.Visible = true;
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,9 @@
namespace mRemoteNG.Config.ACL
{
public enum ACLPermissions
{
Hidden = 0,
ReadOnly = 1,
WriteAllow = 2
}
}

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,13 @@
namespace mRemoteNG.Config.Connections
{
public enum ConnectionsBackupFrequencyEnum
{
Unassigned = 0,
Never = 1,
OnEdit = 2,
OnExit = 3,
Daily = 4,
Weekly = 5,
Custom = 6
}
}

View File

@@ -0,0 +1,59 @@
using System;
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 Optional<ConnectionTreeModel> PreviousConnectionTreeModel { 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 ConnectionTreeModel NewConnectionTreeModel { 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 ConnectionsLoadedEventArgs(Optional<ConnectionTreeModel> previousTreeModelModel,
ConnectionTreeModel newTreeModelModel,
bool previousSourceWasDatabase,
bool newSourceIsDatabase,
string newSourcePath)
{
if (previousTreeModelModel == null)
throw new ArgumentNullException(nameof(previousTreeModelModel));
if (newTreeModelModel == null)
throw new ArgumentNullException(nameof(newTreeModelModel));
if (newSourcePath == null)
throw new ArgumentNullException(nameof(newSourcePath));
PreviousConnectionTreeModel = previousTreeModelModel;
PreviousSourceWasDatabase = previousSourceWasDatabase;
NewConnectionTreeModel = newTreeModelModel;
NewSourceIsDatabase = newSourceIsDatabase;
NewSourcePath = newSourcePath;
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using mRemoteNG.Tree;
namespace mRemoteNG.Config.Connections
{
public class ConnectionsSavedEventArgs
{
public ConnectionTreeModel ModelThatWasSaved { get; }
public bool PreviouslyUsingDatabase { get; }
public bool UsingDatabase { get; }
public string ConnectionFileName { get; }
public ConnectionsSavedEventArgs(ConnectionTreeModel 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,37 @@
using System;
using System.Runtime.Versioning;
using mRemoteNG.App;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Csv;
using mRemoteNG.Security;
using mRemoteNG.Tree;
namespace mRemoteNG.Config.Connections
{
[SupportedOSPlatform("windows")]
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 = "")
{
CsvConnectionsSerializerMremotengFormat csvConnectionsSerializer =
new(_saveFilter, Runtime.CredentialProviderCatalog);
FileDataProvider dataProvider = new(_connectionFileName);
string csvContent = csvConnectionsSerializer.Serialize(connectionTreeModel);
dataProvider.Save(csvContent);
}
}
}

View File

@@ -0,0 +1,9 @@
using mRemoteNG.Tree;
namespace mRemoteNG.Config.Connections
{
public interface IConnectionsLoader
{
ConnectionTreeModel 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,94 @@
using mRemoteNG.App;
using System;
using System.Runtime.Versioning;
using System.Timers;
// ReSharper disable ArrangeAccessorOwnerBody
namespace mRemoteNG.Config.Connections.Multiuser
{
[SupportedOSPlatform("windows")]
public class RemoteConnectionsSyncronizer : IConnectionsUpdateChecker
{
private readonly System.Timers.Timer _updateTimer;
private readonly IConnectionsUpdateChecker _updateChecker;
public double TimerIntervalInMilliseconds
{
get { return _updateTimer.Interval; }
}
public RemoteConnectionsSyncronizer(IConnectionsUpdateChecker updateChecker)
{
_updateChecker = updateChecker;
_updateTimer = new System.Timers.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, false, "");
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.Runtime.Versioning;
using System.Threading;
using mRemoteNG.App;
using mRemoteNG.Config.DatabaseConnectors;
using mRemoteNG.Messages;
namespace mRemoteNG.Config.Connections.Multiuser
{
[SupportedOSPlatform("windows")]
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();
bool updateIsAvailable = DatabaseIsMoreUpToDateThanUs();
if (updateIsAvailable)
RaiseConnectionsUpdateAvailableEvent();
RaiseUpdateCheckFinishedEvent(updateIsAvailable);
return updateIsAvailable;
}
public void IsUpdateAvailableAsync()
{
Thread thread = new(() => 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()
{
DateTime lastUpdateInDb = GetLastUpdateTimeFromDbResponse();
bool amTheLastoneUpdated = CheckIfIAmTheLastOneUpdated(lastUpdateInDb);
return (lastUpdateInDb > LastUpdateTime && !amTheLastoneUpdated);
}
private bool CheckIfIAmTheLastOneUpdated(DateTime lastUpdateInDb)
{
DateTime lastSqlUpdateWithoutMilliseconds = new(LastUpdateTime.Ticks - (LastUpdateTime.Ticks % TimeSpan.TicksPerSecond), LastUpdateTime.Kind);
return lastUpdateInDb == lastSqlUpdateWithoutMilliseconds;
}
private DateTime GetLastUpdateTimeFromDbResponse()
{
DateTime lastUpdateInDb = default(DateTime);
try
{
DbDataReader 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)
{
ConnectionsUpdateCheckFinishedEventArgs args = new() { UpdateAvailable = updateAvailable};
UpdateCheckFinished?.Invoke(this, args);
}
public event ConnectionsUpdateAvailableEventHandler ConnectionsUpdateAvailable;
private void RaiseConnectionsUpdateAvailableEvent()
{
Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Remote connection update is available");
ConnectionsUpdateAvailableEventArgs args = new(_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,59 @@
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using mRemoteNG.Connection;
using mRemoteNG.UI.Forms;
using mRemoteNG.Properties;
using System.Runtime.Versioning;
namespace mRemoteNG.Config.Connections
{
[SupportedOSPlatform("windows")]
public class SaveConnectionsOnEdit
{
private readonly ConnectionsService _connectionsService;
public SaveConnectionsOnEdit(ConnectionsService connectionsService)
{
if (connectionsService == null)
throw new ArgumentNullException(nameof(connectionsService));
_connectionsService = connectionsService;
connectionsService.ConnectionsLoaded += ConnectionsServiceOnConnectionsLoaded;
}
private void ConnectionsServiceOnConnectionsLoaded(object sender, ConnectionsLoadedEventArgs connectionsLoadedEventArgs)
{
connectionsLoadedEventArgs.NewConnectionTreeModel.CollectionChanged += ConnectionTreeModelOnCollectionChanged;
connectionsLoadedEventArgs.NewConnectionTreeModel.PropertyChanged += ConnectionTreeModelOnPropertyChanged;
foreach (Tree.ConnectionTreeModel oldTree in connectionsLoadedEventArgs.PreviousConnectionTreeModel)
{
oldTree.CollectionChanged -= ConnectionTreeModelOnCollectionChanged;
oldTree.PropertyChanged -= ConnectionTreeModelOnPropertyChanged;
}
}
private void ConnectionTreeModelOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
SaveConnectionOnEdit(propertyChangedEventArgs.PropertyName);
}
private void ConnectionTreeModelOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
SaveConnectionOnEdit();
}
private void SaveConnectionOnEdit(string propertyName = "")
{
//OBSOLETE: mRemoteNG.Settings.Default.SaveConnectionsAfterEveryEdit is obsolete and should be removed in a future release
if (Properties.OptionsBackupPage.Default.SaveConnectionsAfterEveryEdit || (Properties.OptionsBackupPage.Default.SaveConnectionsFrequency == (int)ConnectionsBackupFrequencyEnum.OnEdit))
{
if (FrmMain.Default.IsClosing)
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,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Versioning;
using System.Security;
using mRemoteNG.Config.DatabaseConnectors;
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Config.Serializers;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Sql;
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;
using mRemoteNG.Tree.Root;
namespace mRemoteNG.Config.Connections
{
[SupportedOSPlatform("windows")]
public class SqlConnectionsLoader : IConnectionsLoader
{
private readonly IDeserializer<string, IEnumerable<LocalConnectionPropertiesModel>> _localConnectionPropertiesDeserializer;
private readonly IDataProvider<string> _dataProvider;
private 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 ConnectionTreeModel Load()
{
IDatabaseConnector connector = DatabaseConnectorFactory.DatabaseConnectorFromSettings();
SqlDataProvider dataProvider = new(connector);
SqlDatabaseMetaDataRetriever metaDataRetriever = new();
SqlDatabaseVersionVerifier databaseVersionVerifier = new(connector);
LegacyRijndaelCryptographyProvider cryptoProvider = new();
SqlConnectionListMetaData metaData = metaDataRetriever.GetDatabaseMetaData(connector) ?? HandleFirstRun(metaDataRetriever, connector);
Optional<SecureString> decryptionKey = GetDecryptionKey(metaData);
if (!decryptionKey.Any())
throw new Exception("Could not load SQL connections");
databaseVersionVerifier.VerifyDatabaseVersion(metaData.ConfVersion);
System.Data.DataTable dataTable = dataProvider.Load();
DataTableDeserializer deserializer = new(cryptoProvider, decryptionKey.First());
ConnectionTreeModel connectionTree = deserializer.Deserialize(dataTable);
ApplyLocalConnectionProperties(connectionTree.RootNodes.First(i => i is RootNodeInfo));
return connectionTree;
}
private Optional<SecureString> GetDecryptionKey(SqlConnectionListMetaData metaData)
{
LegacyRijndaelCryptographyProvider cryptographyProvider = new();
string cipherText = metaData.Protected;
PasswordAuthenticator authenticator = new(cryptographyProvider, cipherText, AuthenticationRequestor);
bool authenticated = authenticator.Authenticate(new RootNodeInfo(RootNodeType.Connection).DefaultPassword.ConvertToSecureString());
return authenticated ? authenticator.LastAuthenticatedPassword : Optional<SecureString>.Empty;
}
private void ApplyLocalConnectionProperties(ContainerInfo rootNode)
{
string localPropertiesXml = _dataProvider.Load();
IEnumerable<LocalConnectionPropertiesModel> 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,177 @@
using System;
using System.Collections.Generic;
using System.Data;
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.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;
using mRemoteNG.Resources.Language;
using System.Runtime.Versioning;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Sql;
namespace mRemoteNG.Config.Connections
{
[SupportedOSPlatform("windows")]
public class SqlConnectionsSaver : ISaver<ConnectionTreeModel>
{
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)
{
_saveFilter = saveFilter ?? throw new ArgumentNullException(nameof(saveFilter));
_localPropertiesSerializer = localPropertieSerializer.ThrowIfNull(nameof(localPropertieSerializer));
_dataProvider = localPropertiesDataProvider.ThrowIfNull(nameof(localPropertiesDataProvider));
}
public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
{
RootNodeInfo 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 (IDatabaseConnector dbConnector = DatabaseConnectorFactory.DatabaseConnectorFromSettings())
{
dbConnector.Connect();
SqlDatabaseVersionVerifier databaseVersionVerifier = new(dbConnector);
SqlDatabaseMetaDataRetriever metaDataRetriever = new();
SqlConnectionListMetaData 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)
{
IEnumerable<LocalConnectionPropertiesModel> 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,
});
string serializedProperties = _localPropertiesSerializer.Serialize(a);
_dataProvider.Save(serializedProperties);
Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Saved local connection properties");
}
private void UpdateRootNodeTable(RootNodeInfo rootTreeNode, IDatabaseConnector databaseConnector)
{
// TODO: use transaction, but method not used at all?
LegacyRijndaelCryptographyProvider cryptographyProvider = new();
string strProtected;
if (rootTreeNode != null)
{
if (rootTreeNode.Password)
{
System.Security.SecureString password = rootTreeNode.PasswordString.ConvertToSecureString();
strProtected = cryptographyProvider.Encrypt("ThisIsProtected", password);
}
else
{
strProtected = cryptographyProvider.Encrypt("ThisIsNotProtected", Runtime.EncryptionKey);
}
}
else
{
strProtected = cryptographyProvider.Encrypt("ThisIsNotProtected", Runtime.EncryptionKey);
}
System.Data.Common.DbCommand dbQuery = databaseConnector.DbCommand("TRUNCATE TABLE 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 + "')");
dbQuery.ExecuteNonQuery();
}
else
{
Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, $"UpdateRootNodeTable: rootTreeNode was null. Could not insert!");
}
}
private void UpdateConnectionsTable(RootNodeInfo rootTreeNode, IDatabaseConnector databaseConnector)
{
SqlDataProvider dataProvider = new(databaseConnector);
DataTable currentDataTable = dataProvider.Load();
LegacyRijndaelCryptographyProvider cryptoProvider = new();
DataTableSerializer serializer = new(_saveFilter, cryptoProvider, rootTreeNode.PasswordString.ConvertToSecureString());
serializer.SetSourceDataTable(currentDataTable);
DataTable dataTable = serializer.Serialize(rootTreeNode);
dataProvider.Save(dataTable);
}
private void UpdateUpdatesTable(IDatabaseConnector databaseConnector)
{
// TODO: use transaction
System.Data.Common.DbCommand dbQuery = databaseConnector.DbCommand("TRUNCATE TABLE tblUpdate");
dbQuery.ExecuteNonQuery();
dbQuery = databaseConnector.DbCommand("INSERT INTO tblUpdate (LastUpdate) VALUES('" + MiscTools.DBDate(DateTime.Now.ToUniversalTime()) + "')");
dbQuery.ExecuteNonQuery();
}
private bool SqlUserIsReadOnly()
{
return Properties.OptionsDBsPage.Default.SQLReadOnly;
}
}
}

View File

@@ -0,0 +1,42 @@
using mRemoteNG.Config.DataProviders;
using mRemoteNG.Tools;
using mRemoteNG.Tree;
using System;
using System.IO;
using System.Security;
using mRemoteNG.Config.Serializers.ConnectionSerializers.Xml;
using System.Runtime.Versioning;
namespace mRemoteNG.Config.Connections
{
[SupportedOSPlatform("windows")]
public class XmlConnectionsLoader : IConnectionsLoader
{
private readonly string _connectionFilePath;
public XmlConnectionsLoader(string connectionFilePath)
{
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;
}
public ConnectionTreeModel Load()
{
FileDataProvider dataProvider = new(_connectionFilePath);
string xmlString = dataProvider.Load();
XmlConnectionsDeserializer deserializer = new(PromptForPassword);
return deserializer.Deserialize(xmlString);
}
private Optional<SecureString> PromptForPassword()
{
Optional<SecureString> password = MiscTools.PasswordDialog(Path.GetFileName(_connectionFilePath), false);
return password;
}
}
}

View File

@@ -0,0 +1,50 @@
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;
using mRemoteNG.Properties;
using System.Runtime.Versioning;
namespace mRemoteNG.Config.Connections
{
[SupportedOSPlatform("windows")]
public class XmlConnectionsSaver : ISaver<ConnectionTreeModel>
{
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");
_connectionFileName = connectionFileName;
_saveFilter = saveFilter ?? throw new ArgumentNullException(nameof(saveFilter));
}
public void Save(ConnectionTreeModel connectionTreeModel, string propertyNameTrigger = "")
{
try
{
ICryptographyProvider cryptographyProvider = new CryptoProviderFactoryFromSettings().Build();
XmlConnectionSerializerFactory serializerFactory = new();
Serializers.ISerializer<Connection.ConnectionInfo, string> xmlConnectionsSerializer = serializerFactory.Build(cryptographyProvider, connectionTreeModel, _saveFilter, Properties.OptionsSecurityPage.Default.EncryptCompleteConnectionsFile);
RootNodeInfo rootNode = connectionTreeModel.RootNodes.OfType<RootNodeInfo>().First();
string xml = xmlConnectionsSerializer.Serialize(rootNode);
FileDataProviderWithRollingBackup fileDataProvider = new(_connectionFileName);
fileDataProvider.Save(xml);
}
catch (Exception ex)
{
Runtime.MessageCollector?.AddExceptionStackTrace("SaveToXml failed", ex);
}
}
}
}

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