diff --git a/mRemoteNG/Connection/Protocol/PowerShell/Connection.Protocol.PowerShell.cs b/mRemoteNG/Connection/Protocol/PowerShell/Connection.Protocol.PowerShell.cs index 6864a7ee..cb8d0da9 100644 --- a/mRemoteNG/Connection/Protocol/PowerShell/Connection.Protocol.PowerShell.cs +++ b/mRemoteNG/Connection/Protocol/PowerShell/Connection.Protocol.PowerShell.cs @@ -41,8 +41,157 @@ namespace mRemoteNG.Connection.Protocol.PowerShell Padding = new Padding(0, 20, 0, 0) }; - _consoleControl.StartProcess(@"C:\Windows\system32\WindowsPowerShell\v1.0\PowerShell.exe", - $@"-NoExit -Command ""$password = ConvertTo-SecureString ""'{_connectionInfo.Password}'"" -AsPlainText -Force; $cred = New-Object System.Management.Automation.PSCredential -ArgumentList @('{_connectionInfo.Domain}\{_connectionInfo.Username}', $password); Enter-PSSession -ComputerName {_connectionInfo.Hostname} -Credential $cred"""); + /* + * Prepair powershell script parameter and create script + */ + // Path to the Windows PowerShell executable; can be configured through options. + string psExe = @"C:\Windows\system32\WindowsPowerShell\v1.0\PowerShell.exe"; + + // Maximum number of login attempts; can be configured through options. + int psLoginAttempts = 3; + + string psUsername; + if (string.IsNullOrEmpty(_connectionInfo.Domain)) + // Set the username without domain + psUsername = _connectionInfo.Username; + else + // Set the username to Domain\Username if Domain is not empty + psUsername = $"{_connectionInfo.Domain}\\{_connectionInfo.Username}"; + + /* + * The PowerShell script is designed to facilitate multiple login attempts to a remote host using user-provided credentials, + * with an option to specify the maximum number of attempts. + * It handles username and password entry, attempts to establish a PSSession, and reports on login outcomes, ensuring a graceful exit in case of repeated failures. + */ + string psScriptBlock = $@" + [CmdletBinding()] + param ( + [Parameter(Mandatory=$true)] + [String] $Hostname, # The hostname you want to connect to (mandatory parameter) + [String] $Username, # The username, if provided + [String] $Password, # The password for authentication + [int] $LoginAttempts = 3 # The number of login attempts, default set to 3 + ) + + # Dynamically parameters + DynamicParam {{ + $RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary; + + # SecurePassword + $ParameterName = 'SecurePassword'; + $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]; + $ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute; + $ParameterAttribute.Mandatory = $False; + $AttributeCollection.Add($ParameterAttribute); + try {{ + # Try converting the stored password to a secure string + $PSBoundParameters.$($ParameterName) = ConvertTo-SecureString $Password -AsPlainText -Force -ErrorAction Stop; + }} + catch{{ + # Create an empty SecureString if the password cannot be converted (if the password is empty) + $PSBoundParameters.$($ParameterName) = [SecureString]::new(); + }} + $PSBoundParameters.Password = $null; + $RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [SecureString], $AttributeCollection); + $RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter); + + return $RuntimeParameterDictionary; + }} + + process {{ + # Initialize the $cred variable + $cred = $null; + + # Check if a username is provided. + # Please note that some logins may not require a password. Therefore, the first attempt can fail if a username is set and a password is not. + if (-not [string]::IsNullOrEmpty($PSBoundParameters.Username)) {{ + # Create a PSCredential object with the provided username and password + $cred = New-Object System.Management.Automation.PSCredential ($PSBoundParameters.Username, $PSBoundParameters.SecurePassword); + + # It will be needed to determine whether the login credentials were provided or not. + $providedCred = $true; + }} + + # At least one login attempt is required to ensure functionality + if ($LoginAttempts -lt 0) {{$LoginAttempts = 1;}} + + # Loop for connection attempts for $LoginAttempts + for ($i = 0; $i -lt $LoginAttempts; $i++) {{ + <# + The cases for when 'Get-Credential' is needed: + 1. `$i -gt 0`: Indicates the first login attempt has failed. + 2. `-not $cred`: Implies that no credentials have been sent to the function. + 3. `$cred -and $cred.UserName -match ""^([^\\]+\\)$""`: Implies that only the regular Windows domain name is parameterized. + + NOTE: + If the regular expression is used in an if statement such as if (.... -match ""^[^\\]+\\$"")..., + there will be conversion problems with the string. This can then lead to errors when executing PowerShell. + + To work around this problem, create the $regex variable and enclose the expression in single quotes. + Due to the use of variables, double quotes are no longer required in the if statement, and it can be written as follows: if (.... -match $Regex).... + This approach avoids possible string conversion problems caused by double quotes. + #> + [string] $regex = '^[^\\]+\\$' + if ($i -gt 0 -or (-not $cred) -or ($cred -and $cred.Username -match $regex)){{ + # Prompt for credentials with a message and pre-fill username if available + try {{ + if (-not [string]::IsNullOrEmpty($cred.UserName)) {{ + $cred = Get-Credential -Message $Hostname -UserName $cred.UserName -ErrorAction Stop; + }} + else {{ + $cred = Get-Credential -Message $Hostname -ErrorAction Stop; + }} + + $providedCred = $false; # provided creds are overwritten + }} + catch {{ + # If something is wrong for $cred + $cred = $null + }} + }} + + # Try PSSession + try {{ + # If credentials are not provided, abort the loop (mean Get-Credential is canceled) + if ( $cred ) {{ + Enter-PSSession -ComputerName $Hostname -Credential $cred -ErrorAction Stop; + break; # Successfully entered PSSession, exit the loop + }} + else {{ + write-Host '{Language.PsCanceled}'; + exit; + }} + }} + # Handle the case when PSSession entry fails + catch [System.Management.Automation.Remoting.PSRemotingTransportException]{{ + If (-not $providedCred) {{ + Write-Host '{Language.PsConnectionFailed}'; + Write-Host; + }} + else {{ + $LoginAttempts++; + }} + }} + catch {{ + # Handle other exceptions + Write-Host $_.Exception.Message; + Write-Host; + Write-Host '{Language.PsFailed}'; + exit; + }} + }} + + # Maximum login attempts reached + if ($i -ge $LoginAttempts) {{ + Write-Host '{Language.PsLoginAttempts}'; + exit; + }} + }} + "; + + // Setup process for script with arguments + //* The -NoProfile parameter would be a valuable addition but should be able to be deactivated. + _consoleControl.StartProcess(psExe, $@"-NoExit -Command ""& {{ {psScriptBlock} }}"" -Hostname ""'{_connectionInfo.Hostname}'"" -Username ""'{psUsername}'"" -Password ""'{_connectionInfo.Password}'"" -LoginAttempts {psLoginAttempts}"); while (!_consoleControl.IsHandleCreated) break; _handle = _consoleControl.Handle; diff --git a/mRemoteNG/Language/Language.Designer.cs b/mRemoteNG/Language/Language.Designer.cs index bbca400d..39bcb24a 100644 --- a/mRemoteNG/Language/Language.Designer.cs +++ b/mRemoteNG/Language/Language.Designer.cs @@ -4513,6 +4513,42 @@ namespace mRemoteNG.Resources.Language { } } + /// + /// Looks up a localized string similar to Login canceled! Restart if necessary.. + /// + internal static string PsCanceled { + get { + return ResourceManager.GetString("PsCanceled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Please verify username and password and try again.If PSRemoting is not enabled on the server, enable it first.. + /// + internal static string PsConnectionFailed { + get { + return ResourceManager.GetString("PsConnectionFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Login failed!. + /// + internal static string PsFailed { + get { + return ResourceManager.GetString("PsFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Maximum login attempts exceeded. Please connect again.. + /// + internal static string PsLoginAttempts { + get { + return ResourceManager.GetString("PsLoginAttempts", resourceCulture); + } + } + /// /// Looks up a localized string similar to Dispose of Putty process failed!. /// diff --git a/mRemoteNG/Language/Language.de.resx b/mRemoteNG/Language/Language.de.resx index 9ffeed4a..8810ce90 100644 --- a/mRemoteNG/Language/Language.de.resx +++ b/mRemoteNG/Language/Language.de.resx @@ -2031,4 +2031,20 @@ Nightly umfasst Alphas, Betas und Release Candidates. Vererbung auf Kinder anwenden + + Anmeldung abgebrochen! Bei bedarf neu einleiten. + C# to Powershell transfer issue with encoding possible + + + Bitte Benutzernamen und Kennwort abgleichen und erneut versuchen. Wenn PSRemoting auf dem Server nicht aktiviert ist, bitte zuerst aktivieren. + C# to Powershell transfer issue with encoding possible + + + Anmeldung fehlgeschlagen! + C# to Powershell transfer issue with encoding possible + + + Maximale Anmeldeversuche erreicht. Verbindung erneut initiieren. + C# to Powershell transfer issue with encoding possible + \ No newline at end of file diff --git a/mRemoteNG/Language/Language.nl.resx b/mRemoteNG/Language/Language.nl.resx index b35f4082..195e808f 100644 --- a/mRemoteNG/Language/Language.nl.resx +++ b/mRemoteNG/Language/Language.nl.resx @@ -59,7 +59,7 @@ : using a System.ComponentModel.TypeConverter : and then encoded with base64 encoding. --> - + @@ -105,17 +105,17 @@ - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - text/microsoft-resx 2.0 + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Over @@ -1572,4 +1572,20 @@ mRemoteNG zal nu worden gesloten en beginnen met de installatie. Ja + + Inloggen geannuleerd! Herstart indien nodig. + C# to Powershell transfer issue with encoding possible + + + Controleer de gebruikersnaam en wachtwoord en probeer het opnieuw. Als PSRemoting niet is ingeschakeld op de server, schakel dit dan eerst in. + C# to Powershell transfer issue with encoding possible + + + Aanmelding mislukt! + C# to Powershell transfer issue with encoding possible + + + Maximaal aantal inlogpogingen overschreden. Maak opnieuw verbinding. + C# to Powershell transfer issue with encoding possible + \ No newline at end of file diff --git a/mRemoteNG/Language/Language.resx b/mRemoteNG/Language/Language.resx index 9c6c99bd..d2cbc20f 100644 --- a/mRemoteNG/Language/Language.resx +++ b/mRemoteNG/Language/Language.resx @@ -2343,4 +2343,20 @@ Nightly Channel includes Alphas, Betas & Release Candidates. Use RD Gateway access token + + Login canceled! Restart if necessary. + C# to Powershell transfer issue with encoding possible + + + Please verify username and password and try again.If PSRemoting is not enabled on the server, enable it first. + C# to Powershell transfer issue with encoding possible + + + Login failed! + C# to Powershell transfer issue with encoding possible + + + Maximum login attempts exceeded. Please connect again. + C# to Powershell transfer issue with encoding possible + \ No newline at end of file