mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Minor improvements to SSH remoting cmdlets (#2249)
* Fix for relative paths for KeyFilePath cmdlet parameter, renamed cmdlet KeyPath parameter to KeyFilePath parameter, and added tests. * Code review comment update * Added missing KeyFilePath check * Test fix from review comment
This commit is contained in:
committed by
Mike Richmond
parent
bf0774a8bf
commit
0ba1e2d9d1
@@ -693,11 +693,11 @@ namespace Microsoft.PowerShell.Commands
|
||||
[Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)]
|
||||
[Parameter(ParameterSetName = InvokeCommandCommand.FilePathSSHHostParameterSet)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
public override string KeyPath
|
||||
public override string KeyFilePath
|
||||
{
|
||||
get { return base.KeyPath; }
|
||||
get { return base.KeyFilePath; }
|
||||
|
||||
set { base.KeyPath = value; }
|
||||
set { base.KeyFilePath = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -683,7 +683,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// <summary>
|
||||
/// SSH Target Host Name
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
|
||||
[Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet, Mandatory = true)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
public virtual string HostName
|
||||
{
|
||||
@@ -694,7 +694,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// <summary>
|
||||
/// SSH User Name
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
|
||||
[Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet, Mandatory = true)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
public virtual string UserName
|
||||
{
|
||||
@@ -703,11 +703,11 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SSH Key Path
|
||||
/// SSH Key File Path
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
public virtual string KeyPath
|
||||
public virtual string KeyFilePath
|
||||
{
|
||||
get;
|
||||
set;
|
||||
@@ -863,6 +863,14 @@ namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
base.BeginProcessing();
|
||||
|
||||
// Validate KeyFilePath parameter.
|
||||
if ((ParameterSetName == PSRemotingBaseCmdlet.SSHHostParameterSet) &&
|
||||
(this.KeyFilePath != null))
|
||||
{
|
||||
// Resolve the key file path when set.
|
||||
this.KeyFilePath = PathResolver.ResolveProviderAndPath(this.KeyFilePath, true, this, false, RemotingErrorIdStrings.FilePathNotFromFileSystemProvider);
|
||||
}
|
||||
|
||||
// Validate IdleTimeout parameter.
|
||||
int idleTimeout = (int)SessionOption.IdleTimeout.TotalMilliseconds;
|
||||
if (idleTimeout != BaseTransportManager.UseServerDefaultIdleTimeout &&
|
||||
@@ -1166,7 +1174,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// </summary>
|
||||
protected void CreateHelpersForSpecifiedHostNames()
|
||||
{
|
||||
var sshConnectionInfo = new SSHConnectionInfo(this.UserName, this.HostName, this.KeyPath);
|
||||
var sshConnectionInfo = new SSHConnectionInfo(this.UserName, this.HostName, this.KeyFilePath);
|
||||
var typeTable = TypeTable.LoadDefaultTypeFiles();
|
||||
var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace;
|
||||
var pipeline = CreatePipeline(remoteRunspace);
|
||||
@@ -1724,8 +1732,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
|
||||
//Resolve file path
|
||||
PathResolver resolver = new PathResolver();
|
||||
string resolvedPath = resolver.ResolveProviderAndPath(filePath, isLiteralPath, this, false, RemotingErrorIdStrings.FilePathNotFromFileSystemProvider);
|
||||
string resolvedPath = PathResolver.ResolveProviderAndPath(filePath, isLiteralPath, this, false, RemotingErrorIdStrings.FilePathNotFromFileSystemProvider);
|
||||
|
||||
//read content of file
|
||||
ExternalScriptInfo scriptInfo = new ExternalScriptInfo(filePath, resolvedPath, this.Context);
|
||||
@@ -3320,10 +3327,12 @@ namespace Microsoft.PowerShell.Commands
|
||||
} // RaiseOperationCompleteEvent
|
||||
} // ExecutionCmdletHelperComputerName
|
||||
|
||||
#region Path Resolver
|
||||
|
||||
/// <summary>
|
||||
/// A helper class to resolve the path
|
||||
/// </summary>
|
||||
internal class PathResolver
|
||||
internal static class PathResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves the specified path and verifies the path belongs to
|
||||
@@ -3336,7 +3345,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// <param name="allowNonexistingPaths"></param>
|
||||
/// <param name="resourceString">resource string for error when path is not from filesystem provider</param>
|
||||
/// <returns>A fully qualified string representing filename.</returns>
|
||||
internal string ResolveProviderAndPath(string path, bool isLiteralPath, PSCmdlet cmdlet, bool allowNonexistingPaths, string resourceString)
|
||||
internal static string ResolveProviderAndPath(string path, bool isLiteralPath, PSCmdlet cmdlet, bool allowNonexistingPaths, string resourceString)
|
||||
{
|
||||
// First resolve path
|
||||
PathInfo resolvedPath = ResolvePath(path, isLiteralPath, allowNonexistingPaths, cmdlet);
|
||||
@@ -3373,7 +3382,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// A string representing the resolved path.
|
||||
/// </returns>
|
||||
///
|
||||
private PathInfo ResolvePath(
|
||||
private static PathInfo ResolvePath(
|
||||
string pathToResolve,
|
||||
bool isLiteralPath,
|
||||
bool allowNonexistingPaths,
|
||||
@@ -3466,5 +3475,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
} // ResolvePath
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion Helper Classes
|
||||
}
|
||||
|
||||
@@ -1248,7 +1248,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// </summary>
|
||||
private RemoteRunspace GetRunspaceForSSHSession()
|
||||
{
|
||||
var sshConnectionInfo = new SSHConnectionInfo(this.UserName, this.HostName, this.KeyPath);
|
||||
var sshConnectionInfo = new SSHConnectionInfo(this.UserName, this.HostName, this.KeyFilePath);
|
||||
var typeTable = TypeTable.LoadDefaultTypeFiles();
|
||||
var remoteRunspace = RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace;
|
||||
remoteRunspace.Open();
|
||||
|
||||
@@ -1065,7 +1065,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
var sshConnectionInfo = new SSHConnectionInfo(
|
||||
this.UserName,
|
||||
this.HostName,
|
||||
this.KeyPath);
|
||||
this.KeyFilePath);
|
||||
var typeTable = TypeTable.LoadDefaultTypeFiles();
|
||||
remoteRunspaces.Add(RunspaceFactory.CreateRunspace(sshConnectionInfo, this.Host, typeTable) as RemoteRunspace);
|
||||
|
||||
|
||||
@@ -1844,9 +1844,9 @@ namespace System.Management.Automation.Runspaces
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Key Path
|
||||
/// Key File Path
|
||||
/// </summary>
|
||||
private string KeyPath
|
||||
private string KeyFilePath
|
||||
{
|
||||
get;
|
||||
set;
|
||||
@@ -1867,18 +1867,18 @@ namespace System.Management.Automation.Runspaces
|
||||
/// </summary>
|
||||
/// <param name="userName">User Name</param>
|
||||
/// <param name="computerName">Computer Name</param>
|
||||
/// <param name="keyPath">Key Path</param>
|
||||
/// <param name="keyFilePath">Key File Path</param>
|
||||
public SSHConnectionInfo(
|
||||
string userName,
|
||||
string computerName,
|
||||
string keyPath)
|
||||
string keyFilePath)
|
||||
{
|
||||
if (userName == null) { throw new PSArgumentNullException("userName"); }
|
||||
if (computerName == null) { throw new PSArgumentNullException("computerName"); }
|
||||
|
||||
this.UserName = userName;
|
||||
this.ComputerName = computerName;
|
||||
this.KeyPath = keyPath;
|
||||
this.KeyFilePath = keyFilePath;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -1930,7 +1930,7 @@ namespace System.Management.Automation.Runspaces
|
||||
SSHConnectionInfo newCopy = new SSHConnectionInfo();
|
||||
newCopy.ComputerName = this.ComputerName;
|
||||
newCopy.UserName = this.UserName;
|
||||
newCopy.KeyPath = this.KeyPath;
|
||||
newCopy.KeyFilePath = this.KeyFilePath;
|
||||
|
||||
return newCopy;
|
||||
}
|
||||
@@ -1996,11 +1996,17 @@ namespace System.Management.Automation.Runspaces
|
||||
// See sshd_configuration file, subsystems section and it will have this entry:
|
||||
// Subsystem powershell C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Version 5.1 -sshs -NoLogo -NoProfile
|
||||
string arguments;
|
||||
if (!string.IsNullOrEmpty(this.KeyPath))
|
||||
if (!string.IsNullOrEmpty(this.KeyFilePath))
|
||||
{
|
||||
if (!System.IO.File.Exists(this.KeyFilePath))
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
StringUtil.Format(RemotingErrorIdStrings.KeyFileNotFound, this.KeyFilePath));
|
||||
}
|
||||
|
||||
arguments = (string.IsNullOrEmpty(domainName)) ?
|
||||
string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" {1}@{2} -s powershell", this.KeyPath, userName, this.ComputerName) :
|
||||
string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" -l {1}@{2} {3} -s powershell", this.KeyPath, userName, domainName, this.ComputerName);
|
||||
string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" {1}@{2} -s powershell", this.KeyFilePath, userName, this.ComputerName) :
|
||||
string.Format(CultureInfo.InvariantCulture, @"-i ""{0}"" -l {1}@{2} {3} -s powershell", this.KeyFilePath, userName, domainName, this.ComputerName);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2094,7 +2100,9 @@ namespace System.Management.Automation.Runspaces
|
||||
(sshProcess == null) ||
|
||||
(sshProcess.HasExited == true))
|
||||
{
|
||||
throw new InvalidOperationException(RemotingErrorIdStrings.CannotStartSSHClient, ex);
|
||||
throw new InvalidOperationException(
|
||||
StringUtil.Format(RemotingErrorIdStrings.CannotStartSSHClient, (ex != null) ? ex.Message : string.Empty),
|
||||
ex);
|
||||
}
|
||||
|
||||
// Create the std in writer/readers needed for communication with ssh.exe.
|
||||
|
||||
@@ -1524,13 +1524,20 @@ namespace System.Management.Automation.Remoting.Client
|
||||
while (true)
|
||||
{
|
||||
string error = reader.ReadLine();
|
||||
if (!string.IsNullOrEmpty(error) && (error.IndexOf("WARNING:", StringComparison.OrdinalIgnoreCase) < 0))
|
||||
if (!string.IsNullOrEmpty(error) && (error.IndexOf("WARNING:", StringComparison.OrdinalIgnoreCase) > -1))
|
||||
{
|
||||
// Handle as interactive warning message.
|
||||
Console.WriteLine(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Any SSH client error results in a broken session.
|
||||
PSRemotingTransportException psrte = new PSRemotingTransportException(
|
||||
PSRemotingErrorId.IPCServerProcessReportedError,
|
||||
RemotingErrorIdStrings.IPCServerProcessReportedError,
|
||||
error);
|
||||
string.IsNullOrEmpty(error) ?
|
||||
RemotingErrorIdStrings.SSHClientEndNoErrorMessage
|
||||
: StringUtil.Format(RemotingErrorIdStrings.SSHClientEndWithErrorMessage, error));
|
||||
RaiseErrorHandler(new TransportErrorOccuredEventArgs(psrte, TransportMethodEnum.CloseShellOperationEx));
|
||||
CloseConnection();
|
||||
}
|
||||
|
||||
@@ -1610,6 +1610,15 @@ All WinRM sessions connected to Windows PowerShell session configurations, such
|
||||
-The domain or computer name was not included with the specified credential, for example: DOMAIN\UserName or COMPUTER\UserName.</value>
|
||||
</data>
|
||||
<data name="CannotStartSSHClient" xml:space="preserve">
|
||||
<value>An error occurred when starting the SSH.exe client needed for the remoting connection.</value>
|
||||
<value>Failed to start the SSH client process needed for the remoting connection with error: {0}.</value>
|
||||
</data>
|
||||
<data name="KeyFileNotFound" xml:space="preserve">
|
||||
<value>The specified key file {0} was not found.</value>
|
||||
</data>
|
||||
<data name="SSHClientEndWithErrorMessage" xml:space="preserve">
|
||||
<value>The SSH client session has ended with error message: {0}</value>
|
||||
</data>
|
||||
<data name="SSHClientEndNoErrorMessage" xml:space="preserve">
|
||||
<value>The SSH client session has ended with no error message.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,41 @@
|
||||
Describe "SSH Remoting Cmdlet Tests" -Tags "Feature" {
|
||||
|
||||
It "Enter-PSSession HostName parameter set should throw error for invalid key path" {
|
||||
|
||||
try
|
||||
{
|
||||
Enter-PSSession -HostName localhost -UserName User -KeyFilePath NoKeyFile
|
||||
throw "Enter-PSSession did not throw expected PathNotFound exception."
|
||||
}
|
||||
catch
|
||||
{
|
||||
$_.FullyQualifiedErrorId | Should Be "PathNotFound,Microsoft.PowerShell.Commands.EnterPSSessionCommand"
|
||||
}
|
||||
}
|
||||
|
||||
It "New-PSSession HostName parameter set should throw error for invalid key path" {
|
||||
|
||||
try
|
||||
{
|
||||
New-PSSession -HostName localhost -UserName User -KeyFilePath NoKeyFile
|
||||
throw "New-PSSession did not throw expected PathNotFound exception."
|
||||
}
|
||||
catch
|
||||
{
|
||||
$_.FullyQualifiedErrorId | Should Be "PathNotFound,Microsoft.PowerShell.Commands.NewPSSessionCommand"
|
||||
}
|
||||
}
|
||||
|
||||
It "Invoke-Command HostName parameter set should throw error for invalid key path" {
|
||||
|
||||
try
|
||||
{
|
||||
Invoke-Command -HostName localhost -UserName User -KeyFilePath NoKeyFile -ScriptBlock {1}
|
||||
throw "Invoke-Command did not throw expected PathNotFound exception."
|
||||
}
|
||||
catch
|
||||
{
|
||||
$_.FullyQualifiedErrorId | Should Be "PathNotFound,Microsoft.PowerShell.Commands.InvokeCommandCommand"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
Describe "SSH Remoting API Tests" -Tags "Feature" {
|
||||
|
||||
Context "SSHConnectionInfo Class Tests" {
|
||||
|
||||
It "SSHConnectionInfo constructor should throw null argument exception for null UserName parameter" {
|
||||
|
||||
try
|
||||
{
|
||||
[System.Management.Automation.Runspaces.SSHConnectionInfo]::new(
|
||||
[System.Management.Automation.Internal.AutomationNull]::Value,
|
||||
"localhost",
|
||||
[System.Management.Automation.Internal.AutomationNull]::Value)
|
||||
|
||||
throw "SSHConnectionInfo constructor did not throw expected PSArgumentNullException exception"
|
||||
}
|
||||
catch
|
||||
{
|
||||
$_.FullyQualifiedErrorId | Should Match "PSArgumentNullException"
|
||||
}
|
||||
}
|
||||
|
||||
It "SSHConnectionInfo constructor should throw null argument exception for null HostName parameter" {
|
||||
|
||||
try
|
||||
{
|
||||
[System.Management.Automation.Runspaces.SSHConnectionInfo]::new(
|
||||
"UserName",
|
||||
[System.Management.Automation.Internal.AutomationNull]::Value,
|
||||
[System.Management.Automation.Internal.AutomationNull]::Value)
|
||||
|
||||
throw "SSHConnectionInfo constructor did not throw expected PSArgumentNullException exception"
|
||||
}
|
||||
catch
|
||||
{
|
||||
$_.FullyQualifiedErrorId | Should Match "PSArgumentNullException"
|
||||
}
|
||||
}
|
||||
|
||||
It "SSHConnectionInfo should throw file not found exception for invalid key file path" {
|
||||
|
||||
try
|
||||
{
|
||||
$sshConnectionInfo = [System.Management.Automation.Runspaces.SSHConnectionInfo]::new(
|
||||
"UserName",
|
||||
"localhost",
|
||||
"NoValidKeyFilePath")
|
||||
|
||||
$rs = [runspacefactory]::CreateRunspace($sshConnectionInfo)
|
||||
$rs.Open()
|
||||
|
||||
throw "SSHConnectionInfo did not throw expected FileNotFoundException exception"
|
||||
}
|
||||
catch
|
||||
{
|
||||
$expectedFileNotFoundExecption = $null
|
||||
if (($_.Exception -ne $null) -and ($_.Exception.InnerException -ne $null))
|
||||
{
|
||||
$expectedFileNotFoundExecption = $_.Exception.InnerException.InnerException
|
||||
}
|
||||
|
||||
($expectedFileNotFoundExecption.GetType().FullName) | Should Be "System.IO.FileNotFoundException"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user