[feature] Add -CustomPipeName to pwsh
and Enter-PSHostProcess
(#8889)
This allows a user to start PowerShell up with the name of the named pipe that is used for cross process communication (I.e. Enter-PSHostProcess).
This commit is contained in:
parent
63acf6812f
commit
23eccfd641
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -77,6 +77,7 @@ Temporary Items
|
|||
|
||||
# TestsResults
|
||||
TestsResults*.xml
|
||||
ParallelXUnitResults.xml
|
||||
|
||||
# Resharper settings
|
||||
PowerShell.sln.DotSettings.user
|
||||
|
|
|
@ -168,6 +168,9 @@ namespace Microsoft.PowerShell
|
|||
|
||||
internal class CommandLineParameterParser
|
||||
{
|
||||
private const int MaxPipePathLengthLinux = 108;
|
||||
private const int MaxPipePathLengthMacOS = 104;
|
||||
|
||||
internal static string[] validParameters = {
|
||||
"version",
|
||||
"nologo",
|
||||
|
@ -189,7 +192,8 @@ namespace Microsoft.PowerShell
|
|||
"settingsfile",
|
||||
"help",
|
||||
"workingdirectory",
|
||||
"removeworkingdirectorytrailingcharacter"
|
||||
"removeworkingdirectorytrailingcharacter",
|
||||
"custompipename"
|
||||
};
|
||||
|
||||
internal CommandLineParameterParser(PSHostUserInterface hostUI, string bannerText, string helpText)
|
||||
|
@ -341,6 +345,14 @@ namespace Microsoft.PowerShell
|
|||
}
|
||||
}
|
||||
|
||||
internal string CustomPipeName
|
||||
{
|
||||
get
|
||||
{
|
||||
return _customPipeName;
|
||||
}
|
||||
}
|
||||
|
||||
internal Serialization.DataFormat OutputFormat
|
||||
{
|
||||
get
|
||||
|
@ -773,6 +785,33 @@ namespace Microsoft.PowerShell
|
|||
|
||||
_configurationName = args[i];
|
||||
}
|
||||
else if (MatchSwitch(switchKey, "custompipename", "cus"))
|
||||
{
|
||||
++i;
|
||||
if (i >= args.Length)
|
||||
{
|
||||
WriteCommandLineError(
|
||||
CommandLineParameterParserStrings.MissingCustomPipeNameArgument);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!Platform.IsWindows)
|
||||
{
|
||||
int maxNameLength = (Platform.IsLinux ? MaxPipePathLengthLinux : MaxPipePathLengthMacOS) - Path.GetTempPath().Length;
|
||||
if (args[i].Length > maxNameLength)
|
||||
{
|
||||
WriteCommandLineError(
|
||||
string.Format(
|
||||
CommandLineParameterParserStrings.CustomPipeNameTooLong,
|
||||
maxNameLength,
|
||||
args[i],
|
||||
args[i].Length));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_customPipeName = args[i];
|
||||
}
|
||||
else if (MatchSwitch(switchKey, "command", "c"))
|
||||
{
|
||||
if (!ParseCommand(args, ref i, noexitSeen, false))
|
||||
|
@ -1375,6 +1414,7 @@ namespace Microsoft.PowerShell
|
|||
private string _helpText;
|
||||
private bool _abortStartup;
|
||||
private bool _skipUserInit;
|
||||
private string _customPipeName;
|
||||
#if STAMODE
|
||||
// Win8: 182409 PowerShell 3.0 should run in STA mode by default
|
||||
// -sta and -mta are mutually exclusive..so tracking them using nullable boolean
|
||||
|
|
|
@ -1369,6 +1369,12 @@ namespace Microsoft.PowerShell
|
|||
}
|
||||
#endif
|
||||
|
||||
// If the debug pipe name was specified, create the custom IPC channel.
|
||||
if (!string.IsNullOrEmpty(cpp.CustomPipeName))
|
||||
{
|
||||
RemoteSessionNamedPipeServer.CreateCustomNamedPipeServer(cpp.CustomPipeName);
|
||||
}
|
||||
|
||||
// NTRAID#Windows Out Of Band Releases-915506-2005/09/09
|
||||
// Removed HandleUnexpectedExceptions infrastructure
|
||||
#if STAMODE
|
||||
|
@ -2884,16 +2890,12 @@ namespace Microsoft.PowerShell
|
|||
/// <summary>
|
||||
/// Constructs RunspaceCreationEventArgs.
|
||||
/// </summary>
|
||||
/// <param name="initialCommand"></param>
|
||||
/// <param name="skipProfiles"></param>
|
||||
/// <param name="staMode"></param>
|
||||
/// <param name="configurationName"></param>
|
||||
/// <param name="initialCommandArgs"></param>
|
||||
internal RunspaceCreationEventArgs(string initialCommand,
|
||||
bool skipProfiles,
|
||||
bool staMode,
|
||||
string configurationName,
|
||||
Collection<CommandParameter> initialCommandArgs)
|
||||
internal RunspaceCreationEventArgs(
|
||||
string initialCommand,
|
||||
bool skipProfiles,
|
||||
bool staMode,
|
||||
string configurationName,
|
||||
Collection<CommandParameter> initialCommandArgs)
|
||||
{
|
||||
InitialCommand = initialCommand;
|
||||
SkipProfiles = skipProfiles;
|
||||
|
|
|
@ -186,6 +186,12 @@
|
|||
<data name="MissingConfigurationNameArgument" xml:space="preserve">
|
||||
<value>Cannot process the command because -Configuration requires an argument that is a remote endpoint configuration name. Specify this argument and try again.</value>
|
||||
</data>
|
||||
<data name="MissingCustomPipeNameArgument" xml:space="preserve">
|
||||
<value>Cannot process the command because -CustomPipeName requires an argument that is a name of the pipe you want to use. Specify this argument and try again.</value>
|
||||
</data>
|
||||
<data name="CustomPipeNameTooLong" xml:space="preserve">
|
||||
<value>Cannot process the command because -CustomPipeName specified is too long. Pipe names on this platform can be up to {0} characters long. Your pipe name '{1}' is {2} characters.</value>
|
||||
</data>
|
||||
<data name="MissingSettingsFileArgument" xml:space="preserve">
|
||||
<value>Cannot process the command because -SettingsFile requires an argument that is a file path.</value>
|
||||
</data>
|
||||
|
|
|
@ -128,7 +128,8 @@ Type 'help' to get help.</value>
|
|||
<value>Usage: pwsh[.exe] [[-File] <filePath> [args]]
|
||||
[-Command { - | <script-block> [-args <arg-array>]
|
||||
| <string> [<CommandParameters>] } ]
|
||||
[-ConfigurationName <string>] [-EncodedCommand <Base64EncodedCommand>]
|
||||
[-ConfigurationName <string>] [-CustomPipeName <string>]
|
||||
[-EncodedCommand <Base64EncodedCommand>]
|
||||
[-ExecutionPolicy <ExecutionPolicy>] [-InputFormat {Text | XML}]
|
||||
[-Interactive] [-NoExit] [-NoLogo] [-NonInteractive] [-NoProfile]
|
||||
[-OutputFormat {Text | XML}] [-Version] [-WindowStyle <style>]
|
||||
|
@ -174,6 +175,17 @@ All parameters are case-insensitive.</value>
|
|||
|
||||
Example: pwsh -ConfigurationName AdminRoles
|
||||
|
||||
-CustomPipeName
|
||||
Specifies the name to use for an additional IPC server (named pipe) used for debugging
|
||||
and other cross-process communication. This offers a predictable mechanism for connecting
|
||||
to other PowerShell instances. Typically used with the CustomPipeName parameter on Enter-PSHostProcess.
|
||||
|
||||
Example:
|
||||
# PowerShell instance 1
|
||||
pwsh -CustomPipeName mydebugpipe
|
||||
# PowerShell instance 2
|
||||
Enter-PSHostProcess -CustomPipeName mydebugpipe
|
||||
|
||||
-EncodedCommand | -e | -ec
|
||||
Accepts a base64 encoded string version of a command. Use this parameter to submit
|
||||
commands to PowerShell that require complex quotation marks or curly braces.
|
||||
|
|
|
@ -13,6 +13,7 @@ using System.Management.Automation.Host;
|
|||
using System.Management.Automation.Internal;
|
||||
using System.Management.Automation.Remoting;
|
||||
using System.Management.Automation.Runspaces;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
|
@ -37,6 +38,7 @@ namespace Microsoft.PowerShell.Commands
|
|||
private const string ProcessParameterSet = "ProcessParameterSet";
|
||||
private const string ProcessNameParameterSet = "ProcessNameParameterSet";
|
||||
private const string ProcessIdParameterSet = "ProcessIdParameterSet";
|
||||
private const string PipeNameParameterSet = "PipePipeNameParameterSet";
|
||||
private const string PSHostProcessInfoParameterSet = "PSHostProcessInfoParameterSet";
|
||||
|
||||
private const string NamedPipeRunspaceName = "PSAttachRunspace";
|
||||
|
@ -91,6 +93,16 @@ namespace Microsoft.PowerShell.Commands
|
|||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom named pipe name to connect to. This is usually used in conjunction with `pwsh -CustomPipeName`.
|
||||
/// </summary>
|
||||
[Parameter(Mandatory = true, ParameterSetName = EnterPSHostProcessCommand.PipeNameParameterSet)]
|
||||
public string CustomPipeName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional name of AppDomain in process to enter. If not specified then the default AppDomain is used.
|
||||
/// </summary>
|
||||
|
@ -129,26 +141,35 @@ namespace Microsoft.PowerShell.Commands
|
|||
}
|
||||
|
||||
// Check selected process for existence, and whether it hosts PowerShell.
|
||||
Runspace namedPipeRunspace = null;
|
||||
switch (ParameterSetName)
|
||||
{
|
||||
case ProcessIdParameterSet:
|
||||
Process = GetProcessById(Id);
|
||||
VerifyProcess(Process);
|
||||
namedPipeRunspace = CreateNamedPipeRunspace(Process.Id, AppDomainName);
|
||||
break;
|
||||
|
||||
case ProcessNameParameterSet:
|
||||
Process = GetProcessByName(Name);
|
||||
VerifyProcess(Process);
|
||||
namedPipeRunspace = CreateNamedPipeRunspace(Process.Id, AppDomainName);
|
||||
break;
|
||||
|
||||
case PSHostProcessInfoParameterSet:
|
||||
Process = GetProcessByHostProcessInfo(HostProcessInfo);
|
||||
VerifyProcess(Process);
|
||||
|
||||
// Create named pipe runspace for selected process and open.
|
||||
namedPipeRunspace = CreateNamedPipeRunspace(Process.Id, AppDomainName);
|
||||
break;
|
||||
|
||||
case PipeNameParameterSet:
|
||||
VerifyPipeName(CustomPipeName);
|
||||
namedPipeRunspace = CreateNamedPipeRunspace(CustomPipeName);
|
||||
break;
|
||||
}
|
||||
|
||||
VerifyProcess(Process);
|
||||
|
||||
// Create named pipe runspace for selected process and open.
|
||||
Runspace namedPipeRunspace = CreateNamedPipeRunspace(Process.Id, AppDomainName);
|
||||
|
||||
// Set runspace prompt. The runspace is closed on pop so we don't
|
||||
// have to reverse this change.
|
||||
PrepareRunspace(namedPipeRunspace);
|
||||
|
@ -187,9 +208,20 @@ namespace Microsoft.PowerShell.Commands
|
|||
|
||||
#region Private Methods
|
||||
|
||||
private Runspace CreateNamedPipeRunspace(string customPipeName)
|
||||
{
|
||||
NamedPipeConnectionInfo connectionInfo = new NamedPipeConnectionInfo(customPipeName);
|
||||
return CreateNamedPipeRunspace(connectionInfo);
|
||||
}
|
||||
|
||||
private Runspace CreateNamedPipeRunspace(int procId, string appDomainName)
|
||||
{
|
||||
NamedPipeConnectionInfo connectionInfo = new NamedPipeConnectionInfo(procId, appDomainName);
|
||||
return CreateNamedPipeRunspace(connectionInfo);
|
||||
}
|
||||
|
||||
private Runspace CreateNamedPipeRunspace(NamedPipeConnectionInfo connectionInfo)
|
||||
{
|
||||
TypeTable typeTable = TypeTable.LoadDefaultTypeFiles();
|
||||
RemoteRunspace remoteRunspace = RunspaceFactory.CreateRunspace(connectionInfo, this.Host, typeTable) as RemoteRunspace;
|
||||
remoteRunspace.Name = NamedPipeRunspaceName;
|
||||
|
@ -203,20 +235,40 @@ namespace Microsoft.PowerShell.Commands
|
|||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
string msgAppDomainName = (!string.IsNullOrEmpty(appDomainName)) ? appDomainName : NamedPipeUtils.DefaultAppDomainName;
|
||||
|
||||
// Unwrap inner exception for original error message, if any.
|
||||
string errorMessage = (e.InnerException != null) ? (e.InnerException.Message ?? string.Empty) : string.Empty;
|
||||
|
||||
ThrowTerminatingError(
|
||||
new ErrorRecord(
|
||||
new RuntimeException(
|
||||
StringUtil.Format(RemotingErrorIdStrings.EnterPSHostProcessCannotConnectToProcess,
|
||||
msgAppDomainName, procId, errorMessage),
|
||||
e.InnerException),
|
||||
"EnterPSHostProcessCannotConnectToProcess",
|
||||
ErrorCategory.OperationTimeout,
|
||||
this));
|
||||
if (connectionInfo.CustomPipeName != null)
|
||||
{
|
||||
ThrowTerminatingError(
|
||||
new ErrorRecord(
|
||||
new RuntimeException(
|
||||
StringUtil.Format(
|
||||
RemotingErrorIdStrings.EnterPSHostProcessCannotConnectToPipe,
|
||||
connectionInfo.CustomPipeName,
|
||||
errorMessage),
|
||||
e.InnerException),
|
||||
"EnterPSHostProcessCannotConnectToPipe",
|
||||
ErrorCategory.OperationTimeout,
|
||||
this));
|
||||
}
|
||||
else
|
||||
{
|
||||
string msgAppDomainName = connectionInfo.AppDomainName ?? NamedPipeUtils.DefaultAppDomainName;
|
||||
|
||||
ThrowTerminatingError(
|
||||
new ErrorRecord(
|
||||
new RuntimeException(
|
||||
StringUtil.Format(
|
||||
RemotingErrorIdStrings.EnterPSHostProcessCannotConnectToProcess,
|
||||
msgAppDomainName,
|
||||
connectionInfo.ProcessId,
|
||||
errorMessage),
|
||||
e.InnerException),
|
||||
"EnterPSHostProcessCannotConnectToProcess",
|
||||
ErrorCategory.OperationTimeout,
|
||||
this));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -345,6 +397,33 @@ namespace Microsoft.PowerShell.Commands
|
|||
}
|
||||
}
|
||||
|
||||
private void VerifyPipeName(string customPipeName)
|
||||
{
|
||||
// Named Pipes are represented differently on Windows vs macOS & Linux
|
||||
var sb = new StringBuilder(customPipeName.Length);
|
||||
if (Platform.IsWindows)
|
||||
{
|
||||
sb.Append(@"\\.\pipe\");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(Path.GetTempPath()).Append("CoreFxPipe_");
|
||||
}
|
||||
|
||||
sb.Append(customPipeName);
|
||||
|
||||
string pipePath = sb.ToString();
|
||||
if (!File.Exists(pipePath))
|
||||
{
|
||||
ThrowTerminatingError(
|
||||
new ErrorRecord(
|
||||
new PSArgumentException(StringUtil.Format(RemotingErrorIdStrings.EnterPSHostProcessNoNamedPipeFound, customPipeName)),
|
||||
"EnterPSHostProcessNoNamedPipeFound",
|
||||
ErrorCategory.InvalidArgument,
|
||||
this));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
|
|
@ -343,7 +343,7 @@ namespace System.Management.Automation.Remoting
|
|||
/// having correct access restrictions, and provides a listener
|
||||
/// thread loop.
|
||||
/// </summary>
|
||||
internal sealed class RemoteSessionNamedPipeServer : IDisposable
|
||||
public sealed class RemoteSessionNamedPipeServer : IDisposable
|
||||
{
|
||||
#region Members
|
||||
|
||||
|
@ -352,12 +352,17 @@ namespace System.Management.Automation.Remoting
|
|||
|
||||
private const string _threadName = "IPC Listener Thread";
|
||||
private const int _namedPipeBufferSizeForRemoting = 32768;
|
||||
private const int _maxPipePathLengthLinux = 108;
|
||||
private const int _maxPipePathLengthMacOS = 104;
|
||||
|
||||
// Singleton server.
|
||||
private static object s_syncObject;
|
||||
internal static RemoteSessionNamedPipeServer IPCNamedPipeServer;
|
||||
internal static bool IPCNamedPipeServerEnabled;
|
||||
|
||||
// Optional custom server.
|
||||
private static RemoteSessionNamedPipeServer _customNamedPipeServer;
|
||||
|
||||
// Access mask constant taken from PipeSecurity access rights and is equivalent to
|
||||
// PipeAccessRights.FullControl.
|
||||
// See: https://msdn.microsoft.com/library/vstudio/bb348408(v=vs.100).aspx
|
||||
|
@ -371,37 +376,37 @@ namespace System.Management.Automation.Remoting
|
|||
/// <summary>
|
||||
/// Returns the Named Pipe stream object.
|
||||
/// </summary>
|
||||
public NamedPipeServerStream Stream { get; }
|
||||
internal NamedPipeServerStream Stream { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Named Pipe name.
|
||||
/// </summary>
|
||||
public string PipeName { get; }
|
||||
internal string PipeName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if listener is currently running.
|
||||
/// </summary>
|
||||
public bool IsListenerRunning { get; private set; }
|
||||
internal bool IsListenerRunning { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of session configuration.
|
||||
/// </summary>
|
||||
public string ConfigurationName { get; set; }
|
||||
internal string ConfigurationName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Accessor for the named pipe reader.
|
||||
/// </summary>
|
||||
public StreamReader TextReader { get; private set; }
|
||||
internal StreamReader TextReader { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Accessor for the named pipe writer.
|
||||
/// </summary>
|
||||
public StreamWriter TextWriter { get; private set; }
|
||||
internal StreamWriter TextWriter { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if object is currently disposed.
|
||||
/// </summary>
|
||||
public bool IsDisposed { get; private set; }
|
||||
internal bool IsDisposed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Buffer size for PSRP fragmentor.
|
||||
|
@ -419,7 +424,7 @@ namespace System.Management.Automation.Remoting
|
|||
/// Event raised when the named pipe server listening thread
|
||||
/// ends.
|
||||
/// </summary>
|
||||
public event EventHandler<ListenerEndedEventArgs> ListenerEnded;
|
||||
internal event EventHandler<ListenerEndedEventArgs> ListenerEnded;
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -429,7 +434,7 @@ namespace System.Management.Automation.Remoting
|
|||
/// Creates a RemoteSessionNamedPipeServer with the current process and AppDomain information.
|
||||
/// </summary>
|
||||
/// <returns>RemoteSessionNamedPipeServer.</returns>
|
||||
public static RemoteSessionNamedPipeServer CreateRemoteSessionNamedPipeServer()
|
||||
internal static RemoteSessionNamedPipeServer CreateRemoteSessionNamedPipeServer()
|
||||
{
|
||||
string appDomainName = NamedPipeUtils.GetCurrentAppDomainName();
|
||||
|
||||
|
@ -552,6 +557,7 @@ namespace System.Management.Automation.Remoting
|
|||
IPCNamedPipeServerEnabled = true;
|
||||
|
||||
CreateIPCNamedPipeServerSingleton();
|
||||
|
||||
#if !CORECLR // There is only one AppDomain per application in CoreCLR, which would be the default
|
||||
CreateAppDomainUnloadHandler();
|
||||
#endif
|
||||
|
@ -600,6 +606,70 @@ namespace System.Management.Automation.Remoting
|
|||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Creates the custom named pipe server with the given pipename.
|
||||
/// </summary>
|
||||
/// <param name="pipeName">The name of the pipe to create.</param>
|
||||
public static void CreateCustomNamedPipeServer(string pipeName)
|
||||
{
|
||||
lock (s_syncObject)
|
||||
{
|
||||
if (_customNamedPipeServer != null && !_customNamedPipeServer.IsDisposed)
|
||||
{
|
||||
if (pipeName == _customNamedPipeServer.PipeName)
|
||||
{
|
||||
// we shouldn't recreate the server object if we're using the same pipeName
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispose of the current pipe server so we can create a new one with the new pipeName
|
||||
_customNamedPipeServer.Dispose();
|
||||
}
|
||||
|
||||
if (!Platform.IsWindows)
|
||||
{
|
||||
int maxNameLength = (Platform.IsLinux ? _maxPipePathLengthLinux : _maxPipePathLengthMacOS) - Path.GetTempPath().Length;
|
||||
if (pipeName.Length > maxNameLength)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
string.Format(
|
||||
RemotingErrorIdStrings.CustomPipeNameTooLong,
|
||||
maxNameLength,
|
||||
pipeName,
|
||||
pipeName.Length));
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
_customNamedPipeServer = new RemoteSessionNamedPipeServer(pipeName);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// Expected when named pipe server for this process already exists.
|
||||
// This can happen if process has multiple AppDomains hosting PowerShell (SMA.dll).
|
||||
return;
|
||||
}
|
||||
|
||||
// Listener ended callback, used to create listening new pipe server.
|
||||
_customNamedPipeServer.ListenerEnded += OnCustomNamedPipeServerEnded;
|
||||
|
||||
// Start the pipe server listening thread, and provide client connection callback.
|
||||
_customNamedPipeServer.StartListening(ClientConnectionCallback);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_customNamedPipeServer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
/// <summary>
|
||||
/// Starts named pipe server listening thread. When a client connects this thread
|
||||
/// makes a callback to implement the client communication. When the thread ends
|
||||
|
@ -607,7 +677,7 @@ namespace System.Management.Automation.Remoting
|
|||
/// and a new listening thread started to handle subsequent client connections.
|
||||
/// </summary>
|
||||
/// <param name="clientConnectCallback">Connection callback.</param>
|
||||
public void StartListening(
|
||||
internal void StartListening(
|
||||
Action<RemoteSessionNamedPipeServer> clientConnectCallback)
|
||||
{
|
||||
if (clientConnectCallback == null)
|
||||
|
@ -632,10 +702,6 @@ namespace System.Management.Automation.Remoting
|
|||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
internal static CommonSecurityDescriptor GetServerPipeSecurity()
|
||||
{
|
||||
#if UNIX
|
||||
|
@ -925,6 +991,14 @@ namespace System.Management.Automation.Remoting
|
|||
}
|
||||
}
|
||||
|
||||
private static void OnCustomNamedPipeServerEnded(object sender, ListenerEndedEventArgs args)
|
||||
{
|
||||
if (args.RestartListener && sender is RemoteSessionNamedPipeServer server)
|
||||
{
|
||||
CreateCustomNamedPipeServer(server.PipeName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ClientConnectionCallback(RemoteSessionNamedPipeServer pipeServer)
|
||||
{
|
||||
// Create server mediator object and begin remote session with client.
|
||||
|
|
|
@ -1722,12 +1722,21 @@ namespace System.Management.Automation.Runspaces
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom named pipe name to connect to. This is usually used in conjunction with pwsh -CustomPipeName.
|
||||
/// </summary>
|
||||
public string CustomPipeName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// Initializes a new instance of the <see cref="NamedPipeConnectionInfo"/> class.
|
||||
/// </summary>
|
||||
public NamedPipeConnectionInfo()
|
||||
{
|
||||
|
@ -1735,7 +1744,7 @@ namespace System.Management.Automation.Runspaces
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// Initializes a new instance of the <see cref="NamedPipeConnectionInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="processId">Process Id to connect to.</param>
|
||||
public NamedPipeConnectionInfo(
|
||||
|
@ -1744,7 +1753,7 @@ namespace System.Management.Automation.Runspaces
|
|||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// Initializes a new instance of the <see cref="NamedPipeConnectionInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="processId">Process Id to connect to.</param>
|
||||
/// <param name="appDomainName">Application domain name to connect to, or default AppDomain if blank.</param>
|
||||
|
@ -1755,7 +1764,7 @@ namespace System.Management.Automation.Runspaces
|
|||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// Initializes a new instance of the <see cref="NamedPipeConnectionInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="processId">Process Id to connect to.</param>
|
||||
/// <param name="appDomainName">Name of application domain to connect to. Connection is to default application domain if blank.</param>
|
||||
|
@ -1770,6 +1779,33 @@ namespace System.Management.Automation.Runspaces
|
|||
OpenTimeout = openTimeout;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NamedPipeConnectionInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="customPipeName">Pipe name to connect to.</param>
|
||||
public NamedPipeConnectionInfo(
|
||||
string customPipeName) :
|
||||
this(customPipeName, _defaultOpenTimeout)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NamedPipeConnectionInfo"/> class.
|
||||
/// </summary>
|
||||
/// <param name="customPipeName">Pipe name to connect to.</param>
|
||||
/// <param name="openTimeout">Open time out in Milliseconds.</param>
|
||||
public NamedPipeConnectionInfo(
|
||||
string customPipeName,
|
||||
int openTimeout)
|
||||
{
|
||||
if (customPipeName == null)
|
||||
{
|
||||
throw new PSArgumentNullException(nameof(customPipeName));
|
||||
}
|
||||
|
||||
CustomPipeName = customPipeName;
|
||||
OpenTimeout = openTimeout;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
@ -1842,6 +1878,7 @@ namespace System.Management.Automation.Runspaces
|
|||
newCopy.ProcessId = this.ProcessId;
|
||||
newCopy._appDomainName = _appDomainName;
|
||||
newCopy.OpenTimeout = this.OpenTimeout;
|
||||
newCopy.CustomPipeName = this.CustomPipeName;
|
||||
|
||||
return newCopy;
|
||||
}
|
||||
|
|
|
@ -1884,7 +1884,9 @@ namespace System.Management.Automation.Remoting.Client
|
|||
/// </summary>
|
||||
internal override void CreateAsync()
|
||||
{
|
||||
_clientPipe = new RemoteSessionNamedPipeClient(_connectionInfo.ProcessId, _connectionInfo.AppDomainName);
|
||||
_clientPipe = string.IsNullOrEmpty(_connectionInfo.CustomPipeName) ?
|
||||
new RemoteSessionNamedPipeClient(_connectionInfo.ProcessId, _connectionInfo.AppDomainName) :
|
||||
new RemoteSessionNamedPipeClient(_connectionInfo.CustomPipeName);
|
||||
|
||||
// Wait for named pipe to connect.
|
||||
_clientPipe.Connect(_connectionInfo.OpenTimeout);
|
||||
|
|
|
@ -1422,6 +1422,12 @@ All WinRM sessions connected to PowerShell session configurations, such as Micro
|
|||
<data name="EnterPSHostProcessNoProcessFoundWithName" xml:space="preserve">
|
||||
<value>No process was found with Name: {0}.</value>
|
||||
</data>
|
||||
<data name="EnterPSHostProcessNoNamedPipeFound" xml:space="preserve">
|
||||
<value>No named pipe was found with CustomPipeName: {0}.</value>
|
||||
</data>
|
||||
<data name="CustomPipeNameTooLong" xml:space="preserve">
|
||||
<value>Cannot process the command because the pipeName specified is too long. Pipe names on this platform can be up to {0} characters long. Your pipe name '{1}' is {2} characters.</value>
|
||||
</data>
|
||||
<data name="HostDoesNotSupportIASession" xml:space="preserve">
|
||||
<value>The current host does not support the Enter-PSHostProcess cmdlet.</value>
|
||||
</data>
|
||||
|
@ -1440,6 +1446,9 @@ All WinRM sessions connected to PowerShell session configurations, such as Micro
|
|||
<data name="EnterPSHostProcessCannotConnectToProcess" xml:space="preserve">
|
||||
<value>Unable to connect to application domain name {0} of process {1}. Error: {2}.</value>
|
||||
</data>
|
||||
<data name="EnterPSHostProcessCannotConnectToPipe" xml:space="preserve">
|
||||
<value>Unable to connect to pipe with name {0}. Error: {1}.</value>
|
||||
</data>
|
||||
<data name="WSManPluginConnectNoNegotiationData" xml:space="preserve">
|
||||
<value>PowerShell plugin cannot process the Connect operation as required negotiation information is either missing or not complete.</value>
|
||||
</data>
|
||||
|
|
|
@ -2,54 +2,133 @@
|
|||
# Licensed under the MIT License.
|
||||
|
||||
Describe "Enter-PSHostProcess tests" -Tag Feature {
|
||||
BeforeAll {
|
||||
$pwsh_started = New-TemporaryFile
|
||||
$si = [System.Diagnostics.ProcessStartInfo]::new()
|
||||
$si.FileName = "pwsh"
|
||||
$si.Arguments = "-noexit -command 'pwsh' > '$pwsh_started'"
|
||||
$si.RedirectStandardInput = $true
|
||||
$si.RedirectStandardOutput = $true
|
||||
$si.RedirectStandardError = $true
|
||||
$pwsh = [System.Diagnostics.Process]::Start($si)
|
||||
Context "By Process Id" {
|
||||
BeforeAll {
|
||||
$params = @{
|
||||
FilePath = "pwsh"
|
||||
PassThru = $true
|
||||
RedirectStandardOutput = "TestDrive:\pwsh_out.log"
|
||||
RedirectStandardError = "TestDrive:\pwsh_err.log"
|
||||
}
|
||||
$pwsh = Start-Process @params
|
||||
|
||||
if ($IsWindows) {
|
||||
$params = @{
|
||||
FilePath = "powershell"
|
||||
PassThru = $true
|
||||
RedirectStandardOutput = "TestDrive:\powershell_out.log"
|
||||
RedirectStandardError = "TestDrive:\powershell_err.log"
|
||||
}
|
||||
$powershell = Start-Process @params
|
||||
}
|
||||
|
||||
if ($IsWindows) {
|
||||
$powershell_started = New-TemporaryFile
|
||||
$si.FileName = "powershell"
|
||||
$si.Arguments = "-noexit -command 'powershell' >'$powershell_started'"
|
||||
$powershell = [System.Diagnostics.Process]::Start($si)
|
||||
}
|
||||
|
||||
}
|
||||
AfterAll {
|
||||
$pwsh | Stop-Process
|
||||
|
||||
AfterAll {
|
||||
$pwsh | Stop-Process
|
||||
Remove-Item $pwsh_started -Force -ErrorAction SilentlyContinue
|
||||
if ($IsWindows) {
|
||||
$powershell | Stop-Process
|
||||
}
|
||||
}
|
||||
|
||||
if ($IsWindows) {
|
||||
$powershell | Stop-Process
|
||||
Remove-Item $powershell_started -Force -ErrorAction SilentlyContinue
|
||||
It "Can enter and exit another PSHost" {
|
||||
Wait-UntilTrue { (Get-PSHostProcessInfo -Id $pwsh.Id) -ne $null }
|
||||
|
||||
"Enter-PSHostProcess -Id $($pwsh.Id) -ErrorAction Stop
|
||||
`$pid
|
||||
Exit-PSHostProcess" | pwsh -c - | Should -Be $pwsh.Id
|
||||
}
|
||||
|
||||
It "Can enter and exit another Windows PowerShell PSHost" -Skip:(!$IsWindows) {
|
||||
Wait-UntilTrue { (Get-PSHostProcessInfo -Id $powershell.Id) -ne $null }
|
||||
|
||||
"Enter-PSHostProcess -Id $($powershell.Id) -ErrorAction Stop
|
||||
`$pid
|
||||
Exit-PSHostProcess" | pwsh -c - | Should -Be $powershell.Id
|
||||
}
|
||||
|
||||
It "Can enter using NamedPipeConnectionInfo" {
|
||||
try {
|
||||
$npInfo = [System.Management.Automation.Runspaces.NamedPipeConnectionInfo]::new($pwsh.Id)
|
||||
$rs = [runspacefactory]::CreateRunspace($npInfo)
|
||||
$rs.Open()
|
||||
$ps = [powershell]::Create()
|
||||
$ps.Runspace = $rs
|
||||
$ps.AddScript('$pid').Invoke() | Should -Be $pwsh.Id
|
||||
} finally {
|
||||
$rs.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
It "Can enter and exit another PSHost" {
|
||||
Wait-UntilTrue { Test-Path $pwsh_started }
|
||||
Context "By CustomPipeName" {
|
||||
BeforeAll {
|
||||
# Helper function to get the correct path for the named pipe.
|
||||
function Get-PipePath {
|
||||
param (
|
||||
$PipeName
|
||||
)
|
||||
if ($IsWindows) {
|
||||
return "\\.\pipe\$PipeName"
|
||||
}
|
||||
"$([System.IO.Path]::GetTempPath())CoreFxPipe_$PipeName"
|
||||
}
|
||||
|
||||
"enter-pshostprocess -id $($pwsh.Id)`n`$pid`nexit-pshostprocess" | pwsh -c - | Should -Be $pwsh.Id
|
||||
}
|
||||
$pipeName = [System.IO.Path]::GetRandomFileName()
|
||||
$params = @{
|
||||
FilePath = "pwsh"
|
||||
ArgumentList = @("-CustomPipeName",$pipeName)
|
||||
PassThru = $true
|
||||
RedirectStandardOutput = "TestDrive:\pwsh_out.log"
|
||||
RedirectStandardError = "TestDrive:\pwsh_err.log"
|
||||
}
|
||||
$pwsh = Start-Process @params
|
||||
|
||||
It "Can enter and exit another Windows PowerShell PSHost" -Skip:(!$IsWindows) {
|
||||
Wait-UntilTrue { Test-Path $powershell_started }
|
||||
$pipePath = Get-PipePath -PipeName $pipeName
|
||||
}
|
||||
|
||||
"enter-pshostprocess -id $($powershell.Id)`n`$pid`nexit-pshostprocess" | pwsh -c - | Should -Be $powershell.Id
|
||||
}
|
||||
AfterAll {
|
||||
$pwsh | Stop-Process
|
||||
}
|
||||
|
||||
It "Can enter using NamedPipeConnectionInfo" {
|
||||
$npInfo = [System.Management.Automation.Runspaces.NamedPipeConnectionInfo]::new($pwsh.Id)
|
||||
$rs = [runspacefactory]::CreateRunspace($npInfo)
|
||||
$rs.Open()
|
||||
$ps = [powershell]::Create()
|
||||
$ps.Runspace = $rs
|
||||
$ps.AddScript('$pid').Invoke() | Should -Be $pwsh.Id
|
||||
$rs.Dispose()
|
||||
It "Can enter using CustomPipeName" {
|
||||
Wait-UntilTrue { Test-Path $pipePath }
|
||||
|
||||
"Enter-PSHostProcess -CustomPipeName $pipeName -ErrorAction Stop
|
||||
`$pid
|
||||
Exit-PSHostProcess" | pwsh -c - | Should -Be $pwsh.Id
|
||||
}
|
||||
|
||||
It "Can enter, exit, and re-enter using CustomPipeName" {
|
||||
Wait-UntilTrue { Test-Path $pipePath }
|
||||
|
||||
"Enter-PSHostProcess -CustomPipeName $pipeName -ErrorAction Stop
|
||||
`$pid
|
||||
Exit-PSHostProcess" | pwsh -c - | Should -Be $pwsh.Id
|
||||
|
||||
"Enter-PSHostProcess -CustomPipeName $pipeName -ErrorAction Stop
|
||||
`$pid
|
||||
Exit-PSHostProcess" | pwsh -c - | Should -Be $pwsh.Id
|
||||
}
|
||||
|
||||
It "Should throw if CustomPipeName does not exist" {
|
||||
Wait-UntilTrue { Test-Path $pipePath }
|
||||
|
||||
{ Enter-PSHostProcess -CustomPipeName badpipename } | Should -Throw -ExpectedMessage "No named pipe was found with CustomPipeName: badpipename."
|
||||
}
|
||||
|
||||
It "Should throw if CustomPipeName is too long on Linux or macOS" {
|
||||
$longPipeName = "DoggoipsumwaggywagssmolborkingdoggowithalongsnootforpatsdoingmeafrightenporgoYapperporgolongwatershoobcloudsbigolpupperlengthboy"
|
||||
|
||||
if (!$IsWindows) {
|
||||
"`$pid" | pwsh -CustomPipeName $longPipeName -c -
|
||||
# 64 is the ExitCode for BadCommandLineParameter
|
||||
$LASTEXITCODE | Should -Be 64
|
||||
} else {
|
||||
"`$pid" | pwsh -CustomPipeName $longPipeName -c -
|
||||
$LASTEXITCODE | Should -Be 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
58
test/xUnit/csharp/test_NamedPipe.cs
Normal file
58
test/xUnit/csharp/test_NamedPipe.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Management.Automation;
|
||||
using System.Management.Automation.Remoting;
|
||||
using Xunit;
|
||||
|
||||
namespace PSTests.Parallel
|
||||
{
|
||||
public class NamedPipeTests
|
||||
{
|
||||
[Fact]
|
||||
public void TestCustomPipeNameCreation()
|
||||
{
|
||||
string pipeNameForFirstCall = Path.GetRandomFileName();
|
||||
string pipeNameForSecondCall = Path.GetRandomFileName();
|
||||
|
||||
RemoteSessionNamedPipeServer.CreateCustomNamedPipeServer(pipeNameForFirstCall);
|
||||
Assert.True(File.Exists(GetPipePath(pipeNameForFirstCall)));
|
||||
|
||||
// The second call to this method would override the first named pipe.
|
||||
RemoteSessionNamedPipeServer.CreateCustomNamedPipeServer(pipeNameForSecondCall);
|
||||
Assert.True(File.Exists(GetPipePath(pipeNameForSecondCall)));
|
||||
|
||||
// Previous pipe should have been cleaned up.
|
||||
Assert.False(File.Exists(GetPipePath(pipeNameForFirstCall)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestCustomPipeNameCreationTooLongOnNonWindows()
|
||||
{
|
||||
var longPipeName = "DoggoipsumwaggywagssmolborkingdoggowithalongsnootforpatsdoingmeafrightenporgoYapperporgolongwatershoobcloudsbigolpupperlengthboy";
|
||||
|
||||
if (!Platform.IsWindows)
|
||||
{
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
RemoteSessionNamedPipeServer.CreateCustomNamedPipeServer(longPipeName));
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoteSessionNamedPipeServer.CreateCustomNamedPipeServer(longPipeName);
|
||||
Assert.True(File.Exists(GetPipePath(longPipeName)));
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPipePath(string pipeName)
|
||||
{
|
||||
if (Platform.IsWindows)
|
||||
{
|
||||
return $@"\\.\pipe\{pipeName}";
|
||||
}
|
||||
|
||||
return $@"{Path.GetTempPath()}CoreFxPipe_{pipeName}";
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue