diff --git a/CHANGELOG.TXT b/CHANGELOG.TXT index fb47960f9..4fa1c5661 100644 --- a/CHANGELOG.TXT +++ b/CHANGELOG.TXT @@ -1,4 +1,19 @@ -1.76.4 Alpha 6 (2018-xx-xx): +1.76.5 (2018-xx-xx): + +Fixes: +------ +#1030: Exception thrown if importing from port scan and no tree node is selected +#1020: BackupFileKeepCount setting not limiting backup file count +#1004: Duplicating root or PuTTy node through hotkey causes unhandled exception +#1002: Disabling filtering without clearing keyword leaves filtered state +#1001: Connection tree context menu hotkeys stop working and disappear in some cases +#999: Some hotkeys stop working if File menu was called when PuTTy Saved Sessions was selected +#998: Can sometimes add connection under PuTTY Sessions node +#991: Error when deleting host in filtered view +#961: Connections file overwritten if correct decryption password not provided + + +1.76.4 Alpha 6 (2018-06-03): Features/Enhancements: ---------------------- @@ -6,6 +21,7 @@ Features/Enhancements: #942: Improved Russian translation of several items #924: Notification for "No Host Specified" when clicking folders in quick-connect menu #902: Menu bar can once again be moved. View -> "Lock toolbar positions" now also locks the menu position +Added option for creating an empty panel on startup Fixes: ------ diff --git a/Tools/7zip/7za.dll b/Tools/7zip/7za.dll index f63e760c5..e95542c39 100644 Binary files a/Tools/7zip/7za.dll and b/Tools/7zip/7za.dll differ diff --git a/Tools/7zip/7za.exe b/Tools/7zip/7za.exe index 3bc851d5f..d516eb5c1 100644 Binary files a/Tools/7zip/7za.exe and b/Tools/7zip/7za.exe differ diff --git a/Tools/7zip/7zxa.dll b/Tools/7zip/7zxa.dll index e5591acb0..1e3778ae3 100644 Binary files a/Tools/7zip/7zxa.dll and b/Tools/7zip/7zxa.dll differ diff --git a/Tools/7zip/License.txt b/Tools/7zip/License.txt index 7b8e9a04c..2a0f37730 100644 --- a/Tools/7zip/License.txt +++ b/Tools/7zip/License.txt @@ -3,7 +3,7 @@ License for use and distribution ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Copyright (C) 1999-2016 Igor Pavlov. + Copyright (C) 1999-2018 Igor Pavlov. 7-Zip Extra files are under the GNU LGPL license. diff --git a/Tools/7zip/history.txt b/Tools/7zip/history.txt index fe8fb9d8e..e9bac39da 100644 --- a/Tools/7zip/history.txt +++ b/Tools/7zip/history.txt @@ -1,6 +1,25 @@ 7-Zip Extra history ------------------- +This file contains only information about changes related to that package exclusively. +The full history of changes is listed in history.txt in main 7-Zip program. + + +18.05 2018-04-30 +------------------------- +- The speed for LZMA/LZMA2 compressing was increased + by 8% for fastest/fast compression levels and + by 3% for normal/maximum compression levels. + + +18.03 beta 2018-03-04 +------------------------- +- The speed for single-thread LZMA/LZMA2 decoding + was increased by 30% in x64 version and by 3% in x86 version. +- 7-Zip now can use multi-threading for 7z/LZMA2 decoding, + if there are multiple independent data chunks in LZMA2 stream. + + 9.35 beta 2014-12-07 ------------------------------ - SFX modules were moved to LZMA SDK package. diff --git a/Tools/7zip/readme.txt b/Tools/7zip/readme.txt index 8f9bd7b73..c3de12587 100644 --- a/Tools/7zip/readme.txt +++ b/Tools/7zip/readme.txt @@ -1,9 +1,9 @@ -7-Zip Extra 16.02 +7-Zip Extra 18.05 ----------------- 7-Zip Extra is package of extra modules of 7-Zip. -7-Zip Copyright (C) 1999-2016 Igor Pavlov. +7-Zip Copyright (C) 1999-2018 Igor Pavlov. 7-Zip is free software. Read License.txt for more information about license. diff --git a/Tools/zip_portable_files.ps1 b/Tools/zip_portable_files.ps1 index 97ddeebc0..66f0c68bf 100644 --- a/Tools/zip_portable_files.ps1 +++ b/Tools/zip_portable_files.ps1 @@ -13,13 +13,46 @@ param ( ) Write-Output "===== Beginning $($PSCmdlet.MyInvocation.MyCommand) =====" -$path_packageZipScript = Join-Path -Path $SolutionDir -ChildPath "Tools\build-relport.cmd" +if(-not [string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER)) { + Write-Output "Too early to run via Appveyor - artifacts don't get generated properly. Exiting" + Exit +} + +Write-Output "Solution Dir: '$($SolutionDir)'" +Write-Output "Target Dir: '$($TargetDir)'" +$ConfigurationName = $ConfigurationName.Trim() +Write-Output "Config Name (tirmmed): '$($ConfigurationName)'" + + +# Windows Sysinternals Sigcheck from http://technet.microsoft.com/en-us/sysinternals/bb897441 +$SIGCHECK="$($SolutionDir)Tools\exes\sigcheck.exe" +$SEVENZIP="$($SolutionDir)Tools\7zip\7za.exe" # Package Zip -if ($ConfigurationName -match "Release" -and $ConfigurationName -match "Portable") { +if ($ConfigurationName -eq "Release Portable") { Write-Output "Packaging Release Portable ZIP" - & $path_packageZipScript + + $version = & $SIGCHECK /accepteula -q -n "$($SolutionDir)mRemoteV1\bin\$($ConfigurationName)\mRemoteNG.exe" + + Write-Output "Version is $($version)" + + $PortableZip="$($SolutionDir)Release\mRemoteNG-Portable-$($version).zip" + + Remove-Item -Recurse "$($SolutionDir)mRemoteV1\bin\package" -ErrorAction SilentlyContinue | Out-Null + New-Item "$($SolutionDir)mRemoteV1\bin\package" -ItemType "directory" | Out-Null + + Copy-Item "$($SolutionDir)mRemoteV1\Resources\PuTTYNG.exe" -Destination "$($SolutionDir)mRemoteV1\bin\package" + + #Write-Output "$($SolutionDir)mRemoteV1\bin\$ConfigurationName" + #Write-Output "$($SolutionDir)mRemoteV1\bin\package" + Copy-Item "$($SolutionDir)mRemoteV1\bin\$ConfigurationName\*" -Destination "$($SolutionDir)mRemoteV1\bin\package" -Recurse -Force + Copy-Item "$($SolutionDir)*.txt" -Destination "$($SolutionDir)mRemoteV1\bin\package" + + Write-Output "Creating portable ZIP file $($PortableZip)" + Remove-Item -Force $PortableZip -ErrorAction SilentlyContinue + & $SEVENZIP a -bt -bd -bb1 -mx=9 -tzip -y -r $PortableZip "$($SolutionDir)mRemoteV1\bin\package\*.*" + #& $SEVENZIP a -bt -mx=9 -tzip -y $PortableZip "$($SolutionDir)*.TXT" } else { Write-Output "We will not zip anything - this isnt a portable release build." diff --git a/Tools/zip_portable_files_appv.ps1 b/Tools/zip_portable_files_appv.ps1 new file mode 100644 index 000000000..52a72ce33 --- /dev/null +++ b/Tools/zip_portable_files_appv.ps1 @@ -0,0 +1,39 @@ +if([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER)) { + Write-Output "NOT running via Appveyor - Exiting" + Exit +} + +$appvDir = $Env:APPVEYOR_BUILD_FOLDER + +Write-Output "Appveyor Build Dir: '$($appvDir)'" +$ConfigurationName = $Env:CONFIGURATION.Trim() +Write-Output "Config Name (tirmmed): '$($ConfigurationName)'" + + +$SIGCHECK="$($SolutionDir)Tools\exes\sigcheck.exe" +$SEVENZIP="$($SolutionDir)Tools\7zip\7za.exe" + +if ($ConfigurationName -eq "Release Portable") { + Write-Output "Packaging Release Portable ZIP" + + $version = & $SIGCHECK /accepteula -q -n "$($SolutionDir)mRemoteV1\bin\$($ConfigurationName)\mRemoteNG.exe" + + Write-Output "Version is $($version)" + + $PortableZip="$($SolutionDir)Release\mRemoteNG-Portable-$($version).zip" + + Remove-Item -Recurse "$($SolutionDir)mRemoteV1\bin\package" -ErrorAction SilentlyContinue | Out-Null + New-Item "$($SolutionDir)mRemoteV1\bin\package" -ItemType "directory" | Out-Null + + Copy-Item "$($SolutionDir)mRemoteV1\Resources\PuTTYNG.exe" -Destination "$($SolutionDir)mRemoteV1\bin\package" + + Copy-Item "$($SolutionDir)mRemoteV1\bin\$ConfigurationName\*" -Destination "$($SolutionDir)mRemoteV1\bin\package" -Recurse -Force + Copy-Item "$($SolutionDir)*.txt" -Destination "$($SolutionDir)mRemoteV1\bin\package" + + Write-Output "Creating portable ZIP file $($PortableZip)" + Remove-Item -Force $PortableZip -ErrorAction SilentlyContinue + & $SEVENZIP a -bt -bd -bb1 -mx=9 -tzip -y -r $PortableZip "$($SolutionDir)mRemoteV1\bin\package\*.*" +} +else { + Write-Output "We will not zip anything - this isnt a portable release build." +} \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index ed3751f39..1aa639bce 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,10 +1,11 @@ -version: 1.0.{build} +version: 1.76.{build} pull_requests: do_not_increment_build_number: true image: Visual Studio 2017 configuration: - Release - Release Portable +- Release Installer platform: x86 clone_depth: 1 install: @@ -14,7 +15,14 @@ before_build: build: project: mRemoteV1.sln verbosity: normal +after_build: +- ps: "if([string]::IsNullOrEmpty($Env:APPVEYOR_BUILD_FOLDER)) {\n Write-Output \"NOT running via Appveyor - Exiting\"\n Exit\n}\n\n$appvDir = $Env:APPVEYOR_BUILD_FOLDER\n\nWrite-Output \"Appveyor Build Dir: '$($appvDir)'\"\n$ConfigurationName = $Env:CONFIGURATION.Trim()\nWrite-Output \"Config Name (tirmmed): '$($ConfigurationName)'\"\n\n\n$SIGCHECK=\"$($SolutionDir)Tools\\exes\\sigcheck.exe\"\n$SEVENZIP=\"$($SolutionDir)Tools\\7zip\\7za.exe\"\n\nif ($ConfigurationName -eq \"Release Portable\") {\n Write-Output \"Packaging Release Portable ZIP\"\n \n $version = & $SIGCHECK /accepteula -q -n \"$($SolutionDir)mRemoteV1\\bin\\$($ConfigurationName)\\mRemoteNG.exe\"\n\n Write-Output \"Version is $($version)\"\n\n $PortableZip=\"$($SolutionDir)Release\\mRemoteNG-Portable-$($version).zip\"\n\n Remove-Item -Recurse \"$($SolutionDir)mRemoteV1\\bin\\package\" -ErrorAction SilentlyContinue | Out-Null\n New-Item \"$($SolutionDir)mRemoteV1\\bin\\package\" -ItemType \"directory\" | Out-Null\n \n Copy-Item \"$($SolutionDir)mRemoteV1\\Resources\\PuTTYNG.exe\" -Destination \"$($SolutionDir)mRemoteV1\\bin\\package\"\n\n Copy-Item \"$($SolutionDir)mRemoteV1\\bin\\$ConfigurationName\\*\" -Destination \"$($SolutionDir)mRemoteV1\\bin\\package\" -Recurse -Force \n Copy-Item \"$($SolutionDir)*.txt\" -Destination \"$($SolutionDir)mRemoteV1\\bin\\package\"\n\n Write-Output \"Creating portable ZIP file $($PortableZip)\"\n Remove-Item -Force $PortableZip -ErrorAction SilentlyContinue\n & $SEVENZIP a -bt -bd -bb1 -mx=9 -tzip -y -r $PortableZip \"$($SolutionDir)mRemoteV1\\bin\\package\\*.*\"\n}\nelse {\n Write-Output \"We will not zip anything - this isnt a portable release build.\"\n}" test: assemblies: only: - - mRemoteNGTests\bin\$(configuration)\mRemoteNGTests.dll \ No newline at end of file + - mRemoteNGTests\bin\$(configuration)\mRemoteNGTests.dll +artifacts: +- path: Release\*.msi + name: mRemoteNG-installer.msi +- path: Release\*.zip + name: mRemoteNG-portable.zip \ No newline at end of file diff --git a/mRemoteNGTests/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializerTests.cs b/mRemoteNGTests/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializerTests.cs index 4bdcba463..a82212aea 100644 --- a/mRemoteNGTests/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializerTests.cs +++ b/mRemoteNGTests/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializerTests.cs @@ -5,7 +5,6 @@ using System.Windows.Forms; using System.Xml.Linq; using mRemoteNG.App; using mRemoteNG.Config.Putty; -using mRemoteNG.Config.Serializers; using mRemoteNG.Config.Serializers.Xml; using mRemoteNG.Connection; using mRemoteNG.Container; @@ -27,7 +26,7 @@ namespace mRemoteNGTests.Config.Serializers.ConnectionSerializers.Xml var connectionsService = new ConnectionsService(PuttySessionsManager.Instance, new Import(Substitute.For()), Substitute.For()); - _xmlConnectionsDeserializer = new XmlConnectionsDeserializer(connectionsService, Substitute.For(), password.ConvertToSecureString); + _xmlConnectionsDeserializer = new XmlConnectionsDeserializer(connectionsService, Substitute.For(), () => password.ConvertToSecureString); _connectionTreeModel = _xmlConnectionsDeserializer.Deserialize(confCons); } diff --git a/mRemoteNGTests/Security/Authentication/PasswordAuthenticatorTests.cs b/mRemoteNGTests/Security/Authentication/PasswordAuthenticatorTests.cs index d12bcee36..2dc92a77c 100644 --- a/mRemoteNGTests/Security/Authentication/PasswordAuthenticatorTests.cs +++ b/mRemoteNGTests/Security/Authentication/PasswordAuthenticatorTests.cs @@ -2,6 +2,7 @@ using mRemoteNG.Security; using mRemoteNG.Security.Authentication; using mRemoteNG.Security.SymmetricEncryption; +using mRemoteNG.Tools; using NUnit.Framework; @@ -9,35 +10,31 @@ namespace mRemoteNGTests.Security.Authentication { public class PasswordAuthenticatorTests { - private PasswordAuthenticator _authenticator; + private ICryptographyProvider _cryptographyProvider; + private string _cipherText; private readonly SecureString _correctPassword = "9theCorrectPass#5".ConvertToSecureString(); private readonly SecureString _wrongPassword = "wrongPassword".ConvertToSecureString(); [SetUp] public void Setup() { - var cryptoProvider = new AeadCryptographyProvider {KeyDerivationIterations = 10000}; - const string cipherText = "MPELiwk7+xeNlruIyt5uxTvVB+/RLVoLdUGnwY4CWCqwKe7T2IBwWo4oaKum5hdv7447g5m2nZsYPrfARSlotQB4r1KZQg=="; - _authenticator = new PasswordAuthenticator(cryptoProvider, cipherText); - } - - [TearDown] - public void Teardown() - { - _authenticator = null; + _cryptographyProvider = new AeadCryptographyProvider {KeyDerivationIterations = 10000}; + _cipherText = "MPELiwk7+xeNlruIyt5uxTvVB+/RLVoLdUGnwY4CWCqwKe7T2IBwWo4oaKum5hdv7447g5m2nZsYPrfARSlotQB4r1KZQg=="; } [Test] public void AuthenticatingWithCorrectPasswordReturnsTrue() { - var authenticated = _authenticator.Authenticate(_correctPassword); + var authenticator = new PasswordAuthenticator(_cryptographyProvider, _cipherText, () => Optional.Empty); + var authenticated = authenticator.Authenticate(_correctPassword); Assert.That(authenticated); } [Test] public void AuthenticatingWithWrongPasswordReturnsFalse() { - var authenticated = _authenticator.Authenticate(_wrongPassword); + var authenticator = new PasswordAuthenticator(_cryptographyProvider, _cipherText, () => Optional.Empty); + var authenticated = authenticator.Authenticate(_wrongPassword); Assert.That(!authenticated); } @@ -45,12 +42,15 @@ namespace mRemoteNGTests.Security.Authentication public void AuthenticationRequestorIsCalledWhenInitialPasswordIsWrong() { var wasCalled = false; - _authenticator.AuthenticationRequestor = () => + + Optional AuthenticationRequestor() { wasCalled = true; return _correctPassword; - }; - _authenticator.Authenticate(_wrongPassword); + } + + var authenticator = new PasswordAuthenticator(_cryptographyProvider, _cipherText, AuthenticationRequestor); + authenticator.Authenticate(_wrongPassword); Assert.That(wasCalled); } @@ -58,28 +58,30 @@ namespace mRemoteNGTests.Security.Authentication public void AuthenticationRequestorNotCalledWhenInitialPasswordIsCorrect() { var wasCalled = false; - _authenticator.AuthenticationRequestor = () => + Optional AuthenticationRequestor() { wasCalled = true; return _correctPassword; - }; - _authenticator.Authenticate(_correctPassword); + } + + var authenticator = new PasswordAuthenticator(_cryptographyProvider, _cipherText, AuthenticationRequestor); + authenticator.Authenticate(_correctPassword); Assert.That(!wasCalled); } [Test] public void ProvidingCorrectPasswordToTheAuthenticationRequestorReturnsTrue() { - _authenticator.AuthenticationRequestor = () => _correctPassword; - var authenticated = _authenticator.Authenticate(_wrongPassword); + var authenticator = new PasswordAuthenticator(_cryptographyProvider, _cipherText, () => _correctPassword); + var authenticated = authenticator.Authenticate(_wrongPassword); Assert.That(authenticated); } [Test] public void AuthenticationFailsWhenAuthenticationRequestorGivenEmptyPassword() { - _authenticator.AuthenticationRequestor = () => new SecureString(); - var authenticated = _authenticator.Authenticate(_wrongPassword); + var authenticator = new PasswordAuthenticator(_cryptographyProvider, _cipherText, () => new SecureString()); + var authenticated = authenticator.Authenticate(_wrongPassword); Assert.That(!authenticated); } @@ -87,27 +89,34 @@ namespace mRemoteNGTests.Security.Authentication public void AuthenticatorRespectsMaxAttempts() { var authAttempts = 0; - _authenticator.AuthenticationRequestor = () => + Optional AuthenticationRequestor() { authAttempts++; return _wrongPassword; - }; - _authenticator.Authenticate(_wrongPassword); - Assert.That(authAttempts == _authenticator.MaxAttempts); + } + + var authenticator = new PasswordAuthenticator(_cryptographyProvider, _cipherText, AuthenticationRequestor); + authenticator.Authenticate(_wrongPassword); + Assert.That(authAttempts == authenticator.MaxAttempts); } [Test] public void AuthenticatorRespectsMaxAttemptsCustomValue() { const int customMaxAttempts = 5; - _authenticator.MaxAttempts = customMaxAttempts; var authAttempts = 0; - _authenticator.AuthenticationRequestor = () => + Optional AuthenticationRequestor() { authAttempts++; return _wrongPassword; - }; - _authenticator.Authenticate(_wrongPassword); + } + + var authenticator = + new PasswordAuthenticator(_cryptographyProvider, _cipherText, AuthenticationRequestor) + { + MaxAttempts = customMaxAttempts + }; + authenticator.Authenticate(_wrongPassword); Assert.That(authAttempts == customMaxAttempts); } } diff --git a/mRemoteNGTests/Tree/RootNodeInfoTests.cs b/mRemoteNGTests/Tree/RootNodeInfoTests.cs index 355a73338..4baf33ee2 100644 --- a/mRemoteNGTests/Tree/RootNodeInfoTests.cs +++ b/mRemoteNGTests/Tree/RootNodeInfoTests.cs @@ -1,4 +1,5 @@ -using mRemoteNG.Tree.Root; +using mRemoteNG.Tree; +using mRemoteNG.Tree.Root; using NUnit.Framework; @@ -46,5 +47,13 @@ namespace mRemoteNGTests.Tree _rootNodeInfo.PasswordString = password; Assert.That(_rootNodeInfo.PasswordString, Is.EqualTo(password)); } + + [TestCase(RootNodeType.Connection, TreeNodeType.Root)] + [TestCase(RootNodeType.PuttySessions, TreeNodeType.PuttyRoot)] + public void RootNodeHasCorrectTreeNodeType(RootNodeType rootNodeType, TreeNodeType expectedTreeNodeType) + { + var rootNode = new RootNodeInfo(rootNodeType); + Assert.That(rootNode.GetTreeNodeType(), Is.EqualTo(expectedTreeNodeType)); + } } } \ No newline at end of file diff --git a/mRemoteNGTests/UI/Controls/ConnectionTreeTests.cs b/mRemoteNGTests/UI/Controls/ConnectionTreeTests.cs new file mode 100644 index 000000000..b6227d985 --- /dev/null +++ b/mRemoteNGTests/UI/Controls/ConnectionTreeTests.cs @@ -0,0 +1,188 @@ +using System.Linq; +using System.Threading; +using mRemoteNG.Connection; +using mRemoteNG.Container; +using mRemoteNG.Tree; +using mRemoteNG.Tree.Root; +using mRemoteNG.UI.Controls; +using NUnit.Framework; + +namespace mRemoteNGTests.UI.Controls +{ + public class ConnectionTreeTests + { + private ConnectionTreeSearchTextFilter _filter; + private ConnectionTree _connectionTree; + + [SetUp] + public void Setup() + { + _filter = new ConnectionTreeSearchTextFilter(); + _connectionTree = new ConnectionTree + { + UseFiltering = true + }; + } + + [Test] + [Apartment(ApartmentState.STA)] + public void FilteringIsRetainedAndUpdatedWhenNodeDeleted() + { + // root + // |- folder1 + // | |- con1 + // | |- dontshowme + // |- folder2 + // |- con2 + var connectionTreeModel = new ConnectionTreeModel(); + var root = new RootNodeInfo(RootNodeType.Connection); + var folder1 = new ContainerInfo {Name = "folder1"}; + var folder2 = new ContainerInfo {Name = "folder2"}; + var con1 = new ConnectionInfo {Name = "con1"}; + var con2 = new ConnectionInfo {Name = "con2"}; + var conDontShow = new ConnectionInfo {Name = "dontshowme" }; + root.AddChildRange(new []{folder1, folder2}); + folder1.AddChildRange(new []{con1, conDontShow}); + folder2.AddChild(con2); + connectionTreeModel.AddRootNode(root); + + _connectionTree.ConnectionTreeModel = connectionTreeModel; + // ensure all folders expanded + _connectionTree.ExpandAll(); + + // apply filtering on the tree + _filter.FilterText = "con"; + _connectionTree.ModelFilter = _filter; + + connectionTreeModel.DeleteNode(con1); + + Assert.That(_connectionTree.IsFiltering, Is.True); + Assert.That(_connectionTree.FilteredObjects, Does.Not.Contain(con1)); + Assert.That(_connectionTree.FilteredObjects, Does.Not.Contain(conDontShow)); + Assert.That(_connectionTree.FilteredObjects, Does.Contain(con2)); + } + + [Test] + [Apartment(ApartmentState.STA)] + public void CannotAddConnectionToPuttySessionNode() + { + var connectionTreeModel = new ConnectionTreeModel(); + var root = new RootNodeInfo(RootNodeType.Connection); + var puttyRoot = new RootNodeInfo(RootNodeType.PuttySessions); + connectionTreeModel.AddRootNode(root); + connectionTreeModel.AddRootNode(puttyRoot); + + _connectionTree.ConnectionTreeModel = connectionTreeModel; + + _connectionTree.SelectedObject = puttyRoot; + _connectionTree.AddConnection(); + + Assert.That(puttyRoot.Children, Is.Empty); + } + + [Test] + [Apartment(ApartmentState.STA)] + public void CannotAddFolderToPuttySessionNode() + { + var connectionTreeModel = new ConnectionTreeModel(); + var root = new RootNodeInfo(RootNodeType.Connection); + var puttyRoot = new RootNodeInfo(RootNodeType.PuttySessions); + connectionTreeModel.AddRootNode(root); + connectionTreeModel.AddRootNode(puttyRoot); + + _connectionTree.ConnectionTreeModel = connectionTreeModel; + + _connectionTree.SelectedObject = puttyRoot; + _connectionTree.AddFolder(); + + Assert.That(puttyRoot.Children, Is.Empty); + } + + [Test] + [Apartment(ApartmentState.STA)] + public void CannotDuplicateRootConnectionNode() + { + var connectionTreeModel = new ConnectionTreeModel(); + var root = new RootNodeInfo(RootNodeType.Connection); + connectionTreeModel.AddRootNode(root); + _connectionTree.ConnectionTreeModel = connectionTreeModel; + + _connectionTree.SelectedObject = root; + _connectionTree.DuplicateSelectedNode(); + + Assert.That(connectionTreeModel.RootNodes, Has.One.Items); + } + + [Test] + [Apartment(ApartmentState.STA)] + public void CannotDuplicateRootPuttyNode() + { + var connectionTreeModel = new ConnectionTreeModel(); + var puttyRoot = new RootNodeInfo(RootNodeType.PuttySessions); + connectionTreeModel.AddRootNode(puttyRoot); + _connectionTree.ConnectionTreeModel = connectionTreeModel; + + _connectionTree.SelectedObject = puttyRoot; + _connectionTree.DuplicateSelectedNode(); + + Assert.That(connectionTreeModel.RootNodes, Has.One.Items); + } + + [Test] + [Apartment(ApartmentState.STA)] + public void CannotDuplicatePuttyConnectionNode() + { + var connectionTreeModel = new ConnectionTreeModel(); + var puttyRoot = new RootNodeInfo(RootNodeType.PuttySessions); + var puttyConnection = new PuttySessionInfo(); + puttyRoot.AddChild(puttyConnection); + connectionTreeModel.AddRootNode(puttyRoot); + _connectionTree.ConnectionTreeModel = connectionTreeModel; + _connectionTree.ExpandAll(); + + _connectionTree.SelectedObject = puttyConnection; + _connectionTree.DuplicateSelectedNode(); + + Assert.That(puttyRoot.Children, Has.One.Items); + } + + [Test] + [Apartment(ApartmentState.STA)] + public void DuplicatingWithNoNodeSelectedDoesNothing() + { + var connectionTreeModel = new ConnectionTreeModel(); + var puttyRoot = new RootNodeInfo(RootNodeType.PuttySessions); + connectionTreeModel.AddRootNode(puttyRoot); + _connectionTree.ConnectionTreeModel = connectionTreeModel; + + _connectionTree.SelectedObject = null; + _connectionTree.DuplicateSelectedNode(); + + Assert.That(connectionTreeModel.RootNodes, Has.One.Items); + } + + [Test] + [Apartment(ApartmentState.STA)] + public void ExpandingAllItemsUpdatesColumnWidthAppropriately() + { + var connectionTreeModel = new ConnectionTreeModel(); + var root = new RootNodeInfo(RootNodeType.Connection); + connectionTreeModel.AddRootNode(root); + ContainerInfo parent = root; + foreach (var i in Enumerable.Repeat("", 8)) + { + var newContainer = new ContainerInfo {IsExpanded = false}; + parent.AddChild(newContainer); + parent = newContainer; + } + + _connectionTree.ConnectionTreeModel = connectionTreeModel; + + var widthBefore = _connectionTree.Columns[0].Width; + _connectionTree.ExpandAll(); + var widthAfter = _connectionTree.Columns[0].Width; + + Assert.That(widthAfter, Is.GreaterThan(widthBefore)); + } + } +} diff --git a/mRemoteNGTests/mRemoteNGTests.csproj b/mRemoteNGTests/mRemoteNGTests.csproj index 2f18a05a0..1764d49b3 100644 --- a/mRemoteNGTests/mRemoteNGTests.csproj +++ b/mRemoteNGTests/mRemoteNGTests.csproj @@ -209,6 +209,7 @@ + Form diff --git a/mRemoteV1.sln b/mRemoteV1.sln index f7b3e9004..5466c8d33 100644 --- a/mRemoteV1.sln +++ b/mRemoteV1.sln @@ -33,16 +33,19 @@ Global EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug Portable|Any CPU.ActiveCfg = Debug Portable|x86 + {4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug Portable|Any CPU.Build.0 = Debug Portable|x86 {4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug Portable|x86.ActiveCfg = Debug Portable|x86 {4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug Portable|x86.Build.0 = Debug Portable|x86 {4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|Any CPU.ActiveCfg = Debug|x86 + {4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|Any CPU.Build.0 = Debug|x86 {4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|x86.ActiveCfg = Debug|x86 {4934A491-40BC-4E5B-9166-EA1169A220F6}.Debug|x86.Build.0 = Debug|x86 - {4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer|Any CPU.ActiveCfg = Release Portable|x86 - {4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer|Any CPU.Build.0 = Release Portable|x86 + {4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer|Any CPU.ActiveCfg = Release|x86 + {4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer|Any CPU.Build.0 = Release|x86 {4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer|x86.ActiveCfg = Release|x86 {4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Installer|x86.Build.0 = Release|x86 {4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Portable|Any CPU.ActiveCfg = Release Portable|x86 + {4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Portable|Any CPU.Build.0 = Release Portable|x86 {4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Portable|x86.ActiveCfg = Release Portable|x86 {4934A491-40BC-4E5B-9166-EA1169A220F6}.Release Portable|x86.Build.0 = Release Portable|x86 {4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|Any CPU.ActiveCfg = Release|x86 @@ -50,24 +53,26 @@ Global {4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|x86.ActiveCfg = Release|x86 {4934A491-40BC-4E5B-9166-EA1169A220F6}.Release|x86.Build.0 = Release|x86 {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug Portable|Any CPU.ActiveCfg = Debug Portable|x86 + {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug Portable|Any CPU.Build.0 = Debug Portable|x86 {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug Portable|x86.ActiveCfg = Debug Portable|x86 {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug Portable|x86.Build.0 = Debug Portable|x86 {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug|Any CPU.ActiveCfg = Debug|x86 + {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug|Any CPU.Build.0 = Debug|x86 {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug|x86.ActiveCfg = Debug|x86 {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Debug|x86.Build.0 = Debug|x86 - {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Installer|Any CPU.ActiveCfg = Release Portable|x86 - {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Installer|Any CPU.Build.0 = Release Portable|x86 + {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Installer|Any CPU.ActiveCfg = Release|x86 + {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Installer|Any CPU.Build.0 = Release|x86 {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Installer|x86.ActiveCfg = Release|x86 {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Installer|x86.Build.0 = Release|x86 {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Portable|Any CPU.ActiveCfg = Release Portable|x86 + {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Portable|Any CPU.Build.0 = Release Portable|x86 {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Portable|x86.ActiveCfg = Release Portable|x86 {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release Portable|x86.Build.0 = Release Portable|x86 {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release|Any CPU.ActiveCfg = Release|x86 {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release|Any CPU.Build.0 = Release|x86 {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release|x86.ActiveCfg = Release|x86 {1453B37F-8621-499E-B0B2-6091F76DC0BB}.Release|x86.Build.0 = Release|x86 - {5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug Portable|Any CPU.ActiveCfg = Release|x86 - {5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug Portable|Any CPU.Build.0 = Release|x86 + {5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug Portable|Any CPU.ActiveCfg = Debug|x86 {5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug Portable|x86.ActiveCfg = Debug|x86 {5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug|Any CPU.ActiveCfg = Debug|x86 {5423D985-CB48-4344-B47F-E8C6D60C8B04}.Debug|x86.ActiveCfg = Debug|x86 @@ -77,7 +82,6 @@ Global {5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release Installer|x86.ActiveCfg = Release|x86 {5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release Installer|x86.Build.0 = Release|x86 {5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release Portable|Any CPU.ActiveCfg = Release|x86 - {5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release Portable|Any CPU.Build.0 = Release|x86 {5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release Portable|x86.ActiveCfg = Release|x86 {5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release|Any CPU.ActiveCfg = Release|x86 {5423D985-CB48-4344-B47F-E8C6D60C8B04}.Release|Any CPU.Build.0 = Release|x86 @@ -86,8 +90,8 @@ Global {F0168B9F-6815-40DF-BA53-46CEE7683B68}.Debug Portable|x86.ActiveCfg = Debug Portable|x86 {F0168B9F-6815-40DF-BA53-46CEE7683B68}.Debug|Any CPU.ActiveCfg = Debug|x86 {F0168B9F-6815-40DF-BA53-46CEE7683B68}.Debug|x86.ActiveCfg = Debug|x86 - {F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Installer|Any CPU.ActiveCfg = Release Portable|x86 - {F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Installer|Any CPU.Build.0 = Release Portable|x86 + {F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Installer|Any CPU.ActiveCfg = Release|x86 + {F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Installer|Any CPU.Build.0 = Release|x86 {F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Installer|x86.ActiveCfg = Release|x86 {F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Installer|x86.Build.0 = Release|x86 {F0168B9F-6815-40DF-BA53-46CEE7683B68}.Release Portable|Any CPU.ActiveCfg = Release Portable|x86 diff --git a/mRemoteV1/Config/Connections/SaveConnectionsOnEdit.cs b/mRemoteV1/Config/Connections/SaveConnectionsOnEdit.cs index 5902f0cf1..015b6ca6c 100644 --- a/mRemoteV1/Config/Connections/SaveConnectionsOnEdit.cs +++ b/mRemoteV1/Config/Connections/SaveConnectionsOnEdit.cs @@ -20,10 +20,9 @@ namespace mRemoteNG.Config.Connections private void ConnectionsServiceOnConnectionsLoaded(object sender, ConnectionsLoadedEventArgs connectionsLoadedEventArgs) { - - connectionsLoadedEventArgs.NewConnectionTreeModel.CollectionChanged += ConnectionTreeModelOnCollectionChanged; connectionsLoadedEventArgs.NewConnectionTreeModel.PropertyChanged += ConnectionTreeModelOnPropertyChanged; + foreach (var oldTree in connectionsLoadedEventArgs.PreviousConnectionTreeModel) { oldTree.CollectionChanged -= ConnectionTreeModelOnCollectionChanged; @@ -45,7 +44,8 @@ namespace mRemoteNG.Config.Connections { if (!mRemoteNG.Settings.Default.SaveConnectionsAfterEveryEdit) return; - _connectionsService.SaveConnections(); + + _connectionsService.SaveConnectionsAsync(); } } } diff --git a/mRemoteV1/Config/Connections/XmlConnectionsLoader.cs b/mRemoteV1/Config/Connections/XmlConnectionsLoader.cs index bec221124..424b6722c 100644 --- a/mRemoteV1/Config/Connections/XmlConnectionsLoader.cs +++ b/mRemoteV1/Config/Connections/XmlConnectionsLoader.cs @@ -1,14 +1,14 @@ using System.Security; using System.Windows.Forms; using mRemoteNG.Config.DataProviders; -using mRemoteNG.Tools; -using mRemoteNG.Tree; using mRemoteNG.Config.Serializers.Xml; using mRemoteNG.Connection; +using mRemoteNG.Tools; +using mRemoteNG.Tree; namespace mRemoteNG.Config.Connections { - public class XmlConnectionsLoader + public class XmlConnectionsLoader { private readonly IConnectionsService _connectionsService; private readonly string _connectionFilePath; @@ -29,7 +29,7 @@ namespace mRemoteNG.Config.Connections return deserializer.Deserialize(xmlString); } - private SecureString PromptForPassword() + private Optional PromptForPassword() { return MiscTools.PasswordDialog("", false); } diff --git a/mRemoteV1/Config/DataProviders/FileBackupPruner.cs b/mRemoteV1/Config/DataProviders/FileBackupPruner.cs index d371647d0..5572a3456 100644 --- a/mRemoteV1/Config/DataProviders/FileBackupPruner.cs +++ b/mRemoteV1/Config/DataProviders/FileBackupPruner.cs @@ -1,26 +1,29 @@ -using System; -using System.IO; +using System.IO; +using System.Linq; namespace mRemoteNG.Config.DataProviders { public class FileBackupPruner { - public void PruneBackupFiles(string baseName) + public void PruneBackupFiles(string filePath, int maxBackupsToKeep) { - var fileName = Path.GetFileName(baseName); - var directoryName = Path.GetDirectoryName(baseName); + var fileName = Path.GetFileName(filePath); + var directoryName = Path.GetDirectoryName(filePath); - if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(directoryName)) return; + if (string.IsNullOrEmpty(fileName) || string.IsNullOrEmpty(directoryName)) + return; var searchPattern = string.Format(mRemoteNG.Settings.Default.BackupFileNameFormat, fileName, "*"); var files = Directory.GetFiles(directoryName, searchPattern); - if (files.Length <= mRemoteNG.Settings.Default.BackupFileKeepCount) return; + if (files.Length <= maxBackupsToKeep) + return; - Array.Sort(files); - Array.Resize(ref files, files.Length - mRemoteNG.Settings.Default.BackupFileKeepCount); + var filesToDelete = files + .OrderByDescending(s => s) + .Skip(maxBackupsToKeep); - foreach (var file in files) + foreach (var file in filesToDelete) { File.Delete(file); } diff --git a/mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs b/mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs index 9cc09a2d3..964efa4a6 100644 --- a/mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs +++ b/mRemoteV1/Config/Serializers/ConnectionSerializers/Xml/XmlConnectionsDeserializer.cs @@ -21,7 +21,7 @@ using mRemoteNG.UI.TaskDialog; namespace mRemoteNG.Config.Serializers.Xml { - public class XmlConnectionsDeserializer : IDeserializer + public class XmlConnectionsDeserializer : IDeserializer { private readonly IConnectionsService _connectionsService; private XmlDocument _xmlDocument; @@ -32,9 +32,9 @@ namespace mRemoteNG.Config.Serializers.Xml private readonly RootNodeInfo _rootNodeInfo = new RootNodeInfo(RootNodeType.Connection); private readonly IWin32Window _dialogWindowParent; - public Func AuthenticationRequestor { get; set; } + public Func> AuthenticationRequestor { get; set; } - public XmlConnectionsDeserializer(IConnectionsService connectionsService, IWin32Window dialogWindowParent, Func authenticationRequestor = null) + public XmlConnectionsDeserializer(IConnectionsService connectionsService, IWin32Window dialogWindowParent, Func> authenticationRequestor = null) { _connectionsService = connectionsService.ThrowIfNull(nameof(connectionsService)); _dialogWindowParent = dialogWindowParent.ThrowIfNull(nameof(dialogWindowParent)); @@ -52,8 +52,6 @@ namespace mRemoteNG.Config.Serializers.Xml { LoadXmlConnectionData(xml); ValidateConnectionFileVersion(); - if (!import) - _connectionsService.IsConnectionsFileLoaded = false; var rootXmlElement = _xmlDocument.DocumentElement; InitializeRootNode(rootXmlElement); @@ -67,8 +65,6 @@ namespace mRemoteNG.Config.Serializers.Xml var protectedString = _xmlDocument.DocumentElement?.Attributes["Protected"].Value; if (!_decryptor.ConnectionsFileIsAuthentic(protectedString, _rootNodeInfo.PasswordString.ConvertToSecureString())) { - mRemoteNG.Settings.Default.LoadConsFromCustomLocation = false; - mRemoteNG.Settings.Default.CustomConsPath = ""; return null; } } diff --git a/mRemoteV1/Config/Serializers/MiscSerializers/RemoteDesktopConnectionManagerDeserializer.cs b/mRemoteV1/Config/Serializers/MiscSerializers/RemoteDesktopConnectionManagerDeserializer.cs index 5df6a8ca6..d8b77abd6 100644 --- a/mRemoteV1/Config/Serializers/MiscSerializers/RemoteDesktopConnectionManagerDeserializer.cs +++ b/mRemoteV1/Config/Serializers/MiscSerializers/RemoteDesktopConnectionManagerDeserializer.cs @@ -1,5 +1,7 @@ using System; using System.IO; +using System.Security.Cryptography; +using System.Text; using System.Xml; using mRemoteNG.Connection; using mRemoteNG.Connection.Protocol; @@ -7,8 +9,6 @@ using mRemoteNG.Connection.Protocol.RDP; using mRemoteNG.Container; using mRemoteNG.Tree; using mRemoteNG.Tree.Root; -using System.Security.Cryptography; -using System.Text; namespace mRemoteNG.Config.Serializers @@ -60,7 +60,7 @@ namespace mRemoteNG.Config.Serializers } else { - var versionNode = rdcManNode.SelectSingleNode("./version")?.InnerText; + var versionNode = rdcManNode?.SelectSingleNode("./version")?.InnerText; if (versionNode != null) { var version = new Version(versionNode); @@ -101,15 +101,16 @@ namespace mRemoteNG.Config.Serializers { if (_schemaVersion == 1) { - // Program Verison 2.2 wraps all setting inside the Properties tags + // Program Version 2.2 wraps all setting inside the Properties tags containerPropertiesNode = containerPropertiesNode.SelectSingleNode("./properties"); } var newContainer = new ContainerInfo(); var connectionInfo = ConnectionInfoFromXml(containerPropertiesNode); newContainer.CopyFrom(connectionInfo); + if (_schemaVersion == 3) { - // Program Verison 2.7 wraps these properties + // Program Version 2.7 wraps these properties containerPropertiesNode = containerPropertiesNode.SelectSingleNode("./properties"); } newContainer.Name = containerPropertiesNode?.SelectSingleNode("./name")?.InnerText ?? Language.strNewFolder; @@ -130,20 +131,22 @@ namespace mRemoteNG.Config.Serializers var propertiesNode = xmlNode.SelectSingleNode("./properties"); - if (_schemaVersion == 1) propertiesNode = xmlNode; // Version 2.2 defines the container name at the root instead - connectionInfo.Hostname = propertiesNode.SelectSingleNode("./name")?.InnerText; - connectionInfo.Name = propertiesNode.SelectSingleNode("./displayName")?.InnerText ?? connectionInfo.Hostname; - connectionInfo.Description = propertiesNode.SelectSingleNode("./comment")?.InnerText ?? String.Empty; + if (_schemaVersion == 1) propertiesNode = xmlNode; // Version 2.2 defines the container name at the root instead + connectionInfo.Hostname = propertiesNode?.SelectSingleNode("./name")?.InnerText ?? ""; + connectionInfo.Name = propertiesNode?.SelectSingleNode("./displayName")?.InnerText ?? connectionInfo.Hostname; + connectionInfo.Description = propertiesNode?.SelectSingleNode("./comment")?.InnerText ?? string.Empty; var logonCredentialsNode = xmlNode.SelectSingleNode("./logonCredentials"); - if (logonCredentialsNode?.Attributes?["inherit"].Value == "None") + if (logonCredentialsNode?.Attributes?["inherit"]?.Value == "None") { connectionInfo.Username = logonCredentialsNode.SelectSingleNode("userName")?.InnerText; var passwordNode = logonCredentialsNode.SelectSingleNode("./password"); if (_schemaVersion == 1) // Version 2.2 allows clear text passwords { - connectionInfo.Password = passwordNode?.Attributes?["storeAsClearText"].Value == "True" ? passwordNode.InnerText : DecryptRdcManPassword(passwordNode?.InnerText); + connectionInfo.Password = passwordNode?.Attributes?["storeAsClearText"]?.Value == "True" + ? passwordNode.InnerText + : DecryptRdcManPassword(passwordNode?.InnerText); } else { @@ -160,7 +163,7 @@ namespace mRemoteNG.Config.Serializers } var connectionSettingsNode = xmlNode.SelectSingleNode("./connectionSettings"); - if (connectionSettingsNode?.Attributes?["inherit"].Value == "None") + if (connectionSettingsNode?.Attributes?["inherit"]?.Value == "None") { connectionInfo.UseConsoleSession = bool.Parse(connectionSettingsNode.SelectSingleNode("./connectToConsole")?.InnerText ?? "false"); // ./startProgram @@ -174,7 +177,7 @@ namespace mRemoteNG.Config.Serializers } var gatewaySettingsNode = xmlNode.SelectSingleNode("./gatewaySettings"); - if (gatewaySettingsNode?.Attributes?["inherit"].Value == "None") + if (gatewaySettingsNode?.Attributes?["inherit"]?.Value == "None") { connectionInfo.RDGatewayUsageMethod = gatewaySettingsNode.SelectSingleNode("./enabled")?.InnerText == "True" ? RdpProtocol.RDGatewayUsageMethod.Always : RdpProtocol.RDGatewayUsageMethod.Never; connectionInfo.RDGatewayHostname = gatewaySettingsNode.SelectSingleNode("./hostName")?.InnerText; @@ -198,7 +201,7 @@ namespace mRemoteNG.Config.Serializers } var remoteDesktopNode = xmlNode.SelectSingleNode("./remoteDesktop"); - if (remoteDesktopNode?.Attributes?["inherit"].Value == "None") + if (remoteDesktopNode?.Attributes?["inherit"]?.Value == "None") { var resolutionString = remoteDesktopNode.SelectSingleNode("./size")?.InnerText.Replace(" ", ""); try @@ -231,7 +234,7 @@ namespace mRemoteNG.Config.Serializers } var localResourcesNode = xmlNode.SelectSingleNode("./localResources"); - if (localResourcesNode?.Attributes?["inherit"].Value == "None") + if (localResourcesNode?.Attributes?["inherit"]?.Value == "None") { // ReSharper disable once SwitchStatementMissingSomeCases switch (localResourcesNode.SelectSingleNode("./audioRedirection")?.InnerText) @@ -271,10 +274,10 @@ namespace mRemoteNG.Config.Serializers } // ./redirectClipboard - connectionInfo.RedirectDiskDrives = bool.Parse(localResourcesNode.SelectSingleNode("./redirectDrives")?.InnerText ?? "false"); - connectionInfo.RedirectPorts = bool.Parse(localResourcesNode.SelectSingleNode("./redirectPorts")?.InnerText ?? "false"); - connectionInfo.RedirectPrinters = bool.Parse(localResourcesNode.SelectSingleNode("./redirectPrinters")?.InnerText ?? "false"); - connectionInfo.RedirectSmartCards = bool.Parse(localResourcesNode.SelectSingleNode("./redirectSmartCards")?.InnerText ?? "false"); + connectionInfo.RedirectDiskDrives = bool.Parse(localResourcesNode?.SelectSingleNode("./redirectDrives")?.InnerText ?? "false"); + connectionInfo.RedirectPorts = bool.Parse(localResourcesNode?.SelectSingleNode("./redirectPorts")?.InnerText ?? "false"); + connectionInfo.RedirectPrinters = bool.Parse(localResourcesNode?.SelectSingleNode("./redirectPrinters")?.InnerText ?? "false"); + connectionInfo.RedirectSmartCards = bool.Parse(localResourcesNode?.SelectSingleNode("./redirectSmartCards")?.InnerText ?? "false"); } else { @@ -287,7 +290,7 @@ namespace mRemoteNG.Config.Serializers } var securitySettingsNode = xmlNode.SelectSingleNode("./securitySettings"); - if (securitySettingsNode?.Attributes?["inherit"].Value == "None") + if (securitySettingsNode?.Attributes?["inherit"]?.Value == "None") { // ReSharper disable once SwitchStatementMissingSomeCases switch (securitySettingsNode.SelectSingleNode("./authentication")?.InnerText) @@ -321,7 +324,7 @@ namespace mRemoteNG.Config.Serializers private static string DecryptRdcManPassword(string ciphertext) { if (string.IsNullOrEmpty(ciphertext)) - return null; + return string.Empty; try { @@ -332,7 +335,7 @@ namespace mRemoteNG.Config.Serializers catch (Exception /*ex*/) { //Runtime.MessageCollector.AddExceptionMessage("RemoteDesktopConnectionManager.DecryptPassword() failed.", ex, logOnly: true); - return null; + return string.Empty; } } } diff --git a/mRemoteV1/Config/Serializers/XmlConnectionsDecryptor.cs b/mRemoteV1/Config/Serializers/XmlConnectionsDecryptor.cs index 45be38de1..efa0683a9 100644 --- a/mRemoteV1/Config/Serializers/XmlConnectionsDecryptor.cs +++ b/mRemoteV1/Config/Serializers/XmlConnectionsDecryptor.cs @@ -4,6 +4,7 @@ using mRemoteNG.Security; using mRemoteNG.Security.Authentication; using mRemoteNG.Security.Factories; using mRemoteNG.Security.SymmetricEncryption; +using mRemoteNG.Tools; using mRemoteNG.Tree.Root; namespace mRemoteNG.Config.Serializers @@ -13,7 +14,7 @@ namespace mRemoteNG.Config.Serializers private readonly ICryptographyProvider _cryptographyProvider; private readonly RootNodeInfo _rootNodeInfo; - public Func AuthenticationRequestor { get; set; } + public Func> AuthenticationRequestor { get; set; } public int KeyDerivationIterations { @@ -91,16 +92,14 @@ namespace mRemoteNG.Config.Serializers private bool Authenticate(string cipherText, SecureString password) { - var authenticator = new PasswordAuthenticator(_cryptographyProvider, cipherText) - { - AuthenticationRequestor = AuthenticationRequestor - }; - + var authenticator = new PasswordAuthenticator(_cryptographyProvider, cipherText, AuthenticationRequestor); var authenticated = authenticator.Authenticate(password); - if (!authenticated) return authenticated; + if (!authenticated) + return false; + _rootNodeInfo.PasswordString = authenticator.LastAuthenticatedPassword.ConvertToUnsecureString(); - return authenticated; + return true; } } } \ No newline at end of file diff --git a/mRemoteV1/Connection/AbstractConnectionRecord.cs b/mRemoteV1/Connection/AbstractConnectionRecord.cs index d07f6e975..8020169a4 100644 --- a/mRemoteV1/Connection/AbstractConnectionRecord.cs +++ b/mRemoteV1/Connection/AbstractConnectionRecord.cs @@ -87,8 +87,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionName")] public virtual string Name { - get { return _name; } - set { SetField(ref _name, value, "Name"); } + get => _name; + set => SetField(ref _name, value, "Name"); } [LocalizedAttributes.LocalizedCategory("strCategoryDisplay"), @@ -96,8 +96,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionDescription")] public virtual string Description { - get { return GetPropertyValue("Description", _description); } - set { SetField(ref _description, value, "Description"); } + get => GetPropertyValue("Description", _description); + set => SetField(ref _description, value, "Description"); } [LocalizedAttributes.LocalizedCategory("strCategoryDisplay"), @@ -106,8 +106,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionIcon")] public virtual string Icon { - get { return GetPropertyValue("Icon", _icon); } - set { SetField(ref _icon, value, "Icon"); } + get => GetPropertyValue("Icon", _icon); + set => SetField(ref _icon, value, "Icon"); } [LocalizedAttributes.LocalizedCategory("strCategoryDisplay"), @@ -115,8 +115,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionPanel")] public virtual string Panel { - get { return GetPropertyValue("Panel", _panel); } - set { SetField(ref _panel, value, "Panel"); } + get => GetPropertyValue("Panel", _panel); + set => SetField(ref _panel, value, "Panel"); } #endregion @@ -126,8 +126,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionAddress")] public virtual string Hostname { - get { return _hostname.Trim(); } - set { SetField(ref _hostname, value?.Trim(), "Hostname"); } + get => _hostname.Trim(); + set => SetField(ref _hostname, value?.Trim(), "Hostname"); } [LocalizedAttributes.LocalizedCategory("strCategoryConnection", 2), @@ -135,8 +135,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionUsername")] public virtual string Username { - get { return GetPropertyValue("Username", _username); } - set { SetField(ref _username, value?.Trim(), "Username"); } + get => GetPropertyValue("Username", _username); + set => SetField(ref _username, value?.Trim(), "Username"); } [LocalizedAttributes.LocalizedCategory("strCategoryConnection", 2), @@ -145,8 +145,8 @@ namespace mRemoteNG.Connection PasswordPropertyText(true)] public virtual string Password { - get { return GetPropertyValue("Password", _password); } - set { SetField(ref _password, value, "Password"); } + get => GetPropertyValue("Password", _password); + set => SetField(ref _password, value, "Password"); } [LocalizedAttributes.LocalizedCategory("strCategoryConnection", 2), @@ -154,8 +154,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionDomain")] public string Domain { - get { return GetPropertyValue("Domain", _domain).Trim(); } - set { SetField(ref _domain, value?.Trim(), "Domain"); } + get => GetPropertyValue("Domain", _domain).Trim(); + set => SetField(ref _domain, value?.Trim(), "Domain"); } #endregion @@ -166,8 +166,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public virtual ProtocolType Protocol { - get { return GetPropertyValue("Protocol", _protocol); } - set { SetField(ref _protocol, value, "Protocol"); } + get => GetPropertyValue("Protocol", _protocol); + set => SetField(ref _protocol, value, "Protocol"); } [LocalizedAttributes.LocalizedCategory("strCategoryProtocol", 3), @@ -176,8 +176,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(ExternalToolsTypeConverter))] public string ExtApp { - get { return GetPropertyValue("ExtApp", _extApp); } - set { SetField(ref _extApp, value, "ExtApp"); } + get => GetPropertyValue("ExtApp", _extApp); + set => SetField(ref _extApp, value, "ExtApp"); } [LocalizedAttributes.LocalizedCategory("strCategoryProtocol", 3), @@ -185,8 +185,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionPort")] public virtual int Port { - get { return GetPropertyValue("Port", _port); } - set { SetField(ref _port, value, "Port"); } + get => GetPropertyValue("Port", _port); + set => SetField(ref _port, value, "Port"); } [LocalizedAttributes.LocalizedCategory("strCategoryProtocol", 3), @@ -195,8 +195,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(Config.Putty.PuttySessionsManager.SessionList))] public virtual string PuttySession { - get { return GetPropertyValue("PuttySession", _puttySession); } - set { SetField(ref _puttySession, value, "PuttySession"); } + get => GetPropertyValue("PuttySession", _puttySession); + set => SetField(ref _puttySession, value, "PuttySession"); } [LocalizedAttributes.LocalizedCategory("strCategoryProtocol", 3), @@ -205,8 +205,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public IcaProtocol.EncryptionStrength ICAEncryptionStrength { - get { return GetPropertyValue("ICAEncryptionStrength", _icaEncryption); } - set { SetField(ref _icaEncryption, value, "ICAEncryptionStrength"); } + get => GetPropertyValue("ICAEncryptionStrength", _icaEncryption); + set => SetField(ref _icaEncryption, value, "ICAEncryptionStrength"); } [LocalizedAttributes.LocalizedCategory("strCategoryProtocol", 3), @@ -215,8 +215,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.YesNoTypeConverter))] public bool UseConsoleSession { - get { return GetPropertyValue("UseConsoleSession", _useConsoleSession); } - set { SetField(ref _useConsoleSession, value, "UseConsoleSession"); } + get => GetPropertyValue("UseConsoleSession", _useConsoleSession); + set => SetField(ref _useConsoleSession, value, "UseConsoleSession"); } [LocalizedAttributes.LocalizedCategory("strCategoryProtocol", 3), @@ -225,8 +225,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public RdpProtocol.AuthenticationLevel RDPAuthenticationLevel { - get { return GetPropertyValue("RDPAuthenticationLevel", _rdpAuthenticationLevel); } - set { SetField(ref _rdpAuthenticationLevel, value, "RDPAuthenticationLevel"); } + get => GetPropertyValue("RDPAuthenticationLevel", _rdpAuthenticationLevel); + set => SetField(ref _rdpAuthenticationLevel, value, "RDPAuthenticationLevel"); } [LocalizedAttributes.LocalizedCategory("strCategoryProtocol", 3), @@ -234,7 +234,7 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionRDPMinutesToIdleTimeout")] public virtual int RDPMinutesToIdleTimeout { - get { return GetPropertyValue("RDPMinutesToIdleTimeout", _rdpMinutesToIdleTimeout); } + get => GetPropertyValue("RDPMinutesToIdleTimeout", _rdpMinutesToIdleTimeout); set { if(value < 0) value = 0; @@ -249,8 +249,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionRDPAlertIdleTimeout")] public bool RDPAlertIdleTimeout { - get { return GetPropertyValue("RDPAlertIdleTimeout", _rdpAlertIdleTimeout); } - set { SetField(ref _rdpAlertIdleTimeout, value, "RDPAlertIdleTimeout"); } + get => GetPropertyValue("RDPAlertIdleTimeout", _rdpAlertIdleTimeout); + set => SetField(ref _rdpAlertIdleTimeout, value, "RDPAlertIdleTimeout"); } [LocalizedAttributes.LocalizedCategory("strCategoryProtocol", 3), @@ -258,8 +258,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionLoadBalanceInfo")] public string LoadBalanceInfo { - get { return GetPropertyValue("LoadBalanceInfo", _loadBalanceInfo).Trim(); } - set { SetField(ref _loadBalanceInfo, value?.Trim(), "LoadBalanceInfo"); } + get => GetPropertyValue("LoadBalanceInfo", _loadBalanceInfo).Trim(); + set => SetField(ref _loadBalanceInfo, value?.Trim(), "LoadBalanceInfo"); } [LocalizedAttributes.LocalizedCategory("strCategoryProtocol", 3), @@ -268,8 +268,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public HTTPBase.RenderingEngine RenderingEngine { - get { return GetPropertyValue("RenderingEngine", _renderingEngine); } - set { SetField(ref _renderingEngine, value, "RenderingEngine"); } + get => GetPropertyValue("RenderingEngine", _renderingEngine); + set => SetField(ref _renderingEngine, value, "RenderingEngine"); } [LocalizedAttributes.LocalizedCategory("strCategoryProtocol", 3), @@ -278,8 +278,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.YesNoTypeConverter))] public bool UseCredSsp { - get { return GetPropertyValue("UseCredSsp", _useCredSsp); } - set { SetField(ref _useCredSsp, value, "UseCredSsp"); } + get => GetPropertyValue("UseCredSsp", _useCredSsp); + set => SetField(ref _useCredSsp, value, "UseCredSsp"); } #endregion @@ -290,8 +290,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public RdpProtocol.RDGatewayUsageMethod RDGatewayUsageMethod { - get { return GetPropertyValue("RDGatewayUsageMethod", _rdGatewayUsageMethod); } - set { SetField(ref _rdGatewayUsageMethod, value, "RDGatewayUsageMethod"); } + get => GetPropertyValue("RDGatewayUsageMethod", _rdGatewayUsageMethod); + set => SetField(ref _rdGatewayUsageMethod, value, "RDGatewayUsageMethod"); } [LocalizedAttributes.LocalizedCategory("strCategoryGateway", 4), @@ -299,8 +299,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionRDGatewayHostname")] public string RDGatewayHostname { - get { return GetPropertyValue("RDGatewayHostname", _rdGatewayHostname).Trim(); } - set { SetField(ref _rdGatewayHostname, value?.Trim(), "RDGatewayHostname"); } + get => GetPropertyValue("RDGatewayHostname", _rdGatewayHostname).Trim(); + set => SetField(ref _rdGatewayHostname, value?.Trim(), "RDGatewayHostname"); } [LocalizedAttributes.LocalizedCategory("strCategoryGateway", 4), @@ -309,8 +309,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public RdpProtocol.RDGatewayUseConnectionCredentials RDGatewayUseConnectionCredentials { - get { return GetPropertyValue("RDGatewayUseConnectionCredentials", _rdGatewayUseConnectionCredentials); } - set { SetField(ref _rdGatewayUseConnectionCredentials, value, "RDGatewayUseConnectionCredentials"); } + get => GetPropertyValue("RDGatewayUseConnectionCredentials", _rdGatewayUseConnectionCredentials); + set => SetField(ref _rdGatewayUseConnectionCredentials, value, "RDGatewayUseConnectionCredentials"); } [LocalizedAttributes.LocalizedCategory("strCategoryGateway", 4), @@ -318,8 +318,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionRDGatewayUsername")] public string RDGatewayUsername { - get { return GetPropertyValue("RDGatewayUsername", _rdGatewayUsername).Trim(); } - set { SetField(ref _rdGatewayUsername, value?.Trim(), "RDGatewayUsername"); } + get => GetPropertyValue("RDGatewayUsername", _rdGatewayUsername).Trim(); + set => SetField(ref _rdGatewayUsername, value?.Trim(), "RDGatewayUsername"); } [LocalizedAttributes.LocalizedCategory("strCategoryGateway", 4), @@ -328,8 +328,8 @@ namespace mRemoteNG.Connection PasswordPropertyText(true)] public string RDGatewayPassword { - get { return GetPropertyValue("RDGatewayPassword", _rdGatewayPassword); } - set { SetField(ref _rdGatewayPassword, value, "RDGatewayPassword"); } + get => GetPropertyValue("RDGatewayPassword", _rdGatewayPassword); + set => SetField(ref _rdGatewayPassword, value, "RDGatewayPassword"); } [LocalizedAttributes.LocalizedCategory("strCategoryGateway", 4), @@ -337,8 +337,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionRDGatewayDomain")] public string RDGatewayDomain { - get { return GetPropertyValue("RDGatewayDomain", _rdGatewayDomain).Trim(); } - set { SetField(ref _rdGatewayDomain, value?.Trim(), "RDGatewayDomain"); } + get => GetPropertyValue("RDGatewayDomain", _rdGatewayDomain).Trim(); + set => SetField(ref _rdGatewayDomain, value?.Trim(), "RDGatewayDomain"); } #endregion @@ -349,8 +349,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public RdpProtocol.RDPResolutions Resolution { - get { return GetPropertyValue("Resolution", _resolution); } - set { SetField(ref _resolution, value, "Resolution"); } + get => GetPropertyValue("Resolution", _resolution); + set => SetField(ref _resolution, value, "Resolution"); } [LocalizedAttributes.LocalizedCategory("strCategoryAppearance", 5), @@ -359,8 +359,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.YesNoTypeConverter))] public bool AutomaticResize { - get { return GetPropertyValue("AutomaticResize", _automaticResize); } - set { SetField(ref _automaticResize, value, "AutomaticResize"); } + get => GetPropertyValue("AutomaticResize", _automaticResize); + set => SetField(ref _automaticResize, value, "AutomaticResize"); } [LocalizedAttributes.LocalizedCategory("strCategoryAppearance", 5), @@ -369,8 +369,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public RdpProtocol.RDPColors Colors { - get { return GetPropertyValue("Colors", _colors); } - set { SetField(ref _colors, value, "Colors"); } + get => GetPropertyValue("Colors", _colors); + set => SetField(ref _colors, value, "Colors"); } [LocalizedAttributes.LocalizedCategory("strCategoryAppearance", 5), @@ -379,8 +379,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.YesNoTypeConverter))] public bool CacheBitmaps { - get { return GetPropertyValue("CacheBitmaps", _cacheBitmaps); } - set { SetField(ref _cacheBitmaps, value, "CacheBitmaps"); } + get => GetPropertyValue("CacheBitmaps", _cacheBitmaps); + set => SetField(ref _cacheBitmaps, value, "CacheBitmaps"); } [LocalizedAttributes.LocalizedCategory("strCategoryAppearance", 5), @@ -389,8 +389,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.YesNoTypeConverter))] public bool DisplayWallpaper { - get { return GetPropertyValue("DisplayWallpaper", _displayWallpaper); } - set { SetField(ref _displayWallpaper, value, "DisplayWallpaper"); } + get => GetPropertyValue("DisplayWallpaper", _displayWallpaper); + set => SetField(ref _displayWallpaper, value, "DisplayWallpaper"); } [LocalizedAttributes.LocalizedCategory("strCategoryAppearance", 5), @@ -399,8 +399,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.YesNoTypeConverter))] public bool DisplayThemes { - get { return GetPropertyValue("DisplayThemes", _displayThemes); } - set { SetField(ref _displayThemes, value, "DisplayThemes"); } + get => GetPropertyValue("DisplayThemes", _displayThemes); + set => SetField(ref _displayThemes, value, "DisplayThemes"); } [LocalizedAttributes.LocalizedCategory("strCategoryAppearance", 5), @@ -409,8 +409,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.YesNoTypeConverter))] public bool EnableFontSmoothing { - get { return GetPropertyValue("EnableFontSmoothing", _enableFontSmoothing); } - set { SetField(ref _enableFontSmoothing, value, "EnableFontSmoothing"); } + get => GetPropertyValue("EnableFontSmoothing", _enableFontSmoothing); + set => SetField(ref _enableFontSmoothing, value, "EnableFontSmoothing"); } [LocalizedAttributes.LocalizedCategory("strCategoryAppearance", 5), @@ -419,8 +419,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.YesNoTypeConverter))] public bool EnableDesktopComposition { - get { return GetPropertyValue("EnableDesktopComposition", _enableDesktopComposition); } - set { SetField(ref _enableDesktopComposition, value, "EnableDesktopComposition"); } + get => GetPropertyValue("EnableDesktopComposition", _enableDesktopComposition); + set => SetField(ref _enableDesktopComposition, value, "EnableDesktopComposition"); } #endregion @@ -431,8 +431,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.YesNoTypeConverter))] public bool RedirectKeys { - get { return GetPropertyValue("RedirectKeys", _redirectKeys); } - set { SetField(ref _redirectKeys, value, "RedirectKeys"); } + get => GetPropertyValue("RedirectKeys", _redirectKeys); + set => SetField(ref _redirectKeys, value, "RedirectKeys"); } [LocalizedAttributes.LocalizedCategory("strCategoryRedirect", 6), @@ -441,8 +441,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.YesNoTypeConverter))] public bool RedirectDiskDrives { - get { return GetPropertyValue("RedirectDiskDrives", _redirectDiskDrives); } - set { SetField(ref _redirectDiskDrives, value, "RedirectDiskDrives"); } + get => GetPropertyValue("RedirectDiskDrives", _redirectDiskDrives); + set => SetField(ref _redirectDiskDrives, value, "RedirectDiskDrives"); } [LocalizedAttributes.LocalizedCategory("strCategoryRedirect", 6), @@ -451,8 +451,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.YesNoTypeConverter))] public bool RedirectPrinters { - get { return GetPropertyValue("RedirectPrinters", _redirectPrinters); } - set { SetField(ref _redirectPrinters, value, "RedirectPrinters"); } + get => GetPropertyValue("RedirectPrinters", _redirectPrinters); + set => SetField(ref _redirectPrinters, value, "RedirectPrinters"); } [LocalizedAttributes.LocalizedCategory("strCategoryRedirect", 6), @@ -461,8 +461,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.YesNoTypeConverter))] public bool RedirectPorts { - get { return GetPropertyValue("RedirectPorts", _redirectPorts); } - set { SetField(ref _redirectPorts, value, "RedirectPorts"); } + get => GetPropertyValue("RedirectPorts", _redirectPorts); + set => SetField(ref _redirectPorts, value, "RedirectPorts"); } [LocalizedAttributes.LocalizedCategory("strCategoryRedirect", 6), @@ -471,8 +471,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.YesNoTypeConverter))] public bool RedirectSmartCards { - get { return GetPropertyValue("RedirectSmartCards", _redirectSmartCards); } - set { SetField(ref _redirectSmartCards, value, "RedirectSmartCards"); } + get => GetPropertyValue("RedirectSmartCards", _redirectSmartCards); + set => SetField(ref _redirectSmartCards, value, "RedirectSmartCards"); } [LocalizedAttributes.LocalizedCategory("strCategoryRedirect", 6), @@ -481,8 +481,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public RdpProtocol.RDPSounds RedirectSound { - get { return GetPropertyValue("RedirectSound", _redirectSound); } - set { SetField(ref _redirectSound, value, "RedirectSound"); } + get => GetPropertyValue("RedirectSound", _redirectSound); + set => SetField(ref _redirectSound, value, "RedirectSound"); } [LocalizedAttributes.LocalizedCategory("strCategoryRedirect", 6), @@ -491,8 +491,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public RdpProtocol.RDPSoundQuality SoundQuality { - get { return GetPropertyValue("SoundQuality", _soundQuality); } - set { SetField(ref _soundQuality, value, "SoundQuality"); } + get => GetPropertyValue("SoundQuality", _soundQuality); + set => SetField(ref _soundQuality, value, "SoundQuality"); } #endregion @@ -506,8 +506,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(ExternalToolsTypeConverter))] public virtual string PreExtApp { - get { return GetPropertyValue("PreExtApp", _preExtApp); } - set { SetField(ref _preExtApp, value, "PreExtApp"); } + get => GetPropertyValue("PreExtApp", _preExtApp); + set => SetField(ref _preExtApp, value, "PreExtApp"); } [LocalizedAttributes.LocalizedCategory("strCategoryMiscellaneous", 7), @@ -516,8 +516,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(ExternalToolsTypeConverter))] public virtual string PostExtApp { - get { return GetPropertyValue("PostExtApp", _postExtApp); } - set { SetField(ref _postExtApp, value, "PostExtApp"); } + get => GetPropertyValue("PostExtApp", _postExtApp); + set => SetField(ref _postExtApp, value, "PostExtApp"); } [LocalizedAttributes.LocalizedCategory("strCategoryMiscellaneous", 7), @@ -525,8 +525,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionMACAddress")] public virtual string MacAddress { - get { return GetPropertyValue("MacAddress", _macAddress); } - set { SetField(ref _macAddress, value, "MacAddress"); } + get => GetPropertyValue("MacAddress", _macAddress); + set => SetField(ref _macAddress, value, "MacAddress"); } [LocalizedAttributes.LocalizedCategory("strCategoryMiscellaneous", 7), @@ -534,8 +534,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionUser1")] public virtual string UserField { - get { return GetPropertyValue("UserField", _userField); } - set { SetField(ref _userField, value, "UserField"); } + get => GetPropertyValue("UserField", _userField); + set => SetField(ref _userField, value, "UserField"); } #endregion @@ -547,8 +547,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public ProtocolVNC.Compression VNCCompression { - get { return GetPropertyValue("VNCCompression", _vncCompression); } - set { SetField(ref _vncCompression, value, "VNCCompression"); } + get => GetPropertyValue("VNCCompression", _vncCompression); + set => SetField(ref _vncCompression, value, "VNCCompression"); } [LocalizedAttributes.LocalizedCategory("strCategoryAppearance", 5), @@ -558,8 +558,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public ProtocolVNC.Encoding VNCEncoding { - get { return GetPropertyValue("VNCEncoding", _vncEncoding); } - set { SetField(ref _vncEncoding, value, "VNCEncoding"); } + get => GetPropertyValue("VNCEncoding", _vncEncoding); + set => SetField(ref _vncEncoding, value, "VNCEncoding"); } [LocalizedAttributes.LocalizedCategory("strCategoryConnection", 2), @@ -569,8 +569,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public ProtocolVNC.AuthMode VNCAuthMode { - get { return GetPropertyValue("VNCAuthMode", _vncAuthMode); } - set { SetField(ref _vncAuthMode, value, "VNCAuthMode"); } + get => GetPropertyValue("VNCAuthMode", _vncAuthMode); + set => SetField(ref _vncAuthMode, value, "VNCAuthMode"); } [LocalizedAttributes.LocalizedCategory("strCategoryMiscellaneous", 7), @@ -580,8 +580,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public ProtocolVNC.ProxyType VNCProxyType { - get { return GetPropertyValue("VNCProxyType", _vncProxyType); } - set { SetField(ref _vncProxyType, value, "VNCProxyType"); } + get => GetPropertyValue("VNCProxyType", _vncProxyType); + set => SetField(ref _vncProxyType, value, "VNCProxyType"); } [LocalizedAttributes.LocalizedCategory("strCategoryMiscellaneous", 7), @@ -590,8 +590,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionVNCProxyAddress")] public string VNCProxyIP { - get { return GetPropertyValue("VNCProxyIP", _vncProxyIp); } - set { SetField(ref _vncProxyIp, value, "VNCProxyIP"); } + get => GetPropertyValue("VNCProxyIP", _vncProxyIp); + set => SetField(ref _vncProxyIp, value, "VNCProxyIP"); } [LocalizedAttributes.LocalizedCategory("strCategoryMiscellaneous", 7), @@ -600,8 +600,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionVNCProxyPort")] public int VNCProxyPort { - get { return GetPropertyValue("VNCProxyPort", _vncProxyPort); } - set { SetField(ref _vncProxyPort, value, "VNCProxyPort"); } + get => GetPropertyValue("VNCProxyPort", _vncProxyPort); + set => SetField(ref _vncProxyPort, value, "VNCProxyPort"); } [LocalizedAttributes.LocalizedCategory("strCategoryMiscellaneous", 7), @@ -610,8 +610,8 @@ namespace mRemoteNG.Connection LocalizedAttributes.LocalizedDescription("strPropertyDescriptionVNCProxyUsername")] public string VNCProxyUsername { - get { return GetPropertyValue("VNCProxyUsername", _vncProxyUsername); } - set { SetField(ref _vncProxyUsername, value, "VNCProxyUsername"); } + get => GetPropertyValue("VNCProxyUsername", _vncProxyUsername); + set => SetField(ref _vncProxyUsername, value, "VNCProxyUsername"); } [LocalizedAttributes.LocalizedCategory("strCategoryMiscellaneous", 7), @@ -621,8 +621,8 @@ namespace mRemoteNG.Connection PasswordPropertyText(true)] public string VNCProxyPassword { - get { return GetPropertyValue("VNCProxyPassword", _vncProxyPassword); } - set { SetField(ref _vncProxyPassword, value, "VNCProxyPassword"); } + get => GetPropertyValue("VNCProxyPassword", _vncProxyPassword); + set => SetField(ref _vncProxyPassword, value, "VNCProxyPassword"); } [LocalizedAttributes.LocalizedCategory("strCategoryAppearance", 5), @@ -632,8 +632,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public ProtocolVNC.Colors VNCColors { - get { return GetPropertyValue("VNCColors", _vncColors); } - set { SetField(ref _vncColors, value, "VNCColors"); } + get => GetPropertyValue("VNCColors", _vncColors); + set => SetField(ref _vncColors, value, "VNCColors"); } [LocalizedAttributes.LocalizedCategory("strCategoryAppearance", 5), @@ -642,8 +642,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.EnumTypeConverter))] public ProtocolVNC.SmartSizeMode VNCSmartSizeMode { - get { return GetPropertyValue("VNCSmartSizeMode", _vncSmartSizeMode); } - set { SetField(ref _vncSmartSizeMode, value, "VNCSmartSizeMode"); } + get => GetPropertyValue("VNCSmartSizeMode", _vncSmartSizeMode); + set => SetField(ref _vncSmartSizeMode, value, "VNCSmartSizeMode"); } [LocalizedAttributes.LocalizedCategory("strCategoryAppearance", 5), @@ -652,8 +652,8 @@ namespace mRemoteNG.Connection TypeConverter(typeof(MiscTools.YesNoTypeConverter))] public bool VNCViewOnly { - get { return GetPropertyValue("VNCViewOnly", _vncViewOnly); } - set { SetField(ref _vncViewOnly, value, "VNCViewOnly"); } + get => GetPropertyValue("VNCViewOnly", _vncViewOnly); + set => SetField(ref _vncViewOnly, value, "VNCViewOnly"); } #endregion #endregion @@ -665,7 +665,7 @@ namespace mRemoteNG.Connection protected virtual TPropertyType GetPropertyValue(string propertyName, TPropertyType value) { - return (TPropertyType)GetType().GetProperty(propertyName).GetValue(this, null); + return (TPropertyType)GetType().GetProperty(propertyName)?.GetValue(this, null); } public event PropertyChangedEventHandler PropertyChanged; @@ -674,12 +674,11 @@ namespace mRemoteNG.Connection PropertyChanged?.Invoke(sender, new PropertyChangedEventArgs(args.PropertyName)); } - protected bool SetField(ref T field, T value, string propertyName = null) + private void SetField(ref T field, T value, string propertyName = null) { - if (EqualityComparer.Default.Equals(field, value)) return false; + if (EqualityComparer.Default.Equals(field, value)) return; field = value; RaisePropertyChangedEvent(this, new PropertyChangedEventArgs(propertyName)); - return true; } } } \ No newline at end of file diff --git a/mRemoteV1/Connection/ConnectionInfo.cs b/mRemoteV1/Connection/ConnectionInfo.cs index dd6fda9f7..646ddda77 100644 --- a/mRemoteV1/Connection/ConnectionInfo.cs +++ b/mRemoteV1/Connection/ConnectionInfo.cs @@ -173,9 +173,7 @@ namespace mRemoteNG.Connection return value; var inheritedValue = GetInheritedPropertyValue(propertyName); - if (inheritedValue.Equals(default(TPropertyType))) - return value; - return inheritedValue; + return inheritedValue.Equals(default(TPropertyType)) ? value : inheritedValue; } private bool ShouldThisPropertyBeInherited(string propertyName) diff --git a/mRemoteV1/Connection/ConnectionsService.cs b/mRemoteV1/Connection/ConnectionsService.cs index 2b6066930..14ef75b34 100644 --- a/mRemoteV1/Connection/ConnectionsService.cs +++ b/mRemoteV1/Connection/ConnectionsService.cs @@ -8,7 +8,6 @@ using mRemoteNG.App.Info; using mRemoteNG.Config.Connections; using mRemoteNG.Config.Connections.Multiuser; using mRemoteNG.Config.DatabaseConnectors; -using mRemoteNG.Config.DataProviders; using mRemoteNG.Config.Putty; using mRemoteNG.Connection.Protocol; using mRemoteNG.Messages; @@ -17,7 +16,6 @@ using mRemoteNG.Tools; using mRemoteNG.Tree; using mRemoteNG.Tree.Root; using mRemoteNG.UI; -using mRemoteNG.UI.Forms; using mRemoteNG.UI.TaskDialog; namespace mRemoteNG.Connection @@ -29,6 +27,9 @@ namespace mRemoteNG.Connection private readonly PuttySessionsManager _puttySessionsManager; private readonly Import _import; private readonly IWin32Window _dialogWindowParent; + private bool _batchingSaves = false; + private bool _saveRequested = false; + private bool _saveAsyncRequested = false; public bool IsConnectionsFileLoaded { get; set; } public bool UsingDatabase { get; private set; } @@ -58,9 +59,8 @@ namespace mRemoteNG.Connection { var newConnectionsModel = new ConnectionTreeModel(); newConnectionsModel.AddRootNode(new RootNodeInfo(RootNodeType.Connection)); - SaveConnections(newConnectionsModel, false, new SaveFilter(), filename); + SaveConnections(newConnectionsModel, false, new SaveFilter(), filename, true); LoadConnections(false, false, filename); - UpdateCustomConsPathSetting(filename); } catch (Exception ex) { @@ -108,16 +108,24 @@ namespace mRemoteNG.Connection /// /// /// - public ConnectionTreeModel LoadConnections(bool useDatabase, bool import, string connectionFileName) + public void LoadConnections(bool useDatabase, bool import, string connectionFileName) { var oldConnectionTreeModel = ConnectionTreeModel; var oldIsUsingDatabaseValue = UsingDatabase; - var newConnectionTreeModel = - (useDatabase + var newConnectionTreeModel = useDatabase ? new SqlConnectionsLoader(DatabaseConnectorFactory, this).Load() - : new XmlConnectionsLoader(connectionFileName, this, _dialogWindowParent).Load()) - ?? new ConnectionTreeModel(); + : new XmlConnectionsLoader(connectionFileName, this, _dialogWindowParent).Load(); + + if (newConnectionTreeModel == null) + { + DialogFactory.ShowLoadConnectionsFailedDialog(connectionFileName, "Decrypting connection file failed", IsConnectionsFileLoaded); + return; + } + + IsConnectionsFileLoaded = true; + ConnectionFileName = connectionFileName; + UsingDatabase = useDatabase; if (!import) { @@ -125,12 +133,24 @@ namespace mRemoteNG.Connection newConnectionTreeModel.RootNodes.AddRange(_puttySessionsManager.RootPuttySessionsNodes); } - IsConnectionsFileLoaded = true; - ConnectionFileName = connectionFileName; - UsingDatabase = useDatabase; ConnectionTreeModel = newConnectionTreeModel; + UpdateCustomConsPathSetting(connectionFileName); RaiseConnectionsLoadedEvent(oldConnectionTreeModel, newConnectionTreeModel, oldIsUsingDatabaseValue, useDatabase, connectionFileName); - return newConnectionTreeModel; + } + + public void BeginBatchingSaves() + { + _batchingSaves = true; + } + + public void EndBatchingSaves() + { + _batchingSaves = false; + + if (_saveAsyncRequested) + SaveConnectionsAsync(); + else if(_saveRequested) + SaveConnections(); } /// @@ -139,8 +159,6 @@ namespace mRemoteNG.Connection /// public void SaveConnections() { - if (!IsConnectionsFileLoaded) - return; SaveConnections(ConnectionTreeModel, UsingDatabase, new SaveFilter(), ConnectionFileName); } @@ -152,12 +170,24 @@ namespace mRemoteNG.Connection /// /// /// - public void SaveConnections(ConnectionTreeModel connectionTreeModel, bool useDatabase, SaveFilter saveFilter, string connectionFileName) + /// Bypasses safety checks that prevent saving if a connection file isn't loaded. + public void SaveConnections(ConnectionTreeModel connectionTreeModel, bool useDatabase, SaveFilter saveFilter, string connectionFileName, bool forceSave = false) { - if (connectionTreeModel == null) return; + if (connectionTreeModel == null) + return; + + if (!forceSave && !IsConnectionsFileLoaded) + return; + + if (_batchingSaves) + { + _saveRequested = true; + return; + } try { + Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg, "Saving connections..."); RemoteConnectionsSyncronizer?.Disable(); var previouslyUsingDatabase = UsingDatabase; @@ -172,6 +202,7 @@ namespace mRemoteNG.Connection UsingDatabase = useDatabase; ConnectionFileName = connectionFileName; RaiseConnectionsSavedEvent(connectionTreeModel, previouslyUsingDatabase, UsingDatabase, connectionFileName); + Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg, "Successfully saved connections"); } catch (Exception ex) { @@ -185,6 +216,12 @@ namespace mRemoteNG.Connection public void SaveConnectionsAsync() { + if (_batchingSaves) + { + _saveAsyncRequested = true; + return; + } + var t = new Thread(SaveConnectionsBGd); t.SetApartmentState(ApartmentState.STA); t.Start(); @@ -233,12 +270,6 @@ namespace mRemoteNG.Connection { connectionFileName = GetStartupConnectionFileName(); } - - var backupFileCreator = new FileBackupCreator(); - backupFileCreator.CreateBackupFile(connectionFileName); - - var backupPruner = new FileBackupPruner(); - backupPruner.PruneBackupFiles(connectionFileName); } LoadConnections(Settings.Default.UseSQLServer, false, connectionFileName); @@ -356,12 +387,16 @@ namespace mRemoteNG.Connection public string GetStartupConnectionFileName() { - return Settings.Default.LoadConsFromCustomLocation == false ? GetDefaultStartupConnectionFileName() : Settings.Default.CustomConsPath; + return Settings.Default.LoadConsFromCustomLocation == false + ? GetDefaultStartupConnectionFileName() + : Settings.Default.CustomConsPath; } public string GetDefaultStartupConnectionFileName() { - return Runtime.IsPortableEdition ? GetDefaultStartupConnectionFileNamePortableEdition() : GetDefaultStartupConnectionFileNameNormalEdition(); + return Runtime.IsPortableEdition + ? GetDefaultStartupConnectionFileNamePortableEdition() + : GetDefaultStartupConnectionFileNameNormalEdition(); } private void UpdateCustomConsPathSetting(string filename) diff --git a/mRemoteV1/Connection/IConnectionsService.cs b/mRemoteV1/Connection/IConnectionsService.cs index 397fada1c..aafad0b86 100644 --- a/mRemoteV1/Connection/IConnectionsService.cs +++ b/mRemoteV1/Connection/IConnectionsService.cs @@ -27,11 +27,11 @@ namespace mRemoteNG.Connection string GetDefaultStartupConnectionFileName(); string GetStartupConnectionFileName(); void LoadConnections(bool withDialog = false); - ConnectionTreeModel LoadConnections(bool useDatabase, bool import, string connectionFileName); + void LoadConnections(bool useDatabase, bool import, string connectionFileName); void LoadConnectionsAsync(); void NewConnectionsFile(string filename); void SaveConnections(); - void SaveConnections(ConnectionTreeModel connectionTreeModel, bool useDatabase, SaveFilter saveFilter, string connectionFileName); + void SaveConnections(ConnectionTreeModel connectionTreeModel, bool useDatabase, SaveFilter saveFilter, string connectionFileName, bool forceSave = false); void SaveConnectionsAsync(); } } \ No newline at end of file diff --git a/mRemoteV1/Properties/AssemblyInfo.cs b/mRemoteV1/Properties/AssemblyInfo.cs index 61818bcb5..47f3885dc 100644 --- a/mRemoteV1/Properties/AssemblyInfo.cs +++ b/mRemoteV1/Properties/AssemblyInfo.cs @@ -33,5 +33,5 @@ using System.Runtime.InteropServices; // by using the '*' as shown below: // -[assembly: AssemblyVersion("1.76.3.*")] +[assembly: AssemblyVersion("1.76.5.*")] [assembly: NeutralResourcesLanguage("en")] \ No newline at end of file diff --git a/mRemoteV1/Properties/Settings.Designer.cs b/mRemoteV1/Properties/Settings.Designer.cs index 74dd40295..8baba3150 100644 --- a/mRemoteV1/Properties/Settings.Designer.cs +++ b/mRemoteV1/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace mRemoteNG { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.5.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -2698,5 +2698,29 @@ namespace mRemoteNG { this["MultiSshToolbarVisible"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool CreateEmptyPanelOnStartUp { + get { + return ((bool)(this["CreateEmptyPanelOnStartUp"])); + } + set { + this["CreateEmptyPanelOnStartUp"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("General")] + public string StartUpPanelName { + get { + return ((string)(this["StartUpPanelName"])); + } + set { + this["StartUpPanelName"] = value; + } + } } } diff --git a/mRemoteV1/Properties/Settings.settings b/mRemoteV1/Properties/Settings.settings index 3fb304105..313e4d63a 100644 --- a/mRemoteV1/Properties/Settings.settings +++ b/mRemoteV1/Properties/Settings.settings @@ -671,5 +671,11 @@ False + + False + + + General + \ No newline at end of file diff --git a/mRemoteV1/Resources/Language/Language.Designer.cs b/mRemoteV1/Resources/Language/Language.Designer.cs index 1cb1170cc..e93828e7c 100644 --- a/mRemoteV1/Resources/Language/Language.Designer.cs +++ b/mRemoteV1/Resources/Language/Language.Designer.cs @@ -1646,6 +1646,15 @@ namespace mRemoteNG { } } + /// + /// Looks up a localized string similar to Create an empty panel when mRemoteNG starts. + /// + internal static string strCreateEmptyPanelOnStartUp { + get { + return ResourceManager.GetString("strCreateEmptyPanelOnStartUp", resourceCulture); + } + } + /// /// Looks up a localized string similar to Credential Editor. /// diff --git a/mRemoteV1/Resources/Language/Language.resx b/mRemoteV1/Resources/Language/Language.resx index 6d825a51c..f1aae0d96 100644 --- a/mRemoteV1/Resources/Language/Language.resx +++ b/mRemoteV1/Resources/Language/Language.resx @@ -2676,4 +2676,7 @@ This page will walk you through the process of upgrading your connections file o Use UTF8 encoding for RDP "Load Balance Info" property + + Create an empty panel when mRemoteNG starts + \ No newline at end of file diff --git a/mRemoteV1/Security/Authentication/PasswordAuthenticator.cs b/mRemoteV1/Security/Authentication/PasswordAuthenticator.cs index 1d6904e87..83bf82a6d 100644 --- a/mRemoteV1/Security/Authentication/PasswordAuthenticator.cs +++ b/mRemoteV1/Security/Authentication/PasswordAuthenticator.cs @@ -1,5 +1,7 @@ using System; +using System.Linq; using System.Security; +using mRemoteNG.Tools; namespace mRemoteNG.Security.Authentication { @@ -7,15 +9,16 @@ namespace mRemoteNG.Security.Authentication { private readonly ICryptographyProvider _cryptographyProvider; private readonly string _cipherText; + private readonly Func> _authenticationRequestor; - public Func AuthenticationRequestor { get; set; } public int MaxAttempts { get; set; } = 3; public SecureString LastAuthenticatedPassword { get; private set; } - public PasswordAuthenticator(ICryptographyProvider cryptographyProvider, string cipherText) + public PasswordAuthenticator(ICryptographyProvider cryptographyProvider, string cipherText, Func> authenticationRequestor) { - _cryptographyProvider = cryptographyProvider; - _cipherText = cipherText; + _cryptographyProvider = cryptographyProvider.ThrowIfNull(nameof(cryptographyProvider)); + _cipherText = cipherText.ThrowIfNullOrEmpty(nameof(cipherText)); + _authenticationRequestor = authenticationRequestor.ThrowIfNull(nameof(authenticationRequestor)); } public bool Authenticate(SecureString password) @@ -32,7 +35,11 @@ namespace mRemoteNG.Security.Authentication } catch { - password = AuthenticationRequestor?.Invoke(); + var providedPassword = _authenticationRequestor(); + if (!providedPassword.Any()) + return false; + + password = providedPassword.First(); if (password == null || password.Length == 0) break; } attempts++; diff --git a/mRemoteV1/Security/IKeyProvider.cs b/mRemoteV1/Security/IKeyProvider.cs index ed46749ab..425351113 100644 --- a/mRemoteV1/Security/IKeyProvider.cs +++ b/mRemoteV1/Security/IKeyProvider.cs @@ -1,9 +1,10 @@ using System.Security; +using mRemoteNG.Tools; namespace mRemoteNG.Security { public interface IKeyProvider { - SecureString GetKey(); + Optional GetKey(); } } \ No newline at end of file diff --git a/mRemoteV1/Tools/MiscTools.cs b/mRemoteV1/Tools/MiscTools.cs index d3b39cf61..03da94394 100644 --- a/mRemoteV1/Tools/MiscTools.cs +++ b/mRemoteV1/Tools/MiscTools.cs @@ -12,7 +12,7 @@ using static System.String; namespace mRemoteNG.Tools { - public static class MiscTools + public static class MiscTools { public static Icon GetIconFromFile(string FileName) { @@ -34,7 +34,7 @@ namespace mRemoteNG.Tools } } - public static SecureString PasswordDialog(string passwordName = null, bool verify = true) + public static Optional PasswordDialog(string passwordName = null, bool verify = true) { var passwordForm = new PasswordForm(passwordName, verify); return passwordForm.GetKey(); diff --git a/mRemoteV1/Tools/Optional.cs b/mRemoteV1/Tools/Optional.cs index 53204272c..dc1dc0c18 100644 --- a/mRemoteV1/Tools/Optional.cs +++ b/mRemoteV1/Tools/Optional.cs @@ -1,50 +1,152 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; using System.Linq; namespace mRemoteNG.Tools { - public class Optional : IEnumerable - { - private readonly T[] _maybe; + /// + /// Represents a type that may or may not have been assigned a value. + /// A strongly typed collection that contains either 0 or 1 values. + /// + /// The underlying type that may or may not have a value + public class Optional : IEnumerable, IComparable> + { + private readonly T[] _optional; + /// + /// Create a new empty instance of Optional + /// public Optional() { - _maybe = new T[0]; + _optional = new T[0]; } + /// + /// Create a new instance of Optional from the given value. + /// If the value is null, the Optional will be empty + /// public Optional(T value) { - _maybe = value != null + _optional = value != null ? new[] {value} : new T[0]; } - IEnumerator IEnumerable.GetEnumerator() + public override string ToString() + { + return _optional.Any() ? _optional.First().ToString() : ""; + } + + public static implicit operator Optional(T value) + { + return new Optional(value); + } + + public static Optional FromNullable(TOut? value) where TOut : struct + { + return value.HasValue + ? new Optional(value.Value) + : new Optional(); + } + + /// + /// Returns an empty + /// + public static Optional Empty => new Optional(); + + #region IEnumerable + IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerator GetEnumerator() { - return ((IEnumerable)_maybe).GetEnumerator(); + return ((IEnumerable)_optional).GetEnumerator(); } + #endregion - public override string ToString() + #region IComparable + /// + /// Compares this to another instance + /// of the same type. For purposes of comparison, empty Optional + /// objects are treated like Null and will be valued lower than + /// an Optional that contains a value. If both Optionals contain + /// values, the values are compared directly. + /// + /// + public int CompareTo(Optional other) + { + var otherHasAnything = other.Any(); + var thisHasAnything = _optional.Length > 0; + + // both are empty, equivalent value + if (!thisHasAnything && !otherHasAnything) + return 0; + // we are empty, they are greater value + if (!thisHasAnything) + return -1; + // they are empty, we are greater value + if (!otherHasAnything) + return 1; + // neither are empty, compare wrapped objects directly + if (_optional[0] is IComparable) + return ((IComparable)_optional[0]).CompareTo(other.First()); + + throw new ArgumentException(string.Format( + "Cannot compare objects. Optional type {0} is not comparable to itself", + typeof(T).FullName)); + } + #endregion + + #region Override Equals and GetHashCode + + public override bool Equals(object obj) { - return _maybe.Any() ? _maybe.First().ToString() : ""; + if (ReferenceEquals(this, obj)) + return true; + + var objAsOptional = obj as Optional; + if (objAsOptional != null) + return Equals(objAsOptional); + + if (obj is T) + Equals((T)obj); + + return false; } - public static implicit operator Optional(T value) + public bool Equals(Optional other) { - return new Optional(value); + var otherObj = other.FirstOrDefault(); + var thisObj = _optional.FirstOrDefault(); + if (thisObj == null && otherObj == null) + return true; + if (thisObj == null) + return false; + return thisObj.Equals(otherObj); } - public static Optional FromNullable(TOut? value) where TOut : struct + public override int GetHashCode() { - return value.HasValue - ? new Optional(value.Value) - : new Optional(); + return _optional != null + ? _optional.GetHashCode() + : 0; } + #endregion + + #region Operators + + public static bool operator ==(Optional left, Optional right) + { + return Equals(left, right); + } + + public static bool operator !=(Optional left, Optional right) + { + return !Equals(left, right); + } + #endregion } } diff --git a/mRemoteV1/Tree/ConnectionTreeModel.cs b/mRemoteV1/Tree/ConnectionTreeModel.cs index a4a16065e..502cf36ee 100644 --- a/mRemoteV1/Tree/ConnectionTreeModel.cs +++ b/mRemoteV1/Tree/ConnectionTreeModel.cs @@ -8,7 +8,7 @@ using mRemoteNG.Tree.Root; namespace mRemoteNG.Tree { - public sealed class ConnectionTreeModel : INotifyCollectionChanged, INotifyPropertyChanged + public sealed class ConnectionTreeModel : INotifyCollectionChanged, INotifyPropertyChanged { public List RootNodes { get; } = new List(); diff --git a/mRemoteV1/Tree/Root/RootNodeInfo.cs b/mRemoteV1/Tree/Root/RootNodeInfo.cs index ed9798bbb..47e863a71 100644 --- a/mRemoteV1/Tree/Root/RootNodeInfo.cs +++ b/mRemoteV1/Tree/Root/RootNodeInfo.cs @@ -66,7 +66,9 @@ namespace mRemoteNG.Tree.Root public override TreeNodeType GetTreeNodeType() { - return TreeNodeType.Root; + return Type == RootNodeType.Connection + ? TreeNodeType.Root + : TreeNodeType.PuttyRoot; } #endregion diff --git a/mRemoteV1/Tree/SelectedConnectionDeletionConfirmer.cs b/mRemoteV1/Tree/SelectedConnectionDeletionConfirmer.cs index ade37a24d..5b4c464d9 100644 --- a/mRemoteV1/Tree/SelectedConnectionDeletionConfirmer.cs +++ b/mRemoteV1/Tree/SelectedConnectionDeletionConfirmer.cs @@ -17,6 +17,9 @@ namespace mRemoteNG.Tree public bool Confirm(ConnectionInfo deletionTarget) { + if (deletionTarget == null) + return false; + var deletionTargetAsContainer = deletionTarget as ContainerInfo; if (deletionTargetAsContainer != null) return deletionTargetAsContainer.HasChildren() diff --git a/mRemoteV1/UI/Controls/ConnectionTree/ConnectionTree.cs b/mRemoteV1/UI/Controls/ConnectionTree/ConnectionTree.cs index d73346dbf..91e717a4d 100644 --- a/mRemoteV1/UI/Controls/ConnectionTree/ConnectionTree.cs +++ b/mRemoteV1/UI/Controls/ConnectionTree/ConnectionTree.cs @@ -16,7 +16,7 @@ using mRemoteNG.Tree.Root; namespace mRemoteNG.UI.Controls { - public partial class ConnectionTree : TreeListView, IConnectionTree + public partial class ConnectionTree : TreeListView, IConnectionTree { private readonly ConnectionTreeDragAndDropHandler _dragAndDropHandler = new ConnectionTreeDragAndDropHandler(); private readonly PuttySessionsManager _puttySessionsManager = PuttySessionsManager.Instance; @@ -262,6 +262,9 @@ namespace mRemoteNG.UI.Controls private void AddNode(ConnectionInfo newNode) { + if (SelectedNode?.GetTreeNodeType() == TreeNodeType.PuttyRoot || SelectedNode?.GetTreeNodeType() == TreeNodeType.PuttySession) + return; + // use root node if no node is selected ConnectionInfo parentNode = SelectedNode ?? GetRootConnectionNode(); DefaultConnectionInfo.Instance.SaveTo(newNode); @@ -278,6 +281,13 @@ namespace mRemoteNG.UI.Controls public void DuplicateSelectedNode() { + if (SelectedNode == null) + return; + + var selectedNodeType = SelectedNode.GetTreeNodeType(); + if (selectedNodeType != TreeNodeType.Connection && selectedNodeType != TreeNodeType.Container) + return; + var newNode = SelectedNode.Clone(); SelectedNode.Parent.AddChildBelow(newNode, SelectedNode); newNode.Parent.SetChildBelow(newNode, SelectedNode); @@ -301,17 +311,53 @@ namespace mRemoteNG.UI.Controls if (sortTarget == null) sortTarget = GetRootConnectionNode(); + Runtime.ConnectionsService.BeginBatchingSaves(); + var sortTargetAsContainer = sortTarget as ContainerInfo; if (sortTargetAsContainer != null) sortTargetAsContainer.SortRecursive(sortDirection); else SelectedNode.Parent.SortRecursive(sortDirection); + + Runtime.ConnectionsService.EndBatchingSaves(); + } + + /// + /// Expands all tree objects and recalculates the + /// column widths. + /// + public override void ExpandAll() + { + base.ExpandAll(); + AutoResizeColumn(Columns[0]); + } + + protected override void UpdateFiltering() + { + base.UpdateFiltering(); + AutoResizeColumn(Columns[0]); } private void HandleCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) { - RefreshObject(sender); + // disable filtering if necessary. prevents RefreshObjects from + // throwing an exception + var filteringEnabled = IsFiltering; + var filter = ModelFilter; + if (filteringEnabled) + { + ResetColumnFiltering(); + } + + RefreshObject(sender); AutoResizeColumn(Columns[0]); + + // turn filtering back on + if (filteringEnabled) + { + ModelFilter = filter; + UpdateFiltering(); + } } private void OnMouse_DoubleClick(object sender, MouseEventArgs mouseEventArgs) diff --git a/mRemoteV1/UI/DialogFactory.cs b/mRemoteV1/UI/DialogFactory.cs index 7192acf22..6ffbf1adc 100644 --- a/mRemoteV1/UI/DialogFactory.cs +++ b/mRemoteV1/UI/DialogFactory.cs @@ -1,5 +1,10 @@ -using System.Windows.Forms; +using System; +using System.Collections.Generic; +using System.Windows.Forms; +using mRemoteNG.App; using mRemoteNG.App.Info; +using mRemoteNG.Messages; +using mRemoteNG.UI.TaskDialog; namespace mRemoteNG.UI { @@ -15,5 +20,86 @@ namespace mRemoteNG.UI Filter = Language.strFiltermRemoteXML + @"|*.xml|" + Language.strFilterAll + @"|*.*" }; } + + /// + /// Creates and shows a dialog to either create a new connections file, load a different one, + /// exit, or optionally cancel the operation. + /// + /// + /// + /// + public static void ShowLoadConnectionsFailedDialog(string connectionFileName, string messageText, bool showCancelButton) + { + var commandButtons = new List + { + Language.ConfigurationCreateNew, + Language.strOpenADifferentFile, + Language.strMenuExit + }; + + if (showCancelButton) + commandButtons.Add(Language.strButtonCancel); + + var answered = false; + while (!answered) + { + try + { + CTaskDialog.ShowTaskDialogBox( + GeneralAppInfo.ProductName, + messageText, + "", "", "", "", "", + string.Join(" | ", commandButtons), + ETaskDialogButtons.None, + ESysIcons.Question, + ESysIcons.Question); + + switch (CTaskDialog.CommandButtonResult) + { + case 0: // New + var saveAsDialog = ConnectionsSaveAsDialog(); + saveAsDialog.ShowDialog(); + Runtime.ConnectionsService.NewConnectionsFile(saveAsDialog.FileName); + answered = true; + break; + case 1: // Load + Runtime.LoadConnections(true); + answered = true; + break; + case 2: // Exit + Application.Exit(); + answered = true; + break; + case 3: // Cancel + answered = true; + break; + } + } + catch (Exception exception) + { + Runtime.MessageCollector.AddExceptionMessage( + string.Format(Language.strConnectionsFileCouldNotBeLoadedNew, connectionFileName), + exception, + MessageClass.WarningMsg); + } + } + } + + /// + /// Creates a new dialog that allows the user to select an mRemoteNG + /// connections file path. Don't forget to dispose the dialog when you + /// are done! + /// + public static SaveFileDialog ConnectionsSaveAsDialog() + { + return new SaveFileDialog + { + CheckPathExists = true, + InitialDirectory = ConnectionsFileInfo.DefaultConnectionsPath, + FileName = ConnectionsFileInfo.DefaultConnectionsFile, + OverwritePrompt = true, + Filter = Language.strFiltermRemoteXML + @"|*.xml|" + Language.strFilterAll + @"|*.*" + }; + } } } \ No newline at end of file diff --git a/mRemoteV1/UI/Forms/ExportForm.Designer.cs b/mRemoteV1/UI/Forms/ExportForm.Designer.cs index 5cb23e2e2..b1a51586f 100644 --- a/mRemoteV1/UI/Forms/ExportForm.Designer.cs +++ b/mRemoteV1/UI/Forms/ExportForm.Designer.cs @@ -9,27 +9,27 @@ namespace mRemoteNG.UI.Forms private void InitializeComponent() { - this.btnCancel = new Controls.Base.NGButton(); - this.btnOK = new Controls.Base.NGButton(); - this.lblUncheckProperties = new Controls.Base.NGLabel(); - this.chkUsername = new Controls.Base.NGCheckBox(); - this.chkPassword = new Controls.Base.NGCheckBox(); - this.chkDomain = new Controls.Base.NGCheckBox(); - this.chkInheritance = new Controls.Base.NGCheckBox(); - this.txtFileName = new Controls.Base.NGTextBox(); - this.btnBrowse = new Controls.Base.NGButton(); + this.btnCancel = new mRemoteNG.UI.Controls.Base.NGButton(); + this.btnOK = new mRemoteNG.UI.Controls.Base.NGButton(); + this.lblUncheckProperties = new mRemoteNG.UI.Controls.Base.NGLabel(); + this.chkUsername = new mRemoteNG.UI.Controls.Base.NGCheckBox(); + this.chkPassword = new mRemoteNG.UI.Controls.Base.NGCheckBox(); + this.chkDomain = new mRemoteNG.UI.Controls.Base.NGCheckBox(); + this.chkInheritance = new mRemoteNG.UI.Controls.Base.NGCheckBox(); + this.txtFileName = new mRemoteNG.UI.Controls.Base.NGTextBox(); + this.btnBrowse = new mRemoteNG.UI.Controls.Base.NGButton(); this.grpProperties = new System.Windows.Forms.GroupBox(); - this.chkAssignedCredential = new Controls.Base.NGCheckBox(); + this.chkAssignedCredential = new mRemoteNG.UI.Controls.Base.NGCheckBox(); this.grpFile = new System.Windows.Forms.GroupBox(); - this.lblFileFormat = new Controls.Base.NGLabel(); - this.lblFileName = new Controls.Base.NGLabel(); - this.cboFileFormat = new Controls.Base.NGComboBox(); + this.lblFileFormat = new mRemoteNG.UI.Controls.Base.NGLabel(); + this.lblFileName = new mRemoteNG.UI.Controls.Base.NGLabel(); + this.cboFileFormat = new mRemoteNG.UI.Controls.Base.NGComboBox(); this.grpItems = new System.Windows.Forms.GroupBox(); - this.lblSelectedConnection = new Controls.Base.NGLabel(); - this.lblSelectedFolder = new Controls.Base.NGLabel(); - this.rdoExportSelectedConnection = new Controls.Base.NGRadioButton(); - this.rdoExportSelectedFolder = new Controls.Base.NGRadioButton(); - this.rdoExportEverything = new Controls.Base.NGRadioButton(); + this.lblSelectedConnection = new mRemoteNG.UI.Controls.Base.NGLabel(); + this.lblSelectedFolder = new mRemoteNG.UI.Controls.Base.NGLabel(); + this.rdoExportSelectedConnection = new mRemoteNG.UI.Controls.Base.NGRadioButton(); + this.rdoExportSelectedFolder = new mRemoteNG.UI.Controls.Base.NGRadioButton(); + this.rdoExportEverything = new mRemoteNG.UI.Controls.Base.NGRadioButton(); this.grpProperties.SuspendLayout(); this.grpFile.SuspendLayout(); this.grpItems.SuspendLayout(); @@ -37,6 +37,7 @@ namespace mRemoteNG.UI.Forms // // btnCancel // + this.btnCancel._mice = mRemoteNG.UI.Controls.Base.NGButton.MouseState.HOVER; this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; this.btnCancel.Location = new System.Drawing.Point(447, 473); this.btnCancel.Name = "btnCancel"; @@ -48,6 +49,7 @@ namespace mRemoteNG.UI.Forms // // btnOK // + this.btnOK._mice = mRemoteNG.UI.Controls.Base.NGButton.MouseState.HOVER; this.btnOK.Location = new System.Drawing.Point(366, 473); this.btnOK.Name = "btnOK"; this.btnOK.Size = new System.Drawing.Size(75, 23); @@ -67,6 +69,7 @@ namespace mRemoteNG.UI.Forms // // chkUsername // + this.chkUsername._mice = mRemoteNG.UI.Controls.Base.NGCheckBox.MouseState.HOVER; this.chkUsername.AutoSize = true; this.chkUsername.Checked = true; this.chkUsername.CheckState = System.Windows.Forms.CheckState.Checked; @@ -79,6 +82,7 @@ namespace mRemoteNG.UI.Forms // // chkPassword // + this.chkPassword._mice = mRemoteNG.UI.Controls.Base.NGCheckBox.MouseState.HOVER; this.chkPassword.AutoSize = true; this.chkPassword.Checked = true; this.chkPassword.CheckState = System.Windows.Forms.CheckState.Checked; @@ -91,6 +95,7 @@ namespace mRemoteNG.UI.Forms // // chkDomain // + this.chkDomain._mice = mRemoteNG.UI.Controls.Base.NGCheckBox.MouseState.HOVER; this.chkDomain.AutoSize = true; this.chkDomain.Checked = true; this.chkDomain.CheckState = System.Windows.Forms.CheckState.Checked; @@ -103,6 +108,7 @@ namespace mRemoteNG.UI.Forms // // chkInheritance // + this.chkInheritance._mice = mRemoteNG.UI.Controls.Base.NGCheckBox.MouseState.HOVER; this.chkInheritance.AutoSize = true; this.chkInheritance.Checked = true; this.chkInheritance.CheckState = System.Windows.Forms.CheckState.Checked; @@ -124,6 +130,7 @@ namespace mRemoteNG.UI.Forms // // btnBrowse // + this.btnBrowse._mice = mRemoteNG.UI.Controls.Base.NGButton.MouseState.HOVER; this.btnBrowse.Location = new System.Drawing.Point(417, 46); this.btnBrowse.Name = "btnBrowse"; this.btnBrowse.Size = new System.Drawing.Size(75, 23); @@ -149,6 +156,7 @@ namespace mRemoteNG.UI.Forms // // chkAssignedCredential // + this.chkAssignedCredential._mice = mRemoteNG.UI.Controls.Base.NGCheckBox.MouseState.HOVER; this.chkAssignedCredential.AutoSize = true; this.chkAssignedCredential.Checked = true; this.chkAssignedCredential.CheckState = System.Windows.Forms.CheckState.Checked; @@ -158,6 +166,7 @@ namespace mRemoteNG.UI.Forms this.chkAssignedCredential.TabIndex = 5; this.chkAssignedCredential.Text = "Assigned Credential"; this.chkAssignedCredential.UseVisualStyleBackColor = true; + this.chkAssignedCredential.Visible = false; // // grpFile // @@ -193,7 +202,8 @@ namespace mRemoteNG.UI.Forms // // cboFileFormat // - this.cboFileFormat.DropDownStyle = ComboBoxStyle.DropDownList; + this.cboFileFormat._mice = mRemoteNG.UI.Controls.Base.NGComboBox.MouseState.HOVER; + this.cboFileFormat.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.cboFileFormat.FormattingEnabled = true; this.cboFileFormat.Location = new System.Drawing.Point(15, 100); this.cboFileFormat.Name = "cboFileFormat"; diff --git a/mRemoteV1/UI/Forms/ExportForm.cs b/mRemoteV1/UI/Forms/ExportForm.cs index 8b2e8a78c..7daedde4a 100644 --- a/mRemoteV1/UI/Forms/ExportForm.cs +++ b/mRemoteV1/UI/Forms/ExportForm.cs @@ -226,20 +226,21 @@ namespace mRemoteNG.UI.Forms private void cboFileformat_SelectedIndexChanged(object sender, EventArgs e) { - if (SaveFormat == SaveFormat.mRXML) - { - chkUsername.Enabled = false; - chkPassword.Enabled = false; - chkDomain.Enabled = false; - chkAssignedCredential.Enabled = true; - } - else - { - chkUsername.Enabled = true; - chkPassword.Enabled = true; - chkDomain.Enabled = true; - chkAssignedCredential.Enabled = false; - } + // should only be active if we are using the credential manager feature + //if (SaveFormat == SaveFormat.mRXML) + //{ + // chkUsername.Enabled = false; + // chkPassword.Enabled = false; + // chkDomain.Enabled = false; + // chkAssignedCredential.Enabled = true; + //} + //else + //{ + // chkUsername.Enabled = true; + // chkPassword.Enabled = true; + // chkDomain.Enabled = true; + // chkAssignedCredential.Enabled = false; + //} } #endregion diff --git a/mRemoteV1/UI/Forms/OptionsPages/TabsPanelsPage.Designer.cs b/mRemoteV1/UI/Forms/OptionsPages/TabsPanelsPage.Designer.cs index a5b974f94..238d391c0 100644 --- a/mRemoteV1/UI/Forms/OptionsPages/TabsPanelsPage.Designer.cs +++ b/mRemoteV1/UI/Forms/OptionsPages/TabsPanelsPage.Designer.cs @@ -38,6 +38,9 @@ namespace mRemoteNG.UI.Forms.OptionsPages this.chkShowLogonInfoOnTabs = new mRemoteNG.UI.Controls.Base.NGCheckBox(); this.chkDoubleClickClosesTab = new mRemoteNG.UI.Controls.Base.NGCheckBox(); this.chkShowProtocolOnTabs = new mRemoteNG.UI.Controls.Base.NGCheckBox(); + this.chkCreateEmptyPanelOnStart = new mRemoteNG.UI.Controls.Base.NGCheckBox(); + this.txtBoxPanelName = new mRemoteNG.UI.Controls.Base.NGTextBox(); + this.lblPanelName = new mRemoteNG.UI.Controls.Base.NGLabel(); this.SuspendLayout(); // // chkAlwaysShowPanelTabs @@ -117,10 +120,41 @@ namespace mRemoteNG.UI.Forms.OptionsPages this.chkShowProtocolOnTabs.Text = "Show protocols on tab names"; this.chkShowProtocolOnTabs.UseVisualStyleBackColor = true; // + // chkCreateEmptyPanelOnStart + // + this.chkCreateEmptyPanelOnStart._mice = mRemoteNG.UI.Controls.Base.NGCheckBox.MouseState.HOVER; + this.chkCreateEmptyPanelOnStart.AutoSize = true; + this.chkCreateEmptyPanelOnStart.Location = new System.Drawing.Point(3, 164); + this.chkCreateEmptyPanelOnStart.Name = "chkCreateEmptyPanelOnStart"; + this.chkCreateEmptyPanelOnStart.Size = new System.Drawing.Size(253, 17); + this.chkCreateEmptyPanelOnStart.TabIndex = 7; + this.chkCreateEmptyPanelOnStart.Text = "Create an empty panel when mRemoteNG starts"; + this.chkCreateEmptyPanelOnStart.UseVisualStyleBackColor = true; + this.chkCreateEmptyPanelOnStart.CheckedChanged += new System.EventHandler(this.chkCreateEmptyPanelOnStart_CheckedChanged); + // + // txtBoxPanelName + // + this.txtBoxPanelName.Location = new System.Drawing.Point(43, 200); + this.txtBoxPanelName.Name = "txtBoxPanelName"; + this.txtBoxPanelName.Size = new System.Drawing.Size(213, 20); + this.txtBoxPanelName.TabIndex = 8; + // + // lblPanelName + // + this.lblPanelName.AutoSize = true; + this.lblPanelName.Location = new System.Drawing.Point(40, 184); + this.lblPanelName.Name = "lblPanelName"; + this.lblPanelName.Size = new System.Drawing.Size(66, 13); + this.lblPanelName.TabIndex = 9; + this.lblPanelName.Text = "Panel name:"; + // // TabsPanelsPage // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.lblPanelName); + this.Controls.Add(this.txtBoxPanelName); + this.Controls.Add(this.chkCreateEmptyPanelOnStart); this.Controls.Add(this.chkAlwaysShowPanelTabs); this.Controls.Add(this.chkIdentifyQuickConnectTabs); this.Controls.Add(this.chkOpenNewTabRightOfSelected); @@ -142,6 +176,8 @@ namespace mRemoteNG.UI.Forms.OptionsPages internal Controls.Base.NGCheckBox chkShowLogonInfoOnTabs; internal Controls.Base.NGCheckBox chkDoubleClickClosesTab; internal Controls.Base.NGCheckBox chkShowProtocolOnTabs; - - } + private Controls.Base.NGCheckBox chkCreateEmptyPanelOnStart; + private Controls.Base.NGTextBox txtBoxPanelName; + private Controls.Base.NGLabel lblPanelName; + } } diff --git a/mRemoteV1/UI/Forms/OptionsPages/TabsPanelsPage.cs b/mRemoteV1/UI/Forms/OptionsPages/TabsPanelsPage.cs index ccb4e37fc..6514ec51f 100644 --- a/mRemoteV1/UI/Forms/OptionsPages/TabsPanelsPage.cs +++ b/mRemoteV1/UI/Forms/OptionsPages/TabsPanelsPage.cs @@ -30,6 +30,8 @@ namespace mRemoteNG.UI.Forms.OptionsPages chkIdentifyQuickConnectTabs.Text = Language.strIdentifyQuickConnectTabs; chkDoubleClickClosesTab.Text = Language.strDoubleClickTabClosesIt; chkAlwaysShowPanelSelectionDlg.Text = Language.strAlwaysShowPanelSelection; + chkCreateEmptyPanelOnStart.Text = Language.strCreateEmptyPanelOnStartUp; + lblPanelName.Text = $"{Language.strPanelName}:"; } public override void LoadSettings() @@ -43,6 +45,9 @@ namespace mRemoteNG.UI.Forms.OptionsPages chkIdentifyQuickConnectTabs.Checked = Settings.Default.IdentifyQuickConnectTabs; chkDoubleClickClosesTab.Checked = Settings.Default.DoubleClickOnTabClosesIt; chkAlwaysShowPanelSelectionDlg.Checked = Settings.Default.AlwaysShowPanelSelectionDlg; + chkCreateEmptyPanelOnStart.Checked = Settings.Default.CreateEmptyPanelOnStartUp; + txtBoxPanelName.Text = Settings.Default.StartUpPanelName; + UpdatePanelNameTextBox(); } public override void SaveSettings() @@ -58,8 +63,20 @@ namespace mRemoteNG.UI.Forms.OptionsPages Settings.Default.IdentifyQuickConnectTabs = chkIdentifyQuickConnectTabs.Checked; Settings.Default.DoubleClickOnTabClosesIt = chkDoubleClickClosesTab.Checked; Settings.Default.AlwaysShowPanelSelectionDlg = chkAlwaysShowPanelSelectionDlg.Checked; + Settings.Default.CreateEmptyPanelOnStartUp = chkCreateEmptyPanelOnStart.Checked; + Settings.Default.StartUpPanelName = txtBoxPanelName.Text; Settings.Default.Save(); } + + private void UpdatePanelNameTextBox() + { + txtBoxPanelName.Enabled = chkCreateEmptyPanelOnStart.Checked; + } + + private void chkCreateEmptyPanelOnStart_CheckedChanged(object sender, System.EventArgs e) + { + UpdatePanelNameTextBox(); + } } } \ No newline at end of file diff --git a/mRemoteV1/UI/Forms/PasswordForm.cs b/mRemoteV1/UI/Forms/PasswordForm.cs index 04f678352..eee2902e1 100644 --- a/mRemoteV1/UI/Forms/PasswordForm.cs +++ b/mRemoteV1/UI/Forms/PasswordForm.cs @@ -2,6 +2,7 @@ using System; using System.Security; using System.Windows.Forms; using mRemoteNG.Security; +using mRemoteNG.Tools; namespace mRemoteNG.UI.Forms { @@ -19,12 +20,12 @@ namespace mRemoteNG.UI.Forms Verify = verify; } - public SecureString GetKey() + public Optional GetKey() { var dialog = ShowDialog(); return dialog == DialogResult.OK ? _password - : new SecureString(); + : Optional.Empty; } #region Event Handlers diff --git a/mRemoteV1/UI/Forms/frmMain.Designer.cs b/mRemoteV1/UI/Forms/frmMain.Designer.cs index a282e1d22..bc8fc741e 100644 --- a/mRemoteV1/UI/Forms/frmMain.Designer.cs +++ b/mRemoteV1/UI/Forms/frmMain.Designer.cs @@ -190,7 +190,7 @@ namespace mRemoteNG.UI.Forms this.Controls.Add(this.tsContainer); this.Icon = global::mRemoteNG.Resources.mRemote_Icon; this.MainMenuStrip = this.msMain; - this.MinimumSize = new System.Drawing.Size(1145, 610); + this.MinimumSize = new System.Drawing.Size(400, 400); this.Name = "FrmMain"; this.Opacity = 0D; this.Text = "mRemoteNG"; diff --git a/mRemoteV1/UI/Forms/frmMain.cs b/mRemoteV1/UI/Forms/frmMain.cs index 1aac35aff..f42fdbc93 100644 --- a/mRemoteV1/UI/Forms/frmMain.cs +++ b/mRemoteV1/UI/Forms/frmMain.cs @@ -17,8 +17,8 @@ using mRemoteNG.App.Update; using mRemoteNG.Config; using mRemoteNG.Config.Connections; using mRemoteNG.Config.DatabaseConnectors; +using mRemoteNG.Config.DataProviders; using mRemoteNG.Config.Putty; -using mRemoteNG.Config.Serializers.Xml; using mRemoteNG.Config.Settings; using mRemoteNG.Connection; using mRemoteNG.Connection.Protocol; @@ -40,7 +40,7 @@ using WeifenLuo.WinFormsUI.Docking; namespace mRemoteNG.UI.Forms { - public partial class FrmMain + public partial class FrmMain { private static ClipboardchangeEventHandler _clipboardChangedEvent; private bool _inSizeMove; @@ -70,6 +70,7 @@ namespace mRemoteNG.UI.Forms private readonly AppUpdater _appUpdater; private readonly DatabaseConnectorFactory _databaseConnectorFactory; private readonly Screens _screens; + private readonly FileBackupPruner _backupPruner; private readonly MessageCollector _messageCollector = Runtime.MessageCollector; internal FullscreenHandler Fullscreen { get; set; } @@ -86,7 +87,8 @@ namespace mRemoteNG.UI.Forms var externalToolsService = new ExternalToolsService(); _import = new Import(this); _connectionsService = new ConnectionsService(PuttySessionsManager.Instance, _import, this); - _import.ConnectionsService = _connectionsService; + _backupPruner = new FileBackupPruner(); + _import.ConnectionsService = _connectionsService; Func encryptionKeySelectionFunc = () => _connectionsService.EncryptionKey; _appUpdater = new AppUpdater(encryptionKeySelectionFunc); ExternalToolsTypeConverter.ExternalToolsService = externalToolsService; @@ -109,7 +111,7 @@ namespace mRemoteNG.UI.Forms Func updateWindowBuilder = () => new UpdateWindow(new DockContent(), _shutdown, _appUpdater); _notificationAreaIconBuilder = () => new NotificationAreaIcon(this, _connectionInitiator, _shutdown, _connectionsService); Func externalToolsWindowBuilder = () => new ExternalToolsWindow(_connectionInitiator, externalToolsService, () => connectionTree.SelectedNode, this, _connectionsService); - Func portScanWindowBuilder = () => new PortScanWindow(() => connectionTreeWindow.SelectedNode, _import); + Func portScanWindowBuilder = () => new PortScanWindow(connectionTreeWindow, _import); Func activeDirectoryImportWindowBuilder = () => new ActiveDirectoryImportWindow(() => connectionTreeWindow.SelectedNode, _import, _connectionsService); _databaseConnectorFactory = new DatabaseConnectorFactory(encryptionKeySelectionFunc); _windows = new Windows(_connectionInitiator, connectionTreeWindow, configWindow, errorAndInfoWindow, screenshotManagerWindow, @@ -233,6 +235,7 @@ namespace mRemoteNG.UI.Forms SetDefaultLayout(); _connectionsService.ConnectionsLoaded += ConnectionsServiceOnConnectionsLoaded; + _connectionsService.ConnectionsSaved += ConnectionsServiceOnConnectionsSaved; var credsAndConsSetup = new CredsAndConsSetup(_connectionsService); credsAndConsSetup.LoadCredsAndCons(); @@ -250,7 +253,13 @@ namespace mRemoteNG.UI.Forms Opacity = 1; //Fix missing general panel at the first run - _panelAdder.AddPanel(); + if (Settings.Default.CreateEmptyPanelOnStartUp) + { + var panelName = !string.IsNullOrEmpty(Settings.Default.StartUpPanelName) + ? Settings.Default.StartUpPanelName + : Language.strNewPanel; + _panelAdder.AddPanel(panelName); + } } private void ApplyLanguage() @@ -285,6 +294,14 @@ namespace mRemoteNG.UI.Forms UpdateWindowTitle(); } + private void ConnectionsServiceOnConnectionsSaved(object sender, ConnectionsSavedEventArgs connectionsSavedEventArgs) + { + if (connectionsSavedEventArgs.UsingDatabase) + return; + + _backupPruner.PruneBackupFiles(connectionsSavedEventArgs.ConnectionFileName, Settings.Default.BackupFileKeepCount); + } + private void SetMenuDependencies() { fileMenu.TreeWindow = _windows.TreeForm; diff --git a/mRemoteV1/UI/Menu/MainFileMenu.cs b/mRemoteV1/UI/Menu/MainFileMenu.cs index 5e6887d95..336e31d5c 100644 --- a/mRemoteV1/UI/Menu/MainFileMenu.cs +++ b/mRemoteV1/UI/Menu/MainFileMenu.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Windows.Forms; using mRemoteNG.App; -using mRemoteNG.App.Info; using mRemoteNG.Connection; using mRemoteNG.Container; using mRemoteNG.Security; @@ -103,6 +102,7 @@ namespace mRemoteNG.UI.Menu Size = new System.Drawing.Size(37, 20); Text = Language.strMenuFile; //DropDownOpening += mMenFile_DropDownOpening; + DropDownClosed += OnDropDownClosed; // // mMenFileNewConnection // @@ -352,6 +352,16 @@ namespace mRemoteNG.UI.Menu } } + private void OnDropDownClosed(object sender, EventArgs eventArgs) + { + _mMenFileNewConnection.Enabled = true; + _mMenFileNewFolder.Enabled = true; + _mMenFileDelete.Enabled = true; + _mMenFileRename.Enabled = true; + _mMenFileDuplicate.Enabled = true; + _mMenReconnectAll.Enabled = true; + } + private void mMenFileNewConnection_Click(object sender, EventArgs e) { TreeWindow.ConnectionTree.AddConnection(); @@ -364,13 +374,15 @@ namespace mRemoteNG.UI.Menu private void mMenFileNew_Click(object sender, EventArgs e) { - var saveFileDialog = ConnectionsSaveAsDialog(); - if (saveFileDialog.ShowDialog() != DialogResult.OK) + using (var saveFileDialog = DialogFactory.ConnectionsSaveAsDialog()) { - return; - } + if (saveFileDialog.ShowDialog() != DialogResult.OK) + { + return; + } ConnectionsService.NewConnectionsFile(saveFileDialog.FileName); + } } private void mMenFileLoad_Click(object sender, EventArgs e) @@ -399,15 +411,11 @@ namespace mRemoteNG.UI.Menu private void mMenFileSaveAs_Click(object sender, EventArgs e) { - using (var saveFileDialog = new SaveFileDialog()) + using (var saveFileDialog = DialogFactory.ConnectionsSaveAsDialog()) { - saveFileDialog.CheckPathExists = true; - saveFileDialog.InitialDirectory = ConnectionsFileInfo.DefaultConnectionsPath; - saveFileDialog.FileName = ConnectionsFileInfo.DefaultConnectionsFile; - saveFileDialog.OverwritePrompt = true; - saveFileDialog.Filter = $@"{Language.strFiltermRemoteXML}|*.xml|{Language.strFilterAll}|*.*"; + if (saveFileDialog.ShowDialog(FrmMain.Default) != DialogResult.OK) + return; - if (saveFileDialog.ShowDialog(DialogWindowParent) != DialogResult.OK) return; var newFileName = saveFileDialog.FileName; ConnectionsService.SaveConnections(ConnectionsService.ConnectionTreeModel, false, new SaveFilter(), newFileName); @@ -501,18 +509,6 @@ namespace mRemoteNG.UI.Menu { Shutdown.Quit(); } - - public static SaveFileDialog ConnectionsSaveAsDialog() - { - return new SaveFileDialog - { - CheckPathExists = true, - InitialDirectory = ConnectionsFileInfo.DefaultConnectionsPath, - FileName = ConnectionsFileInfo.DefaultConnectionsFile, - OverwritePrompt = true, - Filter = Language.strFiltermRemoteXML + @"|*.xml|" + Language.strFilterAll + @"|*.*" - }; - } #endregion } } \ No newline at end of file diff --git a/mRemoteV1/UI/Window/ConfigWindow.cs b/mRemoteV1/UI/Window/ConfigWindow.cs index 95e719ad2..2e77d2f3d 100644 --- a/mRemoteV1/UI/Window/ConfigWindow.cs +++ b/mRemoteV1/UI/Window/ConfigWindow.cs @@ -24,7 +24,7 @@ using WeifenLuo.WinFormsUI.Docking; namespace mRemoteNG.UI.Window { - public class ConfigWindow : BaseWindow + public class ConfigWindow : BaseWindow { private bool _originalPropertyGridToolStripItemCountValid; private int _originalPropertyGridToolStripItemCount; @@ -740,29 +740,32 @@ namespace mRemoteNG.UI.Window private void UpdateRootInfoNode(PropertyValueChangedEventArgs e) { var rootInfo = _pGrid.SelectedObject as RootNodeInfo; - if (rootInfo == null) return; - if (e.ChangedItem.PropertyDescriptor == null) return; - // ReSharper disable once SwitchStatementMissingSomeCases - switch (e.ChangedItem.PropertyDescriptor.Name) - { - case "Password": - if (rootInfo.Password) - { - var passwordName = Settings.Default.UseSQLServer ? Language.strSQLServer.TrimEnd(':') : Path.GetFileName(_connectionsService.GetStartupConnectionFileName()); + if (rootInfo == null) + return; - var password = MiscTools.PasswordDialog(passwordName); - if (password.Length == 0) - rootInfo.Password = false; - else - rootInfo.PasswordString = password.ConvertToUnsecureString(); - } - else - { - rootInfo.PasswordString = ""; - } - break; - case "Name": - break; + if (e.ChangedItem.PropertyDescriptor?.Name != "Password") + return; + + if (rootInfo.Password) + { + var passwordName = Settings.Default.UseSQLServer + ? Language.strSQLServer.TrimEnd(':') + : Path.GetFileName(_connectionsService.GetStartupConnectionFileName()); + + var password = MiscTools.PasswordDialog(passwordName); + + // operation cancelled, dont set a password + if (!password.Any() || password.First().Length == 0) + { + rootInfo.Password = false; + return; + } + + rootInfo.PasswordString = password.First().ConvertToUnsecureString(); + } + else + { + rootInfo.PasswordString = ""; } } diff --git a/mRemoteV1/UI/Window/ConnectionTreeWindow.cs b/mRemoteV1/UI/Window/ConnectionTreeWindow.cs index 4a060d6f8..91a8da80e 100644 --- a/mRemoteV1/UI/Window/ConnectionTreeWindow.cs +++ b/mRemoteV1/UI/Window/ConnectionTreeWindow.cs @@ -15,7 +15,7 @@ using WeifenLuo.WinFormsUI.Docking; namespace mRemoteNG.UI.Window { - public partial class ConnectionTreeWindow + public partial class ConnectionTreeWindow { private readonly IConnectionInitiator _connectionInitiator; private readonly IConnectionsService _connectionsService; @@ -44,11 +44,22 @@ namespace mRemoteNG.UI.Window InitializeComponent(); SetMenuEventHandlers(); SetConnectionTreeEventHandlers(); - Settings.Default.PropertyChanged += (sender, args) => SetConnectionTreeEventHandlers(); + Settings.Default.PropertyChanged += OnAppSettingsChanged; olvConnections.ModelFilter = _connectionTreeSearchTextFilter; } + private void OnAppSettingsChanged(object o, PropertyChangedEventArgs propertyChangedEventArgs) + { + if (propertyChangedEventArgs.PropertyName == nameof(Settings.UseFilterSearch)) + { + ConnectionTree.UseFiltering = Settings.Default.UseFilterSearch; + ApplyFiltering(); + } + + SetConnectionTreeEventHandlers(); + } - #region Form Stuff + + #region Form Stuff private void Tree_Load(object sender, EventArgs e) { ApplyLanguage(); @@ -100,8 +111,6 @@ namespace mRemoteNG.UI.Window private void SetConnectionTreeEventHandlers() { olvConnections.NodeDeletionConfirmer = new SelectedConnectionDeletionConfirmer(MessageBox.Show); - olvConnections.BeforeLabelEdit += tvConnections_BeforeLabelEdit; - olvConnections.AfterLabelEdit += tvConnections_AfterLabelEdit; olvConnections.KeyDown += tvConnections_KeyDown; olvConnections.KeyPress += tvConnections_KeyPress; SetTreePostSetupActions(); @@ -177,24 +186,6 @@ namespace mRemoteNG.UI.Window { olvConnections.AddFolder(); } - - private void tvConnections_BeforeLabelEdit(object sender, LabelEditEventArgs e) - { - ConnectionTreeContextMenu.DisableShortcutKeys(); - } - - private void tvConnections_AfterLabelEdit(object sender, LabelEditEventArgs e) - { - try - { - ConnectionTreeContextMenu.EnableShortcutKeys(); - ConnectionTree.ConnectionTreeModel.RenameNode(SelectedNode, e.Label); - } - catch (Exception ex) - { - Runtime.MessageCollector.AddExceptionStackTrace("tvConnections_AfterLabelEdit (UI.Window.ConnectionTreeWindow) failed", ex); - } - } #endregion #region Search @@ -243,25 +234,30 @@ namespace mRemoteNG.UI.Window } private void txtSearch_TextChanged(object sender, EventArgs e) - { - if (Settings.Default.UseFilterSearch) - { - if (txtSearch.Text == "" || txtSearch.Text == Language.strSearchPrompt) - { - olvConnections.UseFiltering = false; - olvConnections.ResetColumnFiltering(); - return; - } - olvConnections.UseFiltering = true; - _connectionTreeSearchTextFilter.FilterText = txtSearch.Text; - olvConnections.ModelFilter = _connectionTreeSearchTextFilter; - } - else - { - if (txtSearch.Text == "") return; - olvConnections.NodeSearcher?.SearchByName(txtSearch.Text); - JumpToNode(olvConnections.NodeSearcher?.CurrentMatch); - } + { + ApplyFiltering(); + } + + private void ApplyFiltering() + { + if (Settings.Default.UseFilterSearch) + { + if (txtSearch.Text == "" || txtSearch.Text == Language.strSearchPrompt) + { + olvConnections.UseFiltering = false; + olvConnections.ResetColumnFiltering(); + return; + } + olvConnections.UseFiltering = true; + _connectionTreeSearchTextFilter.FilterText = txtSearch.Text; + olvConnections.ModelFilter = _connectionTreeSearchTextFilter; + } + else + { + if (txtSearch.Text == "") return; + olvConnections.NodeSearcher?.SearchByName(txtSearch.Text); + JumpToNode(olvConnections.NodeSearcher?.CurrentMatch); + } } private void JumpToNode(ConnectionInfo connectionInfo) diff --git a/mRemoteV1/UI/Window/HelpWindow.cs b/mRemoteV1/UI/Window/HelpWindow.cs index 1d961b978..6310223cb 100644 --- a/mRemoteV1/UI/Window/HelpWindow.cs +++ b/mRemoteV1/UI/Window/HelpWindow.cs @@ -10,47 +10,48 @@ namespace mRemoteNG.UI.Window { #region Form Init - internal TreeView tvIndex; + + private TreeView tvIndex; internal ImageList imgListHelp; private System.ComponentModel.Container components; - internal SplitContainer pnlSplitter; - internal Label lblDocName; - internal WebBrowser wbHelp; + private SplitContainer pnlSplitter; + private Label lblDocName; + private WebBrowser wbHelp; private void InitializeComponent() { components = new System.ComponentModel.Container(); - Load += new EventHandler(Help_Load); - Shown += new EventHandler(Help_Shown); - TreeNode TreeNode1 = new TreeNode("Introduction"); - TreeNode TreeNode2 = new TreeNode("Prerequisites"); - TreeNode TreeNode3 = new TreeNode("Installation"); - TreeNode TreeNode4 = new TreeNode("Configuration"); - TreeNode TreeNode5 = new TreeNode("SQL Configuration"); - TreeNode TreeNode6 = new TreeNode("Command-Line Switches"); - TreeNode TreeNode7 = new TreeNode("Getting Started", new[] {TreeNode2, TreeNode3, TreeNode4, TreeNode5, TreeNode6}); - TreeNode TreeNode8 = new TreeNode("Main Menu"); - TreeNode TreeNode9 = new TreeNode("Connections"); - TreeNode TreeNode10 = new TreeNode("Config"); - TreeNode TreeNode11 = new TreeNode("Errors and Infos"); - TreeNode TreeNode12 = new TreeNode("Save As / Export"); - TreeNode TreeNode14 = new TreeNode("Screenshot Manager"); - TreeNode TreeNode15 = new TreeNode("Connection"); - TreeNode TreeNode16 = new TreeNode("Options"); - TreeNode TreeNode17 = new TreeNode("Update"); - TreeNode TreeNode18 = new TreeNode("SSH File Transfer"); - TreeNode TreeNode19 = new TreeNode("Quick Connect"); - TreeNode TreeNode20 = new TreeNode("Import From Active Directory"); - TreeNode TreeNode21 = new TreeNode("External Applications"); - TreeNode TreeNode22 = new TreeNode("Port Scan"); - TreeNode TreeNode23 = new TreeNode("User Interface", new[] {TreeNode8, TreeNode9, TreeNode10, TreeNode11, TreeNode12, TreeNode14, TreeNode15, TreeNode16, TreeNode17, TreeNode18, TreeNode19, TreeNode20, TreeNode21, TreeNode22}); - TreeNode TreeNode24 = new TreeNode("Quick Reference"); - TreeNode TreeNode25 = new TreeNode("Help", new[] {TreeNode1, TreeNode7, TreeNode23, TreeNode24}); + Load += Help_Load; + Shown += Help_Shown; + var TreeNode1 = new TreeNode("Introduction"); + var TreeNode2 = new TreeNode("Prerequisites"); + var TreeNode3 = new TreeNode("Installation"); + var TreeNode4 = new TreeNode("Configuration"); + var TreeNode5 = new TreeNode("SQL Configuration"); + var TreeNode6 = new TreeNode("Command-Line Switches"); + var TreeNode7 = new TreeNode("Getting Started", new[] {TreeNode2, TreeNode3, TreeNode4, TreeNode5, TreeNode6}); + var TreeNode8 = new TreeNode("Main Menu"); + var TreeNode9 = new TreeNode("Connections"); + var TreeNode10 = new TreeNode("Config"); + var TreeNode11 = new TreeNode("Errors and Infos"); + var TreeNode12 = new TreeNode("Save As / Export"); + var TreeNode14 = new TreeNode("Screenshot Manager"); + var TreeNode15 = new TreeNode("Connection"); + var TreeNode16 = new TreeNode("Options"); + var TreeNode17 = new TreeNode("Update"); + var TreeNode18 = new TreeNode("SSH File Transfer"); + var TreeNode19 = new TreeNode("Quick Connect"); + var TreeNode20 = new TreeNode("Import From Active Directory"); + var TreeNode21 = new TreeNode("External Applications"); + var TreeNode22 = new TreeNode("Port Scan"); + var TreeNode23 = new TreeNode("User Interface", new[] {TreeNode8, TreeNode9, TreeNode10, TreeNode11, TreeNode12, TreeNode14, TreeNode15, TreeNode16, TreeNode17, TreeNode18, TreeNode19, TreeNode20, TreeNode21, TreeNode22}); + var TreeNode24 = new TreeNode("Quick Reference"); + var TreeNode25 = new TreeNode("Help", new[] {TreeNode1, TreeNode7, TreeNode23, TreeNode24}); wbHelp = new WebBrowser(); - wbHelp.DocumentTitleChanged += new EventHandler(wbHelp_DocumentTitleChanged); + wbHelp.DocumentTitleChanged += wbHelp_DocumentTitleChanged; tvIndex = new TreeView(); - tvIndex.NodeMouseClick += new TreeNodeMouseClickEventHandler(tvIndex_NodeMouseClick); - tvIndex.AfterSelect += new TreeViewEventHandler(tvIndex_AfterSelect); + tvIndex.NodeMouseClick += tvIndex_NodeMouseClick; + tvIndex.AfterSelect += tvIndex_AfterSelect; imgListHelp = new ImageList(components); pnlSplitter = new SplitContainer(); lblDocName = new Label(); @@ -80,75 +81,27 @@ namespace mRemoteNG.UI.Window tvIndex.HideSelection = false; tvIndex.Location = new System.Drawing.Point(1, 1); tvIndex.Name = "tvIndex"; - TreeNode1.Name = "Node0"; TreeNode1.Tag = "Introduction"; - TreeNode1.Text = "Introduction"; - TreeNode2.Name = "Node0"; TreeNode2.Tag = "Prerequisites"; - TreeNode2.Text = "Prerequisites"; - TreeNode3.Name = "Node3"; TreeNode3.Tag = "Installation"; - TreeNode3.Text = "Installation"; - TreeNode4.Name = "Node4"; TreeNode4.Tag = "Configuration"; - TreeNode4.Text = "Configuration"; - TreeNode5.Name = "Node0"; TreeNode5.Tag = "ConfigurationSQL"; - TreeNode5.Text = "SQL Configuration"; - TreeNode6.Name = "Node5"; TreeNode6.Tag = "CMDSwitches"; - TreeNode6.Text = "Command-Line Switches"; - TreeNode7.Name = "Node1"; - TreeNode7.Text = "Getting Started"; - TreeNode8.Name = "Node7"; TreeNode8.Tag = "MainMenu"; - TreeNode8.Text = "Main Menu"; - TreeNode9.Name = "Node8"; TreeNode9.Tag = "Connections"; - TreeNode9.Text = "Connections"; - TreeNode10.Name = "Node9"; TreeNode10.Tag = "Config"; - TreeNode10.Text = "Config"; - TreeNode11.Name = "Node10"; TreeNode11.Tag = "ErrorsAndInfos"; - TreeNode11.Text = "Errors and Infos"; - TreeNode12.Name = "Node11"; TreeNode12.Tag = "SaveAsExport"; - TreeNode12.Text = "Save As / Export"; - TreeNode14.Name = "Node13"; TreeNode14.Tag = "ScreenshotManager"; - TreeNode14.Text = "Screenshot Manager"; - TreeNode15.Name = "Node14"; TreeNode15.Tag = "Connection"; - TreeNode15.Text = "Connection"; - TreeNode16.Name = "Node15"; TreeNode16.Tag = "Options"; - TreeNode16.Text = "Options"; - TreeNode17.Name = "Node16"; TreeNode17.Tag = "Update"; - TreeNode17.Text = "Update"; - TreeNode18.Name = "Node17"; TreeNode18.Tag = "SSHFileTransfer"; - TreeNode18.Text = "SSH File Transfer"; - TreeNode19.Name = "Node18"; TreeNode19.Tag = "QuickConnect"; - TreeNode19.Text = "Quick Connect"; - TreeNode20.Name = "Node19"; TreeNode20.Tag = "ImportFromAD"; - TreeNode20.Text = "Import From Active Directory"; - TreeNode21.Name = "Node1"; TreeNode21.Tag = "ExternalTools"; - TreeNode21.Text = "External Tools"; - TreeNode22.Name = "Node0"; TreeNode22.Tag = "PortScan"; - TreeNode22.Text = "Port Scan"; - TreeNode23.Name = "Node6"; - TreeNode23.Text = "User Interface"; - TreeNode24.Name = "Node20"; TreeNode24.Tag = "QuickReference"; - TreeNode24.Text = "Quick Reference"; - TreeNode25.Name = "Node0"; - TreeNode25.Text = "Help"; TreeNode25.Tag = "Index"; tvIndex.Nodes.AddRange(new[] {TreeNode25}); tvIndex.ShowRootLines = false; @@ -193,7 +146,7 @@ namespace mRemoteNG.UI.Window lblDocName.Name = "lblDocName"; lblDocName.Size = new System.Drawing.Size(327, 35); lblDocName.TabIndex = 2; - lblDocName.Text = "Introduction"; + lblDocName.Text = @"Introduction"; lblDocName.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // //Help @@ -201,9 +154,8 @@ namespace mRemoteNG.UI.Window ClientSize = new System.Drawing.Size(542, 323); Controls.Add(pnlSplitter); Icon = Resources.Help_Icon; - Name = "Help"; - TabText = "Help"; - Text = "Help"; + TabText = @"Help"; + Text = @"Help"; pnlSplitter.Panel1.ResumeLayout(false); pnlSplitter.Panel2.ResumeLayout(false); pnlSplitter.ResumeLayout(false); @@ -245,7 +197,7 @@ namespace mRemoteNG.UI.Window private void tvIndex_AfterSelect(object sender, TreeViewEventArgs e) { - if ((string)e.Node.Tag != "" && e.Node.Tag != null) + if (!string.IsNullOrEmpty((string)e.Node.Tag)) { wbHelp.Navigate(GeneralAppInfo.HomePath + "\\Help\\" + Convert.ToString(e.Node.Tag) +".htm"); } @@ -263,7 +215,7 @@ namespace mRemoteNG.UI.Window imgListHelp.Images.Add("Help", Resources.Help); } - private void SetImages(TreeNode node) + private static void SetImages(TreeNode node) { node.ImageIndex = 2; node.SelectedImageIndex = 2; diff --git a/mRemoteV1/UI/Window/PortScanWindow.Designer.cs b/mRemoteV1/UI/Window/PortScanWindow.Designer.cs index e7c96a566..465fd1834 100644 --- a/mRemoteV1/UI/Window/PortScanWindow.Designer.cs +++ b/mRemoteV1/UI/Window/PortScanWindow.Designer.cs @@ -88,7 +88,7 @@ namespace mRemoteNG.UI.Window this.ipStart.Location = new System.Drawing.Point(5, 19); this.ipStart.Name = "ipStart"; this.ipStart.Size = new System.Drawing.Size(130, 20); - this.ipStart.TabIndex = 10; + this.ipStart.TabIndex = 1; this.ipStart.ToolTipText = ""; // // ipEnd @@ -96,7 +96,7 @@ namespace mRemoteNG.UI.Window this.ipEnd.Location = new System.Drawing.Point(155, 19); this.ipEnd.Name = "ipEnd"; this.ipEnd.Size = new System.Drawing.Size(130, 20); - this.ipEnd.TabIndex = 15; + this.ipEnd.TabIndex = 2; this.ipEnd.ToolTipText = ""; // // lblStartIP @@ -126,7 +126,7 @@ namespace mRemoteNG.UI.Window this.btnScan.Location = new System.Drawing.Point(769, 5); this.btnScan.Name = "btnScan"; this.btnScan.Size = new System.Drawing.Size(110, 55); - this.btnScan.TabIndex = 20; + this.btnScan.TabIndex = 6; this.btnScan.Text = "&Scan"; this.btnScan.TextImageRelation = System.Windows.Forms.TextImageRelation.ImageBeforeText; this.btnScan.UseVisualStyleBackColor = true; @@ -222,7 +222,7 @@ namespace mRemoteNG.UI.Window this.btnImport.Location = new System.Drawing.Point(800, 5); this.btnImport.Name = "btnImport"; this.btnImport.Size = new System.Drawing.Size(80, 40); - this.btnImport.TabIndex = 101; + this.btnImport.TabIndex = 8; this.btnImport.Text = "&Import"; this.btnImport.UseVisualStyleBackColor = true; this.btnImport.Click += new System.EventHandler(this.btnImport_Click); @@ -244,7 +244,7 @@ namespace mRemoteNG.UI.Window this.cbProtocol.Location = new System.Drawing.Point(5, 25); this.cbProtocol.Name = "cbProtocol"; this.cbProtocol.Size = new System.Drawing.Size(122, 21); - this.cbProtocol.TabIndex = 28; + this.cbProtocol.TabIndex = 7; // // lblOnlyImport // @@ -360,7 +360,7 @@ namespace mRemoteNG.UI.Window 0}); this.numericSelectorTimeout.Name = "numericSelectorTimeout"; this.numericSelectorTimeout.Size = new System.Drawing.Size(67, 22); - this.numericSelectorTimeout.TabIndex = 17; + this.numericSelectorTimeout.TabIndex = 5; // // lblTimeout // @@ -381,7 +381,7 @@ namespace mRemoteNG.UI.Window 0}); this.portEnd.Name = "portEnd"; this.portEnd.Size = new System.Drawing.Size(67, 22); - this.portEnd.TabIndex = 15; + this.portEnd.TabIndex = 4; this.portEnd.Enter += new System.EventHandler(this.portEnd_Enter); // // portStart @@ -394,7 +394,7 @@ namespace mRemoteNG.UI.Window 0}); this.portStart.Name = "portStart"; this.portStart.Size = new System.Drawing.Size(67, 22); - this.portStart.TabIndex = 5; + this.portStart.TabIndex = 3; this.portStart.Enter += new System.EventHandler(this.portStart_Enter); // // Label2 diff --git a/mRemoteV1/UI/Window/PortScanWindow.cs b/mRemoteV1/UI/Window/PortScanWindow.cs index 942266ebd..5b209bf47 100644 --- a/mRemoteV1/UI/Window/PortScanWindow.cs +++ b/mRemoteV1/UI/Window/PortScanWindow.cs @@ -1,25 +1,26 @@ using System; using System.Collections.Generic; -using System.Windows.Forms; +using System.Linq; using mRemoteNG.App; using mRemoteNG.Connection; using mRemoteNG.Connection.Protocol; using mRemoteNG.Container; using mRemoteNG.Messages; using mRemoteNG.Tools; +using mRemoteNG.Tree.Root; using WeifenLuo.WinFormsUI.Docking; namespace mRemoteNG.UI.Window { - public partial class PortScanWindow + public partial class PortScanWindow { - private readonly Func _getSelectedConnectionFunc; + private readonly ConnectionTreeWindow _connectionTreeWindow; private readonly Import _import; #region Constructors - public PortScanWindow(Func getSelectedConnectionFunc, Import import) + public PortScanWindow(ConnectionTreeWindow connectionTreeWindow, Import import) { - _getSelectedConnectionFunc = getSelectedConnectionFunc; + _connectionTreeWindow = connectionTreeWindow.ThrowIfNull(nameof(connectionTreeWindow)); _import = import.ThrowIfNull(nameof(import)); InitializeComponent(); @@ -136,8 +137,6 @@ namespace mRemoteNG.UI.Window { ProtocolType protocol = (ProtocolType)Enum.Parse(typeof(ProtocolType), Convert.ToString(cbProtocol.SelectedItem), true); importSelectedHosts(protocol); - DialogResult = DialogResult.OK; - Close(); } #endregion @@ -263,11 +262,28 @@ namespace mRemoteNG.UI.Window return; } - var selectedNode = _getSelectedConnectionFunc(); - var selectedTreeNodeAsContainer = selectedNode as ContainerInfo ?? selectedNode.Parent; - _import.ImportFromPortScan(hosts, protocol, selectedTreeNodeAsContainer); + var destinationContainer = GetDestinationContainerForImportedHosts(); + _import.ImportFromPortScan(hosts, protocol, destinationContainer); } + /// + /// Determines where the imported hosts will be placed + /// in the connection tree. + /// + private ContainerInfo GetDestinationContainerForImportedHosts() + { + var selectedNode = _connectionTreeWindow.SelectedNode + ?? _connectionTreeWindow.ConnectionTree.ConnectionTreeModel.RootNodes.OfType().First(); + + // if a putty node is selected, place imported connections in the root connection node + if (selectedNode is RootPuttySessionsNodeInfo || selectedNode is PuttySessionInfo) + selectedNode = _connectionTreeWindow.ConnectionTree.ConnectionTreeModel.RootNodes.OfType().First(); + + // if the selected node is a connection, use its parent container + var selectedTreeNodeAsContainer = selectedNode as ContainerInfo ?? selectedNode.Parent; + return selectedTreeNodeAsContainer; + } + private void importVNCToolStripMenuItem_Click(object sender, EventArgs e) { importSelectedHosts(ProtocolType.VNC); diff --git a/mRemoteV1/app.config b/mRemoteV1/app.config index eec613dc6..2e5fd3f69 100644 --- a/mRemoteV1/app.config +++ b/mRemoteV1/app.config @@ -692,6 +692,12 @@ False + + False + + + General +