mirror of
https://github.com/aphrody-labs/PowerShell.git
synced 2026-10-17 12:45:47 +00:00
510 lines
16 KiB
C#
510 lines
16 KiB
C#
/********************************************************************++
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
--********************************************************************/
|
|
|
|
using System.Management.Automation.Host;
|
|
using System.Management.Automation.Remoting.Server;
|
|
using System.Management.Automation.Runspaces;
|
|
using System.Globalization;
|
|
using Dbg = System.Management.Automation.Diagnostics;
|
|
|
|
namespace System.Management.Automation.Remoting
|
|
{
|
|
/// <summary>
|
|
/// The ServerRemoteHost class.
|
|
/// </summary>
|
|
internal class ServerRemoteHost : PSHost, IHostSupportsInteractiveSession
|
|
{
|
|
#region Private Members
|
|
|
|
/// <summary>
|
|
/// Remote host user interface.
|
|
/// </summary>
|
|
private ServerRemoteHostUserInterface _remoteHostUserInterface;
|
|
|
|
/// <summary>
|
|
/// Server method executor.
|
|
/// </summary>
|
|
private ServerMethodExecutor _serverMethodExecutor;
|
|
|
|
/// <summary>
|
|
/// Client runspace pool id.
|
|
/// </summary>
|
|
private Guid _clientRunspacePoolId;
|
|
|
|
/// <summary>
|
|
/// Client power shell id.
|
|
/// </summary>
|
|
private Guid _clientPowerShellId;
|
|
|
|
/// <summary>
|
|
/// Transport manager.
|
|
/// </summary>
|
|
protected AbstractServerTransportManager _transportManager;
|
|
|
|
/// <summary>
|
|
/// ServerDriverRemoteHost
|
|
/// </summary>
|
|
private ServerDriverRemoteHost _serverDriverRemoteHost;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// Constructor for ServerRemoteHost.
|
|
/// </summary>
|
|
internal ServerRemoteHost(
|
|
Guid clientRunspacePoolId,
|
|
Guid clientPowerShellId,
|
|
HostInfo hostInfo,
|
|
AbstractServerTransportManager transportManager,
|
|
Runspace runspace,
|
|
ServerDriverRemoteHost serverDriverRemoteHost)
|
|
{
|
|
_clientRunspacePoolId = clientRunspacePoolId;
|
|
_clientPowerShellId = clientPowerShellId;
|
|
Dbg.Assert(hostInfo != null, "Expected hostInfo != null");
|
|
Dbg.Assert(transportManager != null, "Expected transportManager != null");
|
|
|
|
// Set host-info and the transport-manager.
|
|
HostInfo = hostInfo;
|
|
_transportManager = transportManager;
|
|
_serverDriverRemoteHost = serverDriverRemoteHost;
|
|
|
|
// Create the executor for the host methods.
|
|
_serverMethodExecutor = new ServerMethodExecutor(
|
|
clientRunspacePoolId, clientPowerShellId, _transportManager);
|
|
|
|
// Use HostInfo to create host-UI as null or non-null based on the client's host-UI.
|
|
_remoteHostUserInterface = hostInfo.IsHostUINull ? null : new ServerRemoteHostUserInterface(this);
|
|
|
|
Runspace = runspace;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Server method executor.
|
|
/// </summary>
|
|
internal ServerMethodExecutor ServerMethodExecutor
|
|
{
|
|
get { return _serverMethodExecutor; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The user interface.
|
|
/// </summary>
|
|
public override PSHostUserInterface UI
|
|
{
|
|
get { return _remoteHostUserInterface; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Name.
|
|
/// </summary>
|
|
public override string Name
|
|
{
|
|
get { return "ServerRemoteHost"; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Version.
|
|
/// </summary>
|
|
public override Version Version
|
|
{
|
|
get { return RemotingConstants.HostVersion; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Instance id.
|
|
/// </summary>
|
|
public override Guid InstanceId { get; } = Guid.NewGuid();
|
|
|
|
/// <summary>
|
|
/// Is runspace pushed.
|
|
/// </summary>
|
|
public virtual bool IsRunspacePushed
|
|
{
|
|
get
|
|
{
|
|
if (_serverDriverRemoteHost != null)
|
|
{
|
|
return _serverDriverRemoteHost.IsRunspacePushed;
|
|
}
|
|
else
|
|
{
|
|
throw RemoteHostExceptions.NewNotImplementedException(RemoteHostMethodId.GetIsRunspacePushed);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Runspace.
|
|
/// </summary>
|
|
public Runspace Runspace { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// Host info.
|
|
/// </summary>
|
|
internal HostInfo HostInfo { get; }
|
|
|
|
/// <summary>
|
|
/// Allows a push runspace on this remote server host instance, regardless of
|
|
/// transport being used.
|
|
/// </summary>
|
|
internal virtual bool AllowPushRunspace
|
|
{
|
|
get { return (_serverDriverRemoteHost != null) ? _serverDriverRemoteHost.AllowPushRunspace : false; }
|
|
set { if (_serverDriverRemoteHost != null) { _serverDriverRemoteHost.AllowPushRunspace = value; } }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Method Overrides
|
|
|
|
/// <summary>
|
|
/// Set should exit.
|
|
/// </summary>
|
|
public override void SetShouldExit(int exitCode)
|
|
{
|
|
_serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.SetShouldExit, new object[] { exitCode });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enter nested prompt.
|
|
/// </summary>
|
|
public override void EnterNestedPrompt()
|
|
{
|
|
throw RemoteHostExceptions.NewNotImplementedException(RemoteHostMethodId.EnterNestedPrompt);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Exit nested prompt.
|
|
/// </summary>
|
|
public override void ExitNestedPrompt()
|
|
{
|
|
throw RemoteHostExceptions.NewNotImplementedException(RemoteHostMethodId.ExitNestedPrompt);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Notify begin application.
|
|
/// </summary>
|
|
public override void NotifyBeginApplication()
|
|
{
|
|
// This is called when a native application is executed on the server. It gives the
|
|
// host an opportunity to save state that might be altered by the native application.
|
|
// This call should not be sent to the client because the native application running
|
|
// on the server cannot affect the state of the machine on the client.
|
|
}
|
|
|
|
/// <summary>
|
|
/// Notify end application.
|
|
/// </summary>
|
|
public override void NotifyEndApplication()
|
|
{
|
|
// See note in NotifyBeginApplication.
|
|
}
|
|
|
|
/// <summary>
|
|
/// Current culture.
|
|
/// </summary>
|
|
public override CultureInfo CurrentCulture
|
|
{
|
|
get
|
|
{
|
|
// Return the thread's current culture and rely on WinRM to set this
|
|
// correctly based on the client's culture.
|
|
return CultureInfo.CurrentCulture;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Current ui culture.
|
|
/// </summary>
|
|
public override CultureInfo CurrentUICulture
|
|
{
|
|
get
|
|
{
|
|
// Return the thread's current UI culture and rely on WinRM to set
|
|
// this correctly based on the client's UI culture.
|
|
return CultureInfo.CurrentUICulture;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// Push runspace.
|
|
/// </summary>
|
|
public virtual void PushRunspace(Runspace runspace)
|
|
{
|
|
if (_serverDriverRemoteHost != null)
|
|
{
|
|
_serverDriverRemoteHost.PushRunspace(runspace);
|
|
}
|
|
else
|
|
{
|
|
throw RemoteHostExceptions.NewNotImplementedException(RemoteHostMethodId.PushRunspace);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pop runspace.
|
|
/// </summary>
|
|
public virtual void PopRunspace()
|
|
{
|
|
if ((_serverDriverRemoteHost != null) && (_serverDriverRemoteHost.IsRunspacePushed))
|
|
{
|
|
if (_serverDriverRemoteHost.PropagatePop)
|
|
{
|
|
// Forward the PopRunspace command to client and keep *this* pushed runspace as
|
|
// the configured JEA restricted session.
|
|
_serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.PopRunspace);
|
|
}
|
|
else
|
|
{
|
|
_serverDriverRemoteHost.PopRunspace();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.PopRunspace);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// The remote host class for the ServerRunspacePoolDriver.
|
|
/// </summary>
|
|
internal class ServerDriverRemoteHost : ServerRemoteHost
|
|
{
|
|
#region Private Members
|
|
|
|
private RemoteRunspace _pushedRunspace;
|
|
private ServerRemoteDebugger _debugger;
|
|
private bool _hostSupportsPSEdit;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
internal ServerDriverRemoteHost(
|
|
Guid clientRunspacePoolId,
|
|
Guid clientPowerShellId,
|
|
HostInfo hostInfo,
|
|
AbstractServerSessionTransportManager transportManager,
|
|
ServerRemoteDebugger debugger)
|
|
: base(clientRunspacePoolId, clientPowerShellId, hostInfo, transportManager, null, null)
|
|
{
|
|
_debugger = debugger;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Overrides
|
|
|
|
/// <summary>
|
|
/// True if runspace is pushed
|
|
/// </summary>
|
|
public override bool IsRunspacePushed
|
|
{
|
|
get
|
|
{
|
|
return (_pushedRunspace != null);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Push runspace to use for remote command execution
|
|
/// </summary>
|
|
/// <param name="runspace">RemoteRunspace</param>
|
|
public override void PushRunspace(Runspace runspace)
|
|
{
|
|
// Double session hop is currently allowed only for WSMan (non-OutOfProc) sessions, where
|
|
// the second session is either through a named pipe or hyperV socket connection.
|
|
if (!AllowPushRunspace &&
|
|
((_transportManager is OutOfProcessServerSessionTransportManager) ||
|
|
!(runspace.ConnectionInfo is NamedPipeConnectionInfo ||
|
|
runspace.ConnectionInfo is VMConnectionInfo ||
|
|
runspace.ConnectionInfo is ContainerConnectionInfo))
|
|
)
|
|
{
|
|
throw new PSNotSupportedException();
|
|
}
|
|
|
|
if (_debugger == null)
|
|
{
|
|
throw new PSInvalidOperationException(RemotingErrorIdStrings.ServerDriverRemoteHostNoDebuggerToPush);
|
|
}
|
|
|
|
if (_pushedRunspace != null)
|
|
{
|
|
throw new PSInvalidOperationException(RemotingErrorIdStrings.ServerDriverRemoteHostAlreadyPushed);
|
|
}
|
|
|
|
RemoteRunspace remoteRunspace = runspace as RemoteRunspace;
|
|
if (remoteRunspace == null)
|
|
{
|
|
throw new PSInvalidOperationException(RemotingErrorIdStrings.ServerDriverRemoteHostNotRemoteRunspace);
|
|
}
|
|
|
|
// PSEdit support. Existence of RemoteSessionOpenFileEvent event indicates host supports PSEdit
|
|
_hostSupportsPSEdit = false;
|
|
PSEventManager localEventManager = (Runspace != null) ? Runspace.Events : null;
|
|
_hostSupportsPSEdit = (localEventManager != null) ? localEventManager.GetEventSubscribers(HostUtilities.RemoteSessionOpenFileEvent).GetEnumerator().MoveNext() : false;
|
|
if (_hostSupportsPSEdit)
|
|
{
|
|
AddPSEditForRunspace(remoteRunspace);
|
|
}
|
|
|
|
_debugger.PushDebugger(runspace.Debugger);
|
|
_pushedRunspace = remoteRunspace;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pop runspace
|
|
/// </summary>
|
|
public override void PopRunspace()
|
|
{
|
|
if (_pushedRunspace != null)
|
|
{
|
|
if (_debugger != null)
|
|
{
|
|
_debugger.PopDebugger();
|
|
}
|
|
|
|
if (_hostSupportsPSEdit)
|
|
{
|
|
RemovePSEditFromRunspace(_pushedRunspace);
|
|
}
|
|
|
|
if (_pushedRunspace.ShouldCloseOnPop)
|
|
{
|
|
_pushedRunspace.Close();
|
|
}
|
|
_pushedRunspace = null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Server Debugger
|
|
/// </summary>
|
|
internal Debugger ServerDebugger
|
|
{
|
|
get { return _debugger; }
|
|
set { _debugger = value as ServerRemoteDebugger; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pushed runspace or null
|
|
/// </summary>
|
|
internal Runspace PushedRunspace
|
|
{
|
|
get { return _pushedRunspace; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allows a push runspace on this remote server host instance, regardless of
|
|
/// transport being used.
|
|
/// </summary>
|
|
internal override bool AllowPushRunspace
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// When true will propagate pop call to client after popping runspace from this
|
|
/// host. Used for OutOfProc remote sessions in a restricted (pushed) remote runspace,
|
|
/// where a pop (exit session) should occur.
|
|
/// </summary>
|
|
internal bool PropagatePop
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PSEdit Support for ISE Host
|
|
|
|
private void AddPSEditForRunspace(RemoteRunspace remoteRunspace)
|
|
{
|
|
if (remoteRunspace.Events == null) { return; }
|
|
|
|
// Add event handler.
|
|
remoteRunspace.Events.ReceivedEvents.PSEventReceived += HandleRemoteSessionForwardedEvent;
|
|
|
|
// Add script function.
|
|
using (PowerShell powershell = PowerShell.Create())
|
|
{
|
|
powershell.Runspace = remoteRunspace;
|
|
powershell.AddScript(HostUtilities.CreatePSEditFunction).AddParameter("PSEditFunction", HostUtilities.PSEditFunction);
|
|
try
|
|
{
|
|
powershell.Invoke();
|
|
}
|
|
catch (RemoteException) { }
|
|
}
|
|
}
|
|
|
|
private void RemovePSEditFromRunspace(RemoteRunspace remoteRunspace)
|
|
{
|
|
if (remoteRunspace.Events == null) { return; }
|
|
|
|
// It is possible for the popped runspace to be in a bad state after an error.
|
|
if ((remoteRunspace.RunspaceStateInfo.State != RunspaceState.Opened) || (remoteRunspace.RunspaceAvailability != RunspaceAvailability.Available))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Remove event handler.
|
|
remoteRunspace.Events.ReceivedEvents.PSEventReceived -= HandleRemoteSessionForwardedEvent;
|
|
|
|
// Remove script function.
|
|
using (PowerShell powershell = PowerShell.Create())
|
|
{
|
|
powershell.Runspace = remoteRunspace;
|
|
powershell.AddScript(HostUtilities.RemovePSEditFunction);
|
|
try
|
|
{
|
|
powershell.Invoke();
|
|
}
|
|
catch (RemoteException) { }
|
|
}
|
|
}
|
|
|
|
private void HandleRemoteSessionForwardedEvent(object sender, PSEventArgs args)
|
|
{
|
|
if ((Runspace == null) || (Runspace.Events == null)) { return; }
|
|
|
|
// Forward events from nested pushed session to parent session.
|
|
try
|
|
{
|
|
Runspace.Events.GenerateEvent(
|
|
sourceIdentifier: args.SourceIdentifier,
|
|
sender: null,
|
|
args: args.SourceArgs,
|
|
extraData: null);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
CommandProcessorBase.CheckForSevereException(e);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|