PowerShell/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs

3044 lines
112 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma warning disable 1634, 1691
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Management.Automation;
using System.Management.Automation.Configuration;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
using System.Management.Automation.Language;
using System.Management.Automation.Remoting;
using System.Management.Automation.Runspaces;
using System.Management.Automation.Tracing;
using System.Reflection;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Microsoft.PowerShell.Commands;
using Microsoft.PowerShell.Telemetry;
#if LEGACYTELEMETRY
using Microsoft.PowerShell.Telemetry.Internal;
#endif
using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle;
using Dbg = System.Management.Automation.Diagnostics;
using Debugger = System.Management.Automation.Debugger;
namespace Microsoft.PowerShell
{
/// <summary>
/// Subclasses S.M.A.Host to implement a console-mode monad host.
/// </summary>
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
internal sealed partial class ConsoleHost
:
PSHost,
IDisposable,
#if LEGACYTELEMETRY
IHostProvidesTelemetryData,
#endif
IHostSupportsInteractiveSession
{
#region static methods
internal const int ExitCodeSuccess = 0;
internal const int ExitCodeCtrlBreak = 128 + 21; // SIGBREAK
internal const int ExitCodeInitFailure = 70; // Internal Software Error
internal const int ExitCodeBadCommandLineParameter = 64; // Command Line Usage Error
private const uint SPI_GETSCREENREADER = 0x0046;
#if UNIX
internal const string DECCKM_ON = "\x1b[?1h";
internal const string DECCKM_OFF = "\x1b[?1l";
#endif
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, ref bool pvParam, uint fWinIni);
/// <summary>
/// Internal Entry point in msh console host implementation.
/// </summary>
/// <param name="bannerText">
/// Banner text to be displayed by ConsoleHost
/// </param>
/// <param name="helpText">
/// Help text for minishell. This is displayed on 'minishell -?'.
/// </param>
/// <returns>
/// The exit code for the shell.
///
/// The behavior here is related to monitor work.
/// The low word of the exit code is available for the user. The high word is reserved for the shell and monitor.
///
/// The shell process needs to return:
///
/// - if the shell.exe fails init, 0xFFFF0000
/// - if the exit keyword is called with no parameter at the point of top-level prompt, 0x80000000 (e.g. 0 with the high
/// bit set)
/// - if the exit keyword is called with any int param less than or equal to 0xFFFF, then that int masked with the high
/// bit set. e.g. "exit 3" results in 0x80000003
/// - if the script ends (in the case of msh -command or msh -commandfile), then 0x80000000.
/// - if ctrl-break is pressed, with 0xFFFE0000
/// - if the shell.exe is passed a bad command-line parameter, with 0xFFFD0000.
/// - if the shell.exe crashes, with 0x00000000
///
/// The monitor process gets the exit code. If the high bit is set, then the shell process exited normally (though
/// possibly due to an error). If not, the shell process crashed. If the shell.exe exit code is x00000000 (crashed)
/// or 0xFFFE0000 (user hit ctrl-break), the monitor should restart the shell.exe. Otherwise, the monitor should exit
/// with the same exit code as the shell.exe.
///
/// Anyone checking the exit code of the shell or monitor can mask off the high word to determine the exit code passed
/// by the script that the shell last executed.
/// </returns>
internal static int Start(string bannerText, string helpText)
{
#if DEBUG
if (Environment.GetEnvironmentVariable("POWERSHELL_DEBUG_STARTUP") != null)
{
while (!System.Diagnostics.Debugger.IsAttached)
{
Thread.Sleep(1000);
}
}
#endif
// put PSHOME in front of PATH so that calling `powershell` within `powershell` always starts the same running version
string path = Environment.GetEnvironmentVariable("PATH");
string pshome = Utils.DefaultPowerShellAppBase + Path.PathSeparator;
// to not impact startup perf, we don't remove duplicates, but we avoid adding a duplicate to the front
// we also don't handle the edge case where PATH only contains $PSHOME
if (string.IsNullOrEmpty(path))
{
Environment.SetEnvironmentVariable("PATH", pshome);
}
else if (!path.StartsWith(pshome, StringComparison.Ordinal))
{
Environment.SetEnvironmentVariable("PATH", pshome + path);
}
try
{
string profileDir = Platform.CacheDirectory;
#if !UNIX
if (!Directory.Exists(profileDir))
{
Directory.CreateDirectory(profileDir);
}
#endif
ProfileOptimization.SetProfileRoot(profileDir);
}
catch
{
// It's safe to ignore errors, the guarded code is just there to try and
// improve startup performance.
}
uint exitCode = ExitCodeSuccess;
Thread.CurrentThread.Name = "ConsoleHost main thread";
try
{
// We might be able to ignore console host creation error if we are running in
// server mode, which does not require a console.
HostException hostException = null;
try
{
s_theConsoleHost = ConsoleHost.CreateSingletonInstance();
}
catch (HostException e)
{
hostException = e;
}
PSHostUserInterface hostUI = s_theConsoleHost?.UI ?? new NullHostUserInterface();
s_cpp.ShowErrorHelpBanner(hostUI, bannerText, helpText);
if (s_cpp.ShowVersion)
{
// Alternatively, we could call s_theConsoleHost.UI.WriteLine(s_theConsoleHost.Version.ToString());
// or start up the engine and retrieve the information via $psversiontable.GitCommitId
// but this returns the semantic version and avoids executing a script
s_theConsoleHost.UI.WriteLine("PowerShell " + PSVersionInfo.GitCommitId);
return 0;
}
// Servermode parameter validation check.
if ((s_cpp.ServerMode && s_cpp.NamedPipeServerMode) || (s_cpp.ServerMode && s_cpp.SocketServerMode) || (s_cpp.NamedPipeServerMode && s_cpp.SocketServerMode))
{
s_tracer.TraceError("Conflicting server mode parameters, parameters must be used exclusively.");
if (s_theConsoleHost != null)
{
s_theConsoleHost.ui.WriteErrorLine(ConsoleHostStrings.ConflictingServerModeParameters);
}
return ExitCodeBadCommandLineParameter;
}
#if !UNIX
TaskbarJumpList.CreateRunAsAdministratorJumpList();
#endif
// First check for and handle PowerShell running in a server mode.
if (s_cpp.ServerMode)
{
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("ServerMode");
ProfileOptimization.StartProfile("StartupProfileData-ServerMode");
System.Management.Automation.Remoting.Server.StdIOProcessMediator.Run(
initialCommand: s_cpp.InitialCommand,
workingDirectory: s_cpp.WorkingDirectory,
configurationName: null);
exitCode = 0;
}
else if (s_cpp.SSHServerMode)
{
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SSHServer");
ProfileOptimization.StartProfile("StartupProfileData-SSHServerMode");
System.Management.Automation.Remoting.Server.StdIOProcessMediator.Run(
initialCommand: s_cpp.InitialCommand,
workingDirectory: null,
configurationName: null);
exitCode = 0;
}
else if (s_cpp.NamedPipeServerMode)
{
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("NamedPipe");
ProfileOptimization.StartProfile("StartupProfileData-NamedPipeServerMode");
System.Management.Automation.Remoting.RemoteSessionNamedPipeServer.RunServerMode(
configurationName: s_cpp.ConfigurationName);
exitCode = 0;
}
else if (s_cpp.SocketServerMode)
{
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SocketServerMode");
ProfileOptimization.StartProfile("StartupProfileData-SocketServerMode");
System.Management.Automation.Remoting.Server.HyperVSocketMediator.Run(
initialCommand: s_cpp.InitialCommand,
configurationName: s_cpp.ConfigurationName);
exitCode = 0;
}
else
{
// Run PowerShell in normal console mode.
if (hostException != null)
{
// Unable to create console host.
throw hostException;
}
if (LoadPSReadline())
{
ProfileOptimization.StartProfile("StartupProfileData-Interactive");
if (UpdatesNotification.CanNotifyUpdates)
{
// Start a task in the background to check for the update release.
_ = UpdatesNotification.CheckForUpdates();
}
}
else
{
ProfileOptimization.StartProfile("StartupProfileData-NonInteractive");
}
s_theConsoleHost.BindBreakHandler();
PSHost.IsStdOutputRedirected = Console.IsOutputRedirected;
// Send startup telemetry for ConsoleHost startup
ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("Normal");
exitCode = s_theConsoleHost.Run(s_cpp, false);
}
}
finally
{
if (s_theConsoleHost != null)
{
#if LEGACYTELEMETRY
TelemetryAPI.ReportExitTelemetry(s_theConsoleHost);
#endif
#if UNIX
if (s_theConsoleHost.IsInteractive && s_theConsoleHost.UI.SupportsVirtualTerminal)
{
// https://github.com/dotnet/runtime/issues/27626 leaves terminal in application mode
// for now, we explicitly emit DECRST 1 sequence
s_theConsoleHost.UI.Write(DECCKM_OFF);
}
#endif
s_theConsoleHost.Dispose();
}
}
unchecked
{
return (int)exitCode;
}
}
internal static void ParseCommandLine(string[] args)
{
s_cpp.Parse(args);
#if !UNIX
if (s_cpp.WindowStyle.HasValue)
{
ConsoleControl.SetConsoleMode(s_cpp.WindowStyle.Value);
}
#endif
if (s_cpp.SettingsFile is not null)
{
PowerShellConfig.Instance.SetSystemConfigFilePath(s_cpp.SettingsFile);
}
// Check registry setting for a Group Policy ConfigurationName entry and
// use it to override anything set by the user.
// It depends on setting file so 'SetSystemConfigFilePath()' should be called before.
s_cpp.ConfigurationName = CommandLineParameterParser.GetConfigurationNameFromGroupPolicy();
}
private static readonly CommandLineParameterParser s_cpp = new CommandLineParameterParser();
#if UNIX
/// <summary>
/// The break handler for the program. Dispatches a break event to the current Executor.
/// </summary>
private static void MyBreakHandler(object sender, ConsoleCancelEventArgs args)
{
// Set the Cancel property to true to prevent the process from terminating.
args.Cancel = true;
switch (args.SpecialKey)
{
case ConsoleSpecialKey.ControlC:
SpinUpBreakHandlerThread(shouldEndSession: false);
return;
case ConsoleSpecialKey.ControlBreak:
if (s_cpp.NonInteractive)
{
// ControlBreak mimics ControlC in Noninteractive shells
SpinUpBreakHandlerThread(shouldEndSession: true);
}
else
{
// Break into script debugger.
BreakIntoDebugger();
}
return;
}
}
#else
/// <summary>
/// The break handler for the program. Dispatches a break event to the current Executor.
/// </summary>
/// <param name="signal"></param>
/// <returns></returns>
private static bool MyBreakHandler(ConsoleControl.ConsoleBreakSignal signal)
{
switch (signal)
{
case ConsoleControl.ConsoleBreakSignal.CtrlBreak:
if (s_cpp.NonInteractive)
{
// ControlBreak mimics ControlC in Noninteractive shells
SpinUpBreakHandlerThread(shouldEndSession: true);
}
else
{
// Break into script debugger.
BreakIntoDebugger();
}
return true;
// Run the break handler...
case ConsoleControl.ConsoleBreakSignal.CtrlC:
SpinUpBreakHandlerThread(shouldEndSession: false);
return true;
case ConsoleControl.ConsoleBreakSignal.Logoff:
// Just ignore the logoff signal. This signal is sent to console
// apps running as service anytime *any* user logs off which means
// that PowerShell couldn't be used in services/tasks if we didn't
// suppress this signal...
return true;
case ConsoleControl.ConsoleBreakSignal.Close:
case ConsoleControl.ConsoleBreakSignal.Shutdown:
SpinUpBreakHandlerThread(shouldEndSession: true);
return false;
default:
// Log as much sqm data as possible before we exit.
SpinUpBreakHandlerThread(shouldEndSession: true);
return false;
}
}
#endif
private static bool BreakIntoDebugger()
{
ConsoleHost host = ConsoleHost.SingletonInstance;
Debugger debugger = null;
lock (host.hostGlobalLock)
{
if (host._runspaceRef.Runspace != null &&
host._runspaceRef.Runspace.GetCurrentlyRunningPipeline() != null)
{
debugger = host._runspaceRef.Runspace.Debugger;
}
}
if (debugger != null)
{
debugger.SetDebuggerStepMode(true);
return true;
}
return false;
}
/// <summary>
/// Spin up a new thread to cancel the current pipeline. This will allow subsequent break interrupts to be received even
/// if the cancellation is blocked (which can be the case when the pipeline blocks and nothing implements Cmdlet.Stop
/// properly). That is because the OS will not inject another thread when a break event occurs if one has already been
/// injected and is running.
/// </summary>
/// <param name="shouldEndSession">
/// if true, then flag the parent ConsoleHost that it should shutdown the session. If false, then only the current
/// executing instance is stopped.
///
///</param>
private static void SpinUpBreakHandlerThread(bool shouldEndSession)
{
ConsoleHost host = ConsoleHost.SingletonInstance;
Thread bht = null;
lock (host.hostGlobalLock)
{
bht = host._breakHandlerThread;
if (!host.ShouldEndSession && shouldEndSession)
{
host.ShouldEndSession = shouldEndSession;
}
// Creation of the thread and starting it should be an atomic operation.
// otherwise the code in Run method can get instance of the breakhandlerThread
// after it is created and before started and call join on it. This will result
// in ThreadStateException.
// NTRAID#Windows OutofBand Bugs-938289-2006/07/27-hiteshr
if (bht == null)
{
// we're not already running HandleBreak on a separate thread, so run it now.
host._breakHandlerThread = new Thread(new ThreadStart(ConsoleHost.HandleBreak));
host._breakHandlerThread.Name = "ConsoleHost.HandleBreak";
host._breakHandlerThread.Start();
}
}
}
private static void HandleBreak()
{
ConsoleHost consoleHost = s_theConsoleHost;
if (consoleHost != null)
{
if (consoleHost.InDebugMode)
{
// Only stop a running user command, ignore prompt evaluation.
if (consoleHost.DebuggerCanStopCommand)
{
// Cancel any executing debugger command if in debug mode.
try
{
consoleHost.Runspace.Debugger.StopProcessCommand();
}
catch (Exception)
{
}
}
}
else
{
// Cancel the reconnected debugged running pipeline command.
if (!StopPipeline(consoleHost.runningCmd))
{
Executor.CancelCurrentExecutor();
}
}
if (consoleHost.ShouldEndSession)
{
var runspaceRef = ConsoleHost.SingletonInstance._runspaceRef;
if (runspaceRef != null)
{
var runspace = runspaceRef.Runspace;
if (runspace != null)
{
runspace.Close();
}
}
}
}
// call the console APIs directly, instead of ui.rawui.FlushInputHandle, as ui may be finalized
// already if this thread is lagging behind the main thread.
#if !UNIX
ConsoleHandle handle = ConsoleControl.GetConioDeviceHandle();
ConsoleControl.FlushConsoleInputBuffer(handle);
#endif
ConsoleHost.SingletonInstance._breakHandlerThread = null;
}
private static bool StopPipeline(Pipeline cmd)
{
if (cmd != null &&
(cmd.PipelineStateInfo.State == PipelineState.Running ||
cmd.PipelineStateInfo.State == PipelineState.Disconnected))
{
try
{
cmd.StopAsync();
return true;
}
catch (Exception)
{
}
}
return false;
}
/// <summary>
/// Create single instance of ConsoleHost.
/// </summary>
internal static ConsoleHost CreateSingletonInstance()
{
Dbg.Assert(s_theConsoleHost == null, "CreateSingletonInstance should not be called multiple times");
s_theConsoleHost = new ConsoleHost();
return s_theConsoleHost;
}
internal static ConsoleHost SingletonInstance
{
get
{
Dbg.Assert(s_theConsoleHost != null, "CreateSingletonInstance should be called before calling this method");
return s_theConsoleHost;
}
}
#endregion static methods
#region overrides
/// <summary>
/// See base class.
/// </summary>
/// <value></value>
/// <exception/>
public override string Name
{
get
{
const string myName = "ConsoleHost";
// const, no lock needed.
return myName;
}
}
/// <summary>
/// See base class.
/// </summary>
/// <value></value>
/// <exception/>
public override System.Version Version
{
get
{
// const, no lock needed.
return _ver;
}
}
/// <summary>
/// See base class.
/// </summary>
/// <value></value>
/// <exception/>
public override System.Guid InstanceId { get; } = Guid.NewGuid();
/// <summary>
/// See base class.
/// </summary>
/// <value></value>
/// <exception/>
public override PSHostUserInterface UI
{
get
{
Dbg.Assert(ui != null, "ui should have been allocated in ctor");
return ui;
}
}
/// <summary>
/// See base class.
/// </summary>
public void PushRunspace(Runspace newRunspace)
{
if (_runspaceRef == null) { return; }
RemoteRunspace remoteRunspace = newRunspace as RemoteRunspace;
Dbg.Assert(remoteRunspace != null, "Expected remoteRunspace != null");
remoteRunspace.StateChanged += HandleRemoteRunspaceStateChanged;
// Unsubscribe the local session debugger.
if (_runspaceRef.Runspace.Debugger != null)
{
_runspaceRef.Runspace.Debugger.DebuggerStop -= OnExecutionSuspended;
}
// Subscribe to debugger stop event.
if (remoteRunspace.Debugger != null)
{
remoteRunspace.Debugger.DebuggerStop += OnExecutionSuspended;
}
// Connect a disconnected command.
this.runningCmd = EnterPSSessionCommand.ConnectRunningPipeline(remoteRunspace);
// Push runspace.
_runspaceRef.Override(remoteRunspace, hostGlobalLock, out _isRunspacePushed);
RunspacePushed.SafeInvoke(this, EventArgs.Empty);
if (this.runningCmd != null)
{
EnterPSSessionCommand.ContinueCommand(
remoteRunspace,
this.runningCmd,
this,
InDebugMode,
_runspaceRef.OldRunspace.ExecutionContext);
}
this.runningCmd = null;
}
/// <summary>
/// Handles state changed event of the remote runspace. If the remote runspace
/// gets into a broken or closed state, writes a message and pops out the
/// runspace.
/// </summary>
/// <param name="sender">Not sure.</param>
/// <param name="eventArgs">Arguments describing this event.</param>
private void HandleRemoteRunspaceStateChanged(object sender, RunspaceStateEventArgs eventArgs)
{
RunspaceState state = eventArgs.RunspaceStateInfo.State;
switch (state)
{
case RunspaceState.Opening:
case RunspaceState.Opened:
{
return;
}
case RunspaceState.Closing:
case RunspaceState.Closed:
case RunspaceState.Broken:
case RunspaceState.Disconnected:
{
PopRunspace();
}
break;
}
}
/// <summary>
/// See base class.
/// </summary>
public void PopRunspace()
{
if (_runspaceRef == null ||
!_runspaceRef.IsRunspaceOverridden)
{
return;
}
if (_inPushedConfiguredSession)
{
// For configured endpoint sessions, end session when configured runspace is popped.
this.ShouldEndSession = true;
}
if (_runspaceRef.Runspace.Debugger != null)
{
// Unsubscribe pushed runspace debugger.
_runspaceRef.Runspace.Debugger.DebuggerStop -= OnExecutionSuspended;
StopPipeline(this.runningCmd);
if (this.InDebugMode)
{
ExitDebugMode(DebuggerResumeAction.Continue);
}
}
this.runningCmd = null;
lock (hostGlobalLock)
{
_runspaceRef.Revert();
_isRunspacePushed = false;
}
// Re-subscribe local runspace debugger.
_runspaceRef.Runspace.Debugger.DebuggerStop += OnExecutionSuspended;
// raise events outside the lock
RunspacePopped.SafeInvoke(this, EventArgs.Empty);
}
/// <summary>
/// True if a runspace is pushed; false otherwise.
/// </summary>
public bool IsRunspacePushed
{
get
{
return _isRunspacePushed;
}
}
private bool _isRunspacePushed = false;
/// <summary>
/// Returns the current runspace associated with this host.
/// </summary>
public Runspace Runspace
{
get
{
if (this.RunspaceRef == null) { return null; }
return this.RunspaceRef.Runspace;
}
}
internal LocalRunspace LocalRunspace
{
get
{
if (_isRunspacePushed)
{
return RunspaceRef.OldRunspace as LocalRunspace;
}
if (RunspaceRef == null) { return null; }
return RunspaceRef.Runspace as LocalRunspace;
}
}
public class ConsoleColorProxy
{
private readonly ConsoleHostUserInterface _ui;
public ConsoleColorProxy(ConsoleHostUserInterface ui)
{
if (ui == null) throw new ArgumentNullException(nameof(ui));
_ui = ui;
}
public ConsoleColor FormatAccentColor
{
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
get
{
return _ui.FormatAccentColor;
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
set
{
_ui.FormatAccentColor = value;
}
}
public ConsoleColor ErrorAccentColor
{
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
get
{
return _ui.ErrorAccentColor;
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
set
{
_ui.ErrorAccentColor = value;
}
}
public ConsoleColor ErrorForegroundColor
{
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
get
{
return _ui.ErrorForegroundColor;
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
set
{
_ui.ErrorForegroundColor = value;
}
}
public ConsoleColor ErrorBackgroundColor
{
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
get
{
return _ui.ErrorBackgroundColor;
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
set
{
_ui.ErrorBackgroundColor = value;
}
}
public ConsoleColor WarningForegroundColor
{
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
get
{
return _ui.WarningForegroundColor;
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
set
{
_ui.WarningForegroundColor = value;
}
}
public ConsoleColor WarningBackgroundColor
{
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
get
{
return _ui.WarningBackgroundColor;
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
set
{
_ui.WarningBackgroundColor = value;
}
}
public ConsoleColor DebugForegroundColor
{
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
get
{
return _ui.DebugForegroundColor;
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
set
{
_ui.DebugForegroundColor = value;
}
}
public ConsoleColor DebugBackgroundColor
{
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
get
{
return _ui.DebugBackgroundColor;
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
set
{
_ui.DebugBackgroundColor = value;
}
}
public ConsoleColor VerboseForegroundColor
{
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
get
{
return _ui.VerboseForegroundColor;
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
set
{
_ui.VerboseForegroundColor = value;
}
}
public ConsoleColor VerboseBackgroundColor
{
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
get
{
return _ui.VerboseBackgroundColor;
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
set
{
_ui.VerboseBackgroundColor = value;
}
}
public ConsoleColor ProgressForegroundColor
{
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
get
{
return _ui.ProgressForegroundColor;
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
set
{
_ui.ProgressForegroundColor = value;
}
}
public ConsoleColor ProgressBackgroundColor
{
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
get
{
return _ui.ProgressBackgroundColor;
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
set
{
_ui.ProgressBackgroundColor = value;
}
}
}
/// <summary>
/// Return the actual console host object so that the user can get at
/// the unproxied methods.
/// </summary>
public override PSObject PrivateData
{
get
{
if (ui == null) return null;
return _consoleColorProxy ??= PSObject.AsPSObject(new ConsoleColorProxy(ui));
}
}
private PSObject _consoleColorProxy;
/// <summary>
/// See base class.
/// </summary>
/// <value></value>
/// <exception/>
public override System.Globalization.CultureInfo CurrentCulture
{
get
{
lock (hostGlobalLock)
{
return CultureInfo.CurrentCulture;
}
}
}
/// <summary>
/// See base class.
/// </summary>
/// <value></value>
/// <exception/>
public override System.Globalization.CultureInfo CurrentUICulture
{
get
{
lock (hostGlobalLock)
{
return CultureInfo.CurrentUICulture;
}
}
}
/// <summary>
/// </summary>
/// <exception/>
public override void SetShouldExit(int exitCode)
{
lock (hostGlobalLock)
{
// Check for the pushed runspace scenario.
if (this.IsRunspacePushed)
{
this.PopRunspace();
}
else if (InDebugMode)
{
ExitDebugMode(DebuggerResumeAction.Continue);
}
else
{
_setShouldExitCalled = true;
_exitCodeFromRunspace = exitCode;
ShouldEndSession = true;
}
}
}
/// <summary>
/// If an input loop is running, then starts a new, nested input loop. If an input loop is not running,
/// throws an exception.
/// </summary>
/// <exception cref="InvalidOperationException">
/// If a nested prompt is entered while the host is not running at least one prompt loop.
/// </exception>
public override void EnterNestedPrompt()
{
// save the old Executor, then clear it so that a break does not cancel the pipeline from which this method
// might be called.
Executor oldCurrent = Executor.CurrentExecutor;
try
{
// this assignment is threadsafe -- protected in CurrentExecutor property
Executor.CurrentExecutor = null;
lock (hostGlobalLock)
{
IsNested = oldCurrent != null || this.ui.IsCommandCompletionRunning;
}
InputLoop.RunNewInputLoop(this, IsNested);
}
finally
{
Executor.CurrentExecutor = oldCurrent;
}
}
/// <summary>
/// See base class.
/// </summary>
/// <exception cref="InvalidOperationException">
/// If there is no nested prompt.
/// </exception>
public override void ExitNestedPrompt()
{
lock (hostGlobalLock)
{
IsNested = InputLoop.ExitCurrentLoop();
}
}
/// <summary>
/// See base class.
/// </summary>
public override void NotifyBeginApplication()
{
lock (hostGlobalLock)
{
++_beginApplicationNotifyCount;
if (_beginApplicationNotifyCount == 1)
{
// save the window title when first notified.
_savedWindowTitle = ui.RawUI.WindowTitle;
#if !UNIX
if (_initialConsoleMode != ConsoleControl.ConsoleModes.Unknown)
{
var activeScreenBufferHandle = ConsoleControl.GetActiveScreenBufferHandle();
_savedConsoleMode = ConsoleControl.GetMode(activeScreenBufferHandle);
ConsoleControl.SetMode(activeScreenBufferHandle, _initialConsoleMode);
}
#endif
}
}
}
/// <summary>
/// See base class
/// <seealso cref="NotifyBeginApplication"/>
/// </summary>
public override void NotifyEndApplication()
{
lock (hostGlobalLock)
{
Dbg.Assert(_beginApplicationNotifyCount > 0, "Not running an executable - NotifyBeginApplication was not called!");
--_beginApplicationNotifyCount;
if (_beginApplicationNotifyCount == 0)
{
// restore the window title when the last application started has ended.
ui.RawUI.WindowTitle = _savedWindowTitle;
#if !UNIX
if (_savedConsoleMode != ConsoleControl.ConsoleModes.Unknown)
{
ConsoleControl.SetMode(ConsoleControl.GetActiveScreenBufferHandle(), _savedConsoleMode);
}
#endif
}
}
}
#if LEGACYTELEMETRY
bool IHostProvidesTelemetryData.HostIsInteractive
{
get
{
return !s_cpp.NonInteractive && !s_cpp.AbortStartup &&
((s_cpp.InitialCommand == null && s_cpp.File == null) || s_cpp.NoExit);
}
}
double IHostProvidesTelemetryData.ProfileLoadTimeInMS { get { return _profileLoadTimeInMS; } }
double IHostProvidesTelemetryData.ReadyForInputTimeInMS { get { return _readyForInputTimeInMS; } }
int IHostProvidesTelemetryData.InteractiveCommandCount { get { return _interactiveCommandCount; } }
private double _readyForInputTimeInMS;
private int _interactiveCommandCount;
#endif
private double _profileLoadTimeInMS;
#endregion overrides
#region non-overrides
/// <summary>
/// Constructs a new instance.
/// </summary>
internal ConsoleHost()
{
#if !UNIX
try
{
// Capture the initial console mode before constructing our ui - that constructor
// will change the console mode to turn on VT100 support.
_initialConsoleMode = ConsoleControl.GetMode(ConsoleControl.GetActiveScreenBufferHandle());
}
catch (Exception)
{
// Make sure we skip setting the console mode later.
_savedConsoleMode = _initialConsoleMode = ConsoleControl.ConsoleModes.Unknown;
}
#endif
Thread.CurrentThread.CurrentUICulture = this.CurrentUICulture;
Thread.CurrentThread.CurrentCulture = this.CurrentCulture;
// BUG: 610329. Tell PowerShell engine to apply console
// related properties while launching Pipeline Execution
// Thread.
base.ShouldSetThreadUILanguageToZero = true;
InDebugMode = false;
_displayDebuggerBanner = true;
this.ui = new ConsoleHostUserInterface(this);
_consoleWriter = new ConsoleTextWriter(ui);
UnhandledExceptionEventHandler handler = new UnhandledExceptionEventHandler(UnhandledExceptionHandler);
AppDomain.CurrentDomain.UnhandledException += handler;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Performance",
"CA1822:Mark members as static",
Justification = "Accesses instance members in preprocessor branch.")]
private void BindBreakHandler()
{
#if UNIX
Console.CancelKeyPress += new ConsoleCancelEventHandler(MyBreakHandler);
#else
breakHandlerGcHandle = GCHandle.Alloc(new ConsoleControl.BreakHandler(MyBreakHandler));
ConsoleControl.AddBreakHandler((ConsoleControl.BreakHandler)breakHandlerGcHandle.Target);
#endif
}
private void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args)
{
// FYI: sender is a reference to the source app domain
// The CLR will shut down the app as soon as this handler is complete, but just in case, we want
// to exit at our next opportunity.
_shouldEndSession = true;
Exception e = null;
if (args != null)
{
e = (Exception)args.ExceptionObject;
}
ui.WriteLine();
ui.Write(
ConsoleColor.Red,
ui.RawUI.BackgroundColor,
ConsoleHostStrings.UnhandledExceptionShutdownMessage);
ui.WriteLine();
}
/// <summary>
/// Disposes of this instance, per the IDisposable pattern.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool isDisposingNotFinalizing)
{
if (!_isDisposed)
{
#if !UNIX
ConsoleControl.RemoveBreakHandler();
if (breakHandlerGcHandle.IsAllocated)
{
breakHandlerGcHandle.Free();
}
#endif
if (isDisposingNotFinalizing)
{
if (IsTranscribing)
{
StopTranscribing();
}
if (_outputSerializer != null)
{
_outputSerializer.End();
}
if (_errorSerializer != null)
{
_errorSerializer.End();
}
if (_runspaceRef != null)
{
// NTRAID#Windows Out Of Band Releases-925297-2005/12/14
try
{
_runspaceRef.Runspace.Dispose();
}
catch (InvalidRunspaceStateException)
{
}
}
_runspaceRef = null;
ui = null;
}
}
_isDisposed = true;
}
/// <summary>
/// Finalizes an instance of the <see cref="ConsoleHost"/> class.
/// </summary>
~ConsoleHost()
{
Dispose(false);
}
/// <summary>
/// Indicates if the session should be terminated or not. Typically set by the break handler for Close, Logoff, and
/// Shutdown events. Note that the only valid transition for this property is from false to true: it is not legal to
/// try to set it to false after is was set to true.
/// </summary>
/// <value>
/// true to shut down the session. false is only allowed if the property is already false.
/// </value>
internal bool ShouldEndSession
{
// This might get called from the main thread, or from the pipeline thread, or from a break handler thread.
get
{
bool result = false;
lock (hostGlobalLock)
{
result = _shouldEndSession;
}
return result;
}
set
{
lock (hostGlobalLock)
{
// If ShouldEndSession is already true, you can't set it back
Dbg.Assert(!_shouldEndSession || value,
"ShouldEndSession can only be set from false to true");
_shouldEndSession = value;
}
}
}
/// <summary>
/// The Runspace ref object being used by this Host instance. A host only opens one Runspace.
/// </summary>
/// <value></value>
internal RunspaceRef RunspaceRef
{
get
{
return _runspaceRef;
}
}
internal WrappedSerializer.DataFormat OutputFormat { get; private set; }
internal bool OutputFormatSpecified { get; private set; }
internal WrappedSerializer.DataFormat InputFormat { get; private set; }
internal WrappedDeserializer.DataFormat ErrorFormat
{
get
{
WrappedDeserializer.DataFormat format = OutputFormat;
// If this shell is invoked in non-interactive, error is redirected, and OutputFormat was not
// specified write data in error stream in xml format assuming PowerShell->PowerShell usage.
if (!OutputFormatSpecified && !IsInteractive && Console.IsErrorRedirected && _wasInitialCommandEncoded)
{
format = Serialization.DataFormat.XML;
}
return format;
}
}
internal bool IsRunningAsync
{
get
{
return !IsInteractive && ((OutputFormat != Serialization.DataFormat.Text) || Console.IsInputRedirected);
}
}
internal bool IsNested { get; private set; }
internal WrappedSerializer OutputSerializer
{
get
{
if (_outputSerializer == null)
{
_outputSerializer =
new WrappedSerializer(
OutputFormat,
"Output",
Console.IsOutputRedirected ? Console.Out : ConsoleTextWriter);
}
return _outputSerializer;
}
}
internal WrappedSerializer ErrorSerializer
{
get
{
if (_errorSerializer == null)
{
_errorSerializer =
new WrappedSerializer(
ErrorFormat,
"Error",
Console.IsErrorRedirected ? Console.Error : ConsoleTextWriter);
}
return _errorSerializer;
}
}
internal bool IsInteractive
{
get
{
// we're running interactive if we're in a prompt loop, and we're not reading keyboard input from stdin.
return _isRunningPromptLoop && !ui.ReadFromStdin;
}
}
internal TextWriter ConsoleTextWriter
{
get
{
Dbg.Assert(_consoleWriter != null, "consoleWriter should have been initialized");
return _consoleWriter;
}
}
/// <summary>
/// The main run loop of the program: processes command line parameters, and starts up a runspace.
/// </summary>
/// <param name="cpp">
/// Commandline parameter parser. The commandline parameter parser is expected to parse all the
/// arguments before calling this method.
/// </param>
/// <param name="isPrestartWarned">
/// Is there any warning at startup
/// </param>
/// <returns>
/// The process exit code to be returned by Main.
/// </returns>
private uint Run(CommandLineParameterParser cpp, bool isPrestartWarned)
{
Dbg.Assert(cpp != null, "CommandLine parameter parser cannot be null.");
uint exitCode = ExitCodeSuccess;
do
{
s_runspaceInitTracer.WriteLine("starting parse of command line parameters");
exitCode = ExitCodeSuccess;
if (!string.IsNullOrEmpty(cpp.InitialCommand) && isPrestartWarned)
{
s_tracer.TraceError("Start up warnings made command \"{0}\" not executed", cpp.InitialCommand);
string msg = StringUtil.Format(ConsoleHostStrings.InitialCommandNotExecuted, cpp.InitialCommand);
ui.WriteErrorLine(msg);
exitCode = ExitCodeInitFailure;
break;
}
if (cpp.AbortStartup)
{
s_tracer.WriteLine("processing of cmdline args failed, exiting");
exitCode = cpp.ExitCode;
break;
}
OutputFormat = cpp.OutputFormat;
OutputFormatSpecified = cpp.OutputFormatSpecified;
InputFormat = cpp.InputFormat;
_wasInitialCommandEncoded = cpp.WasInitialCommandEncoded;
ui.ReadFromStdin = cpp.ExplicitReadCommandsFromStdin || Console.IsInputRedirected;
ui.NoPrompt = cpp.NoPrompt;
ui.ThrowOnReadAndPrompt = cpp.ThrowOnReadAndPrompt;
_noExit = cpp.NoExit;
#if !UNIX
// See if we need to change the process-wide execution
// policy
if (!string.IsNullOrEmpty(cpp.ExecutionPolicy))
{
ExecutionPolicy executionPolicy = SecuritySupport.ParseExecutionPolicy(cpp.ExecutionPolicy);
SecuritySupport.SetExecutionPolicy(ExecutionPolicyScope.Process, executionPolicy, null);
}
#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
exitCode = DoRunspaceLoop(cpp.InitialCommand, cpp.SkipProfiles, cpp.Args, cpp.StaMode, cpp.ConfigurationName);
}
while (false);
return exitCode;
}
/// <summary>
/// Loops over the Host's sole Runspace; opens the runspace, initializes it, then recycles it if the Runspace fails.
/// </summary>
/// <returns>
/// The process exit code to be returned by Main.
/// </returns>
private uint DoRunspaceLoop(string initialCommand, bool skipProfiles, Collection<CommandParameter> initialCommandArgs, bool staMode, string configurationName)
{
ExitCode = ExitCodeSuccess;
while (!ShouldEndSession)
{
RunspaceCreationEventArgs args = new RunspaceCreationEventArgs(initialCommand, skipProfiles, staMode, configurationName, initialCommandArgs);
CreateRunspace(args);
if (ExitCode == ExitCodeInitFailure) { break; }
if (!_noExit)
{
// Wait for runspace to open, init, and run init script before
// setting ShouldEndSession, to allow debugger to work.
ShouldEndSession = true;
}
else
{
// Start nested prompt loop.
EnterNestedPrompt();
}
if (_setShouldExitCalled)
{
ExitCode = unchecked((uint)_exitCodeFromRunspace);
}
else
{
Executor exec = new Executor(this, false, false);
bool dollarHook = exec.ExecuteCommandAndGetResultAsBool("$global:?") ?? false;
if (dollarHook && (_lastRunspaceInitializationException == null))
{
ExitCode = ExitCodeSuccess;
}
else
{
ExitCode = ExitCodeSuccess | 0x1;
}
}
_runspaceRef.Runspace.Close();
_runspaceRef = null;
if (staMode)
{
// don't continue the session in STA mode
ShouldEndSession = true;
}
}
return ExitCode;
}
private Exception InitializeRunspaceHelper(string command, Executor exec, Executor.ExecutionOptions options)
{
Dbg.Assert(!string.IsNullOrEmpty(command), "command should have a value");
Dbg.Assert(exec != null, "non-null Executor instance needed");
s_runspaceInitTracer.WriteLine("running command {0}", command);
Exception e = null;
if (IsRunningAsync)
{
exec.ExecuteCommandAsync(command, out e, options);
}
else
{
exec.ExecuteCommand(command, out e, options);
}
if (e != null)
{
ReportException(e, exec);
}
return e;
}
private void CreateRunspace(object runspaceCreationArgs)
{
RunspaceCreationEventArgs args = null;
try
{
args = runspaceCreationArgs as RunspaceCreationEventArgs;
Dbg.Assert(args != null, "Event Arguments to CreateRunspace should not be null");
DoCreateRunspace(args.InitialCommand, args.SkipProfiles, args.StaMode, args.ConfigurationName, args.InitialCommandArgs);
}
catch (ConsoleHostStartupException startupException)
{
ReportExceptionFallback(startupException.InnerException, startupException.Message);
ExitCode = ExitCodeInitFailure;
}
}
/// <summary>
/// Check if a screen reviewer utility is running.
/// When a screen reader is running, we don't auto-load the PSReadLine module at startup,
/// since PSReadLine is not accessibility-firendly enough as of today.
/// </summary>
private bool IsScreenReaderActive()
{
if (_screenReaderActive.HasValue)
{
return _screenReaderActive.Value;
}
_screenReaderActive = false;
if (Platform.IsWindowsDesktop)
{
// Note: this API can detect if a third-party screen reader is active, such as NVDA, but not the in-box Windows Narrator.
// Quoted from https://docs.microsoft.com/windows/win32/api/winuser/nf-winuser-systemparametersinfoa about the
// accessibility parameter 'SPI_GETSCREENREADER':
// "Narrator, the screen reader that is included with Windows, does not set the SPI_SETSCREENREADER or SPI_GETSCREENREADER flags."
bool enabled = false;
if (SystemParametersInfo(SPI_GETSCREENREADER, 0, ref enabled, 0))
{
_screenReaderActive = enabled;
}
}
return _screenReaderActive.Value;
}
private static bool LoadPSReadline()
{
// Don't load PSReadline if:
// * we don't think the process will be interactive, e.g. -command or -file
// - exception: when -noexit is specified, we will be interactive after the command/file finishes
// * -noninteractive: this should be obvious, they've asked that we don't ever prompt
//
// Note that PSReadline doesn't support redirected stdin/stdout, but we don't check that here because
// a future version might, and we should automatically load it at that unknown point in the future.
// PSReadline will ideally fall back to Console.ReadLine or whatever when stdin/stdout is redirected.
return ((s_cpp.InitialCommand == null && s_cpp.File == null) || s_cpp.NoExit) && !s_cpp.NonInteractive;
}
/// <summary>
/// Opens and Initializes the Host's sole Runspace. Processes the startup scripts and runs any command passed on the
/// command line.
/// </summary>
private void DoCreateRunspace(string initialCommand, bool skipProfiles, bool staMode, string configurationName, Collection<CommandParameter> initialCommandArgs)
{
Dbg.Assert(_runspaceRef == null, "runspace should be null");
Dbg.Assert(DefaultInitialSessionState != null, "DefaultInitialSessionState should not be null");
s_runspaceInitTracer.WriteLine("Calling RunspaceFactory.CreateRunspace");
try
{
Runspace consoleRunspace = null;
bool psReadlineFailed = false;
// Load PSReadline by default unless there is no use:
// - screen reader is active, such as NVDA, indicating non-visual access
// - we're running a command/file and just exiting
// - stdin is redirected by a parent process
// - we're not interactive
// - we're explicitly reading from stdin (the '-' argument)
// It's also important to have a scenario where PSReadline is not loaded so it can be updated, e.g.
// powershell -command "Update-Module PSReadline"
// This should work just fine as long as no other instances of PowerShell are running.
ReadOnlyCollection<ModuleSpecification> defaultImportModulesList = null;
if (LoadPSReadline())
{
if (IsScreenReaderActive())
{
s_theConsoleHost.UI.WriteLine(ManagedEntranceStrings.PSReadLineDisabledWhenScreenReaderIsActive);
s_theConsoleHost.UI.WriteLine();
}
else
{
// Create and open Runspace with PSReadline.
defaultImportModulesList = DefaultInitialSessionState.Modules;
DefaultInitialSessionState.ImportPSModule(new[] { "PSReadLine" });
consoleRunspace = RunspaceFactory.CreateRunspace(this, DefaultInitialSessionState);
try
{
OpenConsoleRunspace(consoleRunspace, staMode);
}
catch (Exception)
{
consoleRunspace = null;
psReadlineFailed = true;
}
}
}
if (consoleRunspace == null)
{
if (psReadlineFailed)
{
// Try again but without importing the PSReadline module.
DefaultInitialSessionState.ClearPSModules();
DefaultInitialSessionState.ImportPSModule(defaultImportModulesList);
}
consoleRunspace = RunspaceFactory.CreateRunspace(this, DefaultInitialSessionState);
OpenConsoleRunspace(consoleRunspace, staMode);
}
Runspace.PrimaryRunspace = consoleRunspace;
_runspaceRef = new RunspaceRef(consoleRunspace);
if (psReadlineFailed)
{
// Notify the user that PSReadline could not be loaded.
Console.Error.WriteLine(ConsoleHostStrings.CannotLoadPSReadline);
}
}
catch (Exception e)
{
// no need to do CheckForSevereException here
// since the ConsoleHostStartupException is uncaught
// higher in the call stack and the whole process
// will exit soon
throw new ConsoleHostStartupException(ConsoleHostStrings.ShellCannotBeStarted, e);
}
finally
{
// Stop PerfTrack
PSEtwLog.LogOperationalInformation(PSEventId.Perftrack_ConsoleStartupStop, PSOpcode.WinStop,
PSTask.PowershellConsoleStartup, PSKeyword.UseAlwaysOperational);
}
#if LEGACYTELEMETRY
// Record how long it took from process start to runspace open for telemetry.
_readyForInputTimeInMS = (DateTime.Now - Process.GetCurrentProcess().StartTime).TotalMilliseconds;
#endif
DoRunspaceInitialization(skipProfiles, initialCommand, configurationName, initialCommandArgs);
}
private static void OpenConsoleRunspace(Runspace runspace, bool staMode)
{
if (staMode && Platform.IsWindowsDesktop)
{
runspace.ApartmentState = ApartmentState.STA;
}
runspace.ThreadOptions = PSThreadOptions.ReuseThread;
runspace.EngineActivityId = EtwActivity.GetActivityId();
s_runspaceInitTracer.WriteLine("Calling Runspace.Open");
runspace.Open();
}
private void DoRunspaceInitialization(bool skipProfiles, string initialCommand, string configurationName, Collection<CommandParameter> initialCommandArgs)
{
if (_runspaceRef.Runspace.Debugger != null)
{
_runspaceRef.Runspace.Debugger.SetDebugMode(DebugModes.LocalScript | DebugModes.RemoteScript);
_runspaceRef.Runspace.Debugger.DebuggerStop += this.OnExecutionSuspended;
}
Executor exec = new Executor(this, false, false);
// If working directory was specified, set it
if (s_cpp != null && s_cpp.WorkingDirectory != null)
{
Pipeline tempPipeline = exec.CreatePipeline();
var command = new Command("Set-Location");
command.Parameters.Add("LiteralPath", s_cpp.WorkingDirectory);
tempPipeline.Commands.Add(command);
Exception exception;
if (IsRunningAsync)
{
exec.ExecuteCommandAsyncHelper(tempPipeline, out exception, Executor.ExecutionOptions.AddOutputter);
}
else
{
exec.ExecuteCommandHelper(tempPipeline, out exception, Executor.ExecutionOptions.AddOutputter);
}
if (exception != null)
{
_lastRunspaceInitializationException = exception;
ReportException(exception, exec);
}
}
if (!string.IsNullOrEmpty(configurationName))
{
// If an endpoint configuration is specified then create a loop-back remote runspace targeting
// the endpoint and push onto runspace ref stack. Ignore profile and configuration scripts.
try
{
RemoteRunspace remoteRunspace = HostUtilities.CreateConfiguredRunspace(configurationName, this);
remoteRunspace.ShouldCloseOnPop = true;
PushRunspace(remoteRunspace);
// Ensure that session ends when configured remote runspace is popped.
_inPushedConfiguredSession = true;
}
catch (Exception e)
{
throw new ConsoleHostStartupException(ConsoleHostStrings.ShellCannotBeStarted, e);
}
}
else
{
const string shellId = "Microsoft.PowerShell";
// If the system lockdown policy says "Enforce", do so. Do this after types / formatting, default functions, etc
// are loaded so that they are trusted. (Validation of their signatures is done in F&O)
Utils.EnforceSystemLockDownLanguageMode(_runspaceRef.Runspace.ExecutionContext);
string allUsersProfile = HostUtilities.GetFullProfileFileName(null, false);
string allUsersHostSpecificProfile = HostUtilities.GetFullProfileFileName(shellId, false);
string currentUserProfile = HostUtilities.GetFullProfileFileName(null, true);
string currentUserHostSpecificProfile = HostUtilities.GetFullProfileFileName(shellId, true);
// $PROFILE has to be set from the host
// Should be "per-user,host-specific profile.ps1"
// This should be set even if -noprofile is specified
_runspaceRef.Runspace.SessionStateProxy.SetVariable("PROFILE",
HostUtilities.GetDollarProfile(
allUsersProfile,
allUsersHostSpecificProfile,
currentUserProfile,
currentUserHostSpecificProfile));
if (!skipProfiles)
{
// Run the profiles.
// Profiles are run in the following order:
// 1. host independent profile meant for all users
// 2. host specific profile meant for all users
// 3. host independent profile of the current user
// 4. host specific profile of the current user
var sw = new Stopwatch();
sw.Start();
RunProfile(allUsersProfile, exec);
RunProfile(allUsersHostSpecificProfile, exec);
RunProfile(currentUserProfile, exec);
RunProfile(currentUserHostSpecificProfile, exec);
sw.Stop();
var profileLoadTimeInMs = sw.ElapsedMilliseconds;
if (profileLoadTimeInMs > 500 && s_cpp.ShowBanner)
{
Console.Error.WriteLine(ConsoleHostStrings.SlowProfileLoadingMessage, profileLoadTimeInMs);
}
_profileLoadTimeInMS = profileLoadTimeInMs;
}
else
{
s_tracer.WriteLine("-noprofile option specified: skipping profiles");
}
}
#if LEGACYTELEMETRY
// Startup is reported after possibly running the profile, but before running the initial command (or file)
// if one is specified.
TelemetryAPI.ReportStartupTelemetry(this);
#endif
// If a file was specified as the argument to run, then run it...
if (s_cpp != null && s_cpp.File != null)
{
string filePath = s_cpp.File;
s_tracer.WriteLine("running -file '{0}'", filePath);
Pipeline tempPipeline = exec.CreatePipeline();
Command c;
#if UNIX
// if file doesn't have .ps1 extension, we read the contents and treat it as a script to support shebang with no .ps1 extension usage
if (!filePath.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase))
{
string script = File.ReadAllText(filePath);
c = new Command(script, isScript: true, useLocalScope: false);
}
else
#endif
{
c = new Command(filePath, false, false);
}
tempPipeline.Commands.Add(c);
if (initialCommandArgs != null)
{
// add the args passed to the command.
foreach (CommandParameter p in initialCommandArgs)
{
c.Parameters.Add(p);
}
}
// If we're not going to continue, then get the exit code out of the runspace and
// and indicate that it should be returned...
if (!_noExit && this.Runspace is not RemoteRunspace)
{
this.Runspace.ExecutionContext.ScriptCommandProcessorShouldRethrowExit = true;
}
Exception e1;
if (IsRunningAsync)
{
Executor.ExecutionOptions executionOptions = Executor.ExecutionOptions.AddOutputter;
Token[] tokens;
ParseError[] errors;
// Detect if they're using input. If so, read from it.
Ast parsedInput = Parser.ParseFile(filePath, out tokens, out errors);
if (AstSearcher.IsUsingDollarInput(parsedInput))
{
executionOptions |= Executor.ExecutionOptions.ReadInputObjects;
// We will consume all of the input to pass to the script, so don't try to read commands from stdin.
ui.ReadFromStdin = false;
}
exec.ExecuteCommandAsyncHelper(tempPipeline, out e1, executionOptions);
}
else
{
exec.ExecuteCommandHelper(tempPipeline, out e1, Executor.ExecutionOptions.AddOutputter);
}
// Pipeline.Invoke has thrown, that's bad. It means the script did not actually
// execute properly. These exceptions should be reflected in the exit code
if (e1 != null)
{
if (!_noExit)
{
// Set ExitCode to 0x1
lock (hostGlobalLock)
{
_setShouldExitCalled = true;
_exitCodeFromRunspace = 0x1;
ShouldEndSession = true;
}
}
ReportException(e1, exec);
}
}
else if (!string.IsNullOrEmpty(initialCommand))
{
// Run the command passed on the command line
s_tracer.WriteLine("running initial command");
Pipeline tempPipeline = exec.CreatePipeline(initialCommand, true);
if (initialCommandArgs != null)
{
// add the args passed to the command.
foreach (CommandParameter p in initialCommandArgs)
{
tempPipeline.Commands[0].Parameters.Add(p);
}
}
Exception e1;
if (IsRunningAsync)
{
Executor.ExecutionOptions executionOptions = Executor.ExecutionOptions.AddOutputter;
Token[] tokens;
ParseError[] errors;
// Detect if they're using input. If so, read from it.
Ast parsedInput = Parser.ParseInput(initialCommand, out tokens, out errors);
if (AstSearcher.IsUsingDollarInput(parsedInput))
{
executionOptions |= Executor.ExecutionOptions.ReadInputObjects;
// We will consume all of the input to pass to the script, so don't try to read commands from stdin.
ui.ReadFromStdin = false;
}
exec.ExecuteCommandAsyncHelper(tempPipeline, out e1, executionOptions);
}
else
{
exec.ExecuteCommandHelper(tempPipeline, out e1, Executor.ExecutionOptions.AddOutputter);
}
if (e1 != null)
{
// Remember last exception
_lastRunspaceInitializationException = e1;
ReportException(e1, exec);
}
}
}
private void RunProfile(string profileFileName, Executor exec)
{
if (!string.IsNullOrEmpty(profileFileName))
{
s_runspaceInitTracer.WriteLine("checking profile" + profileFileName);
try
{
if (File.Exists(profileFileName))
{
InitializeRunspaceHelper(
". '" + EscapeSingleQuotes(profileFileName) + "'",
exec,
Executor.ExecutionOptions.AddOutputter);
}
else
{
s_runspaceInitTracer.WriteLine("profile file not found");
}
}
catch (Exception e) // Catch-all OK, 3rd party callout
{
ReportException(e, exec);
s_runspaceInitTracer.WriteLine("Could not load profile.");
}
}
}
/// <summary>
/// Escapes backtick and tick characters with a backtick, returns the result.
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
internal static string EscapeSingleQuotes(string str)
{
// worst case we have to escape every character, so capacity is twice as large as input length
StringBuilder sb = new StringBuilder(str.Length * 2);
for (int i = 0; i < str.Length; ++i)
{
char c = str[i];
if (c == '\'')
{
sb.Append(c);
}
sb.Append(c);
}
string result = sb.ToString();
return result;
}
private void WriteErrorLine(string line)
{
const ConsoleColor fg = ConsoleColor.Red;
ConsoleColor bg = UI.RawUI.BackgroundColor;
UI.WriteLine(fg, bg, line);
}
// NTRAID#Windows Out Of Band Releases-915506-2005/09/09
// Removed HandleUnexpectedExceptions infrastructure
private void ReportException(Exception e, Executor exec)
{
Dbg.Assert(e != null, "must supply an Exception");
Dbg.Assert(exec != null, "must supply an Executor");
// NTRAID#Windows Out Of Band Releases-915506-2005/09/09
// Removed HandleUnexpectedExceptions infrastructure
// Attempt to write the exception into the error stream so that the normal F&O machinery will
// display it according to preferences.
object error = null;
Pipeline tempPipeline = exec.CreatePipeline();
// NTRAID#Windows OS Bugs-1143621-2005/04/08-sburns
IContainsErrorRecord icer = e as IContainsErrorRecord;
if (icer != null)
{
error = icer.ErrorRecord;
}
else
{
error = (object)new ErrorRecord(e, "ConsoleHost.ReportException", ErrorCategory.NotSpecified, null);
}
PSObject wrappedError = new PSObject(error)
{
WriteStream = WriteStreamType.Error
};
Exception e1 = null;
tempPipeline.Input.Write(wrappedError);
if (IsRunningAsync)
{
exec.ExecuteCommandAsyncHelper(tempPipeline, out e1, Executor.ExecutionOptions.AddOutputter);
}
else
{
exec.ExecuteCommandHelper(tempPipeline, out e1, Executor.ExecutionOptions.AddOutputter);
}
if (e1 != null)
{
// that didn't work. Write out the error ourselves as a last resort.
ReportExceptionFallback(e, null);
}
}
/// <summary>
/// Reports an exception according to the exception reporting settings in effect.
/// </summary>
/// <param name="e">
/// The exception to report.
/// </param>
/// <param name="header">
/// Optional header message. Empty or null means "no header"
/// </param>
private void ReportExceptionFallback(Exception e, string header)
{
if (!string.IsNullOrEmpty(header))
{
Console.Error.WriteLine(header);
}
if (e == null)
{
return;
}
// See if the exception has an error record attached to it...
ErrorRecord er = null;
IContainsErrorRecord icer = e as IContainsErrorRecord;
if (icer != null)
er = icer.ErrorRecord;
if (e is PSRemotingTransportException)
{
// For remoting errors use full fidelity error writer.
UI.WriteErrorLine(e.Message);
}
else if (e is TargetInvocationException)
{
Console.Error.WriteLine(e.InnerException.Message);
}
else
{
Console.Error.WriteLine(e.Message);
}
// Add the position message for the error if it's available.
if (er != null && er.InvocationInfo != null)
Console.Error.WriteLine(er.InvocationInfo.PositionMessage);
}
/// <summary>
/// Raised when the host pops a runspace.
/// </summary>
internal event EventHandler RunspacePopped;
/// <summary>
/// Raised when the host pushes a runspace.
/// </summary>
internal event EventHandler RunspacePushed;
#endregion non-overrides
#region debugger
/// <summary>
/// Handler for debugger events.
/// </summary>
private void OnExecutionSuspended(object sender, DebuggerStopEventArgs e)
{
// Check local runspace internalHost to see if debugging is enabled.
LocalRunspace localrunspace = LocalRunspace;
if ((localrunspace != null) && !localrunspace.ExecutionContext.EngineHostInterface.DebuggerEnabled) { return; }
_debuggerStopEventArgs = e;
InputLoop baseLoop = null;
try
{
if (this.IsRunspacePushed)
{
// For remote debugging block data coming from the main (not-nested)
// running command.
baseLoop = InputLoop.GetNonNestedLoop();
if (baseLoop != null)
{
baseLoop.BlockCommandOutput();
}
}
//
// Display the banner only once per session
//
if (_displayDebuggerBanner)
{
WriteDebuggerMessage(ConsoleHostStrings.EnteringDebugger);
WriteDebuggerMessage(string.Empty);
_displayDebuggerBanner = false;
}
//
// If we hit a breakpoint output its info
//
if (e.Breakpoints.Count > 0)
{
string format = ConsoleHostStrings.HitBreakpoint;
foreach (Breakpoint breakpoint in e.Breakpoints)
{
WriteDebuggerMessage(string.Format(CultureInfo.CurrentCulture, format, breakpoint));
}
WriteDebuggerMessage(string.Empty);
}
//
// Write the source line
//
if (e.InvocationInfo != null)
{
// line = StringUtil.Format(ConsoleHostStrings.DebuggerSourceCodeFormat, scriptFileName, e.InvocationInfo.ScriptLineNumber, e.InvocationInfo.Line);
WriteDebuggerMessage(e.InvocationInfo.PositionMessage);
}
//
// Start the debug mode
//
EnterDebugMode();
}
finally
{
_debuggerStopEventArgs = null;
if (baseLoop != null)
{
baseLoop.ResumeCommandOutput();
}
}
}
/// <summary>
/// Returns true if the host is in debug mode.
/// </summary>
private bool InDebugMode { get; set; }
/// <summary>
/// True when debugger command is user and available
/// for stopping.
/// </summary>
internal bool DebuggerCanStopCommand
{
get;
set;
}
private Exception _lastRunspaceInitializationException = null;
internal uint ExitCode;
/// <summary>
/// Sets the host to debug mode and enters a nested prompt.
/// </summary>
private void EnterDebugMode()
{
InDebugMode = true;
try
{
//
// Note that we need to enter the nested prompt via the InternalHost interface.
//
// EnterNestedPrompt must always be run on the local runspace.
Runspace runspace = _runspaceRef.OldRunspace ?? this.RunspaceRef.Runspace;
runspace.ExecutionContext.EngineHostInterface.EnterNestedPrompt();
}
catch (PSNotImplementedException)
{
WriteDebuggerMessage(ConsoleHostStrings.SessionDoesNotSupportDebugger);
}
finally
{
InDebugMode = false;
}
}
/// <summary>
/// Exits the debugger's nested prompt.
/// </summary>
private void ExitDebugMode(DebuggerResumeAction resumeAction)
{
_debuggerStopEventArgs.ResumeAction = resumeAction;
try
{
//
// Note that we need to exit the nested prompt via the InternalHost interface.
//
// ExitNestedPrompt must always be run on the local runspace.
Runspace runspace = _runspaceRef.OldRunspace ?? this.RunspaceRef.Runspace;
runspace.ExecutionContext.EngineHostInterface.ExitNestedPrompt();
}
catch (ExitNestedPromptException)
{
// ignore the exception
}
}
/// <summary>
/// Writes a line using the debugger colors.
/// </summary>
private void WriteDebuggerMessage(string line)
{
this.ui.WriteLine(this.ui.DebugForegroundColor, this.ui.DebugBackgroundColor, line);
}
#endregion debugger
#region aux classes
/// <summary>
/// InputLoop represents the prompt-input-execute loop of the interactive host. Input loops can be nested, meaning that
/// one input loop can be interrupted and another started; when the second ends, the first resumes.
///
/// Neither this class' instances nor its static data is threadsafe. Caller is responsible for ensuring threadsafe
/// access.
/// </summary>
private sealed class InputLoop
{
internal static void RunNewInputLoop(ConsoleHost parent, bool isNested)
{
// creates an instance and adds it to the stack and starts it running.
int stackCount = s_instanceStack.Count;
if (stackCount == PSHost.MaximumNestedPromptLevel)
{
throw PSTraceSource.NewInvalidOperationException(ConsoleHostStrings.TooManyNestedPromptsError);
}
InputLoop il = new InputLoop(parent, isNested);
s_instanceStack.Push(il);
il.Run(s_instanceStack.Count > 1);
// Once the loop has finished running, remove it from the instance stack.
InputLoop il2 = s_instanceStack.Pop();
Dbg.Assert(il == il2, "top of instance stack does not correspond to the instance pushed");
}
// Presently, this will not work if the Run loop is blocked on a ReadLine call. Whether that's a
// problem or not depends on when we expect calls to this function to be made.
/// <summary>
/// </summary>
/// <returns>True if next input loop is nested, False otherwise.</returns>
/// <exception cref="InvalidOperationException">
/// when there is no instanceStack.Count == 0
/// </exception>
internal static bool ExitCurrentLoop()
{
if (s_instanceStack.Count == 0)
{
throw PSTraceSource.NewInvalidOperationException(ConsoleHostStrings.InputExitCurrentLoopOutOfSyncError);
}
InputLoop il = s_instanceStack.Peek();
il._shouldExit = true;
// The main (non-nested) input loop has Count == 1,
// so Count == 2 is the value that indicates the next
// popped stack input loop is non-nested.
return (s_instanceStack.Count > 2);
}
/// <summary>
/// Returns current root (non-nested) loop only if there is no
/// nesting. This is used *only* by the debugger for remote debugging
/// where data handling on the base commands needs to be blocked
/// during remote debug stop handling.
/// </summary>
/// <returns></returns>
internal static InputLoop GetNonNestedLoop()
{
if (s_instanceStack.Count == 1)
{
return s_instanceStack.Peek();
}
return null;
}
private InputLoop(ConsoleHost parent, bool isNested)
{
_parent = parent;
_isNested = isNested;
_isRunspacePushed = parent.IsRunspacePushed;
parent.RunspacePopped += HandleRunspacePopped;
parent.RunspacePushed += HandleRunspacePushed;
_exec = new Executor(parent, isNested, false);
_promptExec = new Executor(parent, isNested, true);
}
private void HandleRunspacePushed(object sender, EventArgs e)
{
lock (_syncObject)
{
_isRunspacePushed = true;
_runspacePopped = false;
}
}
/// <summary>
/// When a runspace is popped, we need to reevaluate the
/// prompt.
/// </summary>
/// <param name="sender">Sender of this event, unused.</param>
/// <param name="eventArgs">Arguments describing this event, unused.</param>
private void HandleRunspacePopped(object sender, EventArgs eventArgs)
{
lock (_syncObject)
{
_isRunspacePushed = false;
_runspacePopped = true;
}
}
// NTRAID#Windows Out Of Band Releases-915506-2005/09/09
// Removed HandleUnexpectedExceptions infrastructure
/// <summary>
/// Evaluates the prompt, displays it, gets a command from the console, and executes it. Repeats until the command
/// is "exit", or until the shutdown flag is set.
/// </summary>
internal void Run(bool inputLoopIsNested)
{
System.Management.Automation.Host.PSHostUserInterface c = _parent.UI;
ConsoleHostUserInterface ui = c as ConsoleHostUserInterface;
Dbg.Assert(ui != null, "Host.UI should return an instance.");
bool inBlockMode = false;
bool previousResponseWasEmpty = false;
StringBuilder inputBlock = new StringBuilder();
while (!_parent.ShouldEndSession && !_shouldExit)
{
#if !UNIX
if (ui.SupportsVirtualTerminal)
{
// need to re-enable VT mode if it was previously enabled as native commands may have turned it off
ui.TryTurnOnVtMode();
}
#endif
try
{
_parent._isRunningPromptLoop = true;
string prompt = null;
string line = null;
if (!ui.NoPrompt)
{
if (inBlockMode)
{
// use a special prompt that denotes block mode
prompt = ">> ";
}
else
{
// Make sure the cursor is at the start of the line - some external programs don't
// write a newline, so we do that for them.
if (ui.RawUI.CursorPosition.X != 0)
ui.WriteLine();
// Evaluate any suggestions
if (!previousResponseWasEmpty)
{
EvaluateSuggestions(ui);
}
// Then output the prompt
if (_parent.InDebugMode)
{
prompt = EvaluateDebugPrompt();
}
if (prompt == null)
{
prompt = EvaluatePrompt();
}
}
ui.Write(prompt);
}
#if UNIX
if (c.SupportsVirtualTerminal)
{
// enable DECCKM as .NET requires cursor keys to emit VT for Console class
c.Write(DECCKM_ON);
}
#endif
previousResponseWasEmpty = false;
// There could be a profile. So there could be a user defined custom readline command
line = ui.ReadLineWithTabCompletion(_exec);
// line will be null in the case that Ctrl-C terminated the input
if (line == null)
{
previousResponseWasEmpty = true;
s_tracer.WriteLine("line is null");
if (!ui.ReadFromStdin)
{
// If we're not reading from stdin, the we probably got here
// because the user hit ctrl-C. Do a writeline to clean up
// the output...
ui.WriteLine();
}
inBlockMode = false;
if (Console.IsInputRedirected)
{
// null is also the result of reading stdin to EOF.
_parent.ShouldEndSession = true;
break;
}
continue;
}
if (string.IsNullOrWhiteSpace(line))
{
if (inBlockMode)
{
// end block mode and execute the block accumulated block
s_tracer.WriteLine("exiting block mode");
line = inputBlock.ToString();
inBlockMode = false;
}
else if (!_parent.InDebugMode)
{
previousResponseWasEmpty = true;
continue;
}
}
else
{
if (inBlockMode)
{
s_tracer.WriteLine("adding line to block");
inputBlock.Append('\n');
inputBlock.Append(line);
continue;
}
}
Dbg.Assert(line != null, "line should not be null");
Dbg.Assert(line.Length > 0 || _parent.InDebugMode, "line should not be empty unless the host is in debug mode");
Dbg.Assert(!inBlockMode, "should not be in block mode at point of pipeline execution");
Exception e = null;
if (_parent.InDebugMode)
{
DebuggerCommandResults results = ProcessDebugCommand(line, out e);
if (results.ResumeAction != null)
{
_parent.ExitDebugMode(results.ResumeAction.Value);
}
if (e != null)
{
var ex = e as PSInvalidOperationException;
if (e is PSRemotingTransportException ||
e is RemoteException ||
(ex != null &&
ex.ErrorRecord != null &&
ex.ErrorRecord.FullyQualifiedErrorId.Equals("Debugger:CannotProcessCommandNotStopped", StringComparison.OrdinalIgnoreCase)))
{
// Debugger session is broken. Exit nested loop.
_parent.ExitDebugMode(DebuggerResumeAction.Continue);
}
else
{
// Handle incomplete parse and other errors.
inBlockMode = HandleErrors(e, line, inBlockMode, ref inputBlock);
}
}
continue;
}
if (_runspacePopped)
{
string msg = StringUtil.Format(ConsoleHostStrings.CommandNotExecuted, line);
ui.WriteErrorLine(msg);
_runspacePopped = false;
}
else
{
#if UNIX
if (c.SupportsVirtualTerminal)
{
// disable DECCKM to standard mode as applications may not expect VT for cursor keys
c.Write(DECCKM_OFF);
}
#endif
if (_parent.IsRunningAsync && !_parent.IsNested)
{
_exec.ExecuteCommandAsync(line, out e, Executor.ExecutionOptions.AddOutputter | Executor.ExecutionOptions.AddToHistory);
}
else
{
_exec.ExecuteCommand(line, out e, Executor.ExecutionOptions.AddOutputter | Executor.ExecutionOptions.AddToHistory);
}
Thread bht = null;
lock (_parent.hostGlobalLock)
{
bht = _parent._breakHandlerThread;
}
if (bht != null)
{
bht.Join();
}
// Once the pipeline has been executed, we toss any outstanding progress data and
// take down the display.
ui.ResetProgress();
if (e != null)
{
// Handle incomplete parse and other errors.
inBlockMode = HandleErrors(e, line, inBlockMode, ref inputBlock);
// If a remote runspace is pushed and it is not in a good state
// then pop it.
if (_isRunspacePushed && (_parent.Runspace != null) &&
((_parent.Runspace.RunspaceStateInfo.State != RunspaceState.Opened) ||
(_parent.Runspace.RunspaceAvailability != RunspaceAvailability.Available)))
{
_parent.PopRunspace();
}
}
#if LEGACYTELEMETRY
if (!inBlockMode)
s_theConsoleHost._interactiveCommandCount += 1;
#endif
}
}
// NTRAID#Windows Out Of Band Releases-915506-2005/09/09
// Removed HandleUnexpectedExceptions infrastructure
finally
{
_parent._isRunningPromptLoop = false;
}
}
}
internal void BlockCommandOutput()
{
RemotePipeline rCmdPipeline = _parent.runningCmd as RemotePipeline;
if (rCmdPipeline != null)
{
rCmdPipeline.DrainIncomingData();
rCmdPipeline.SuspendIncomingData();
}
else
{
_exec.BlockCommandOutput();
}
}
internal void ResumeCommandOutput()
{
RemotePipeline rCmdPipeline = _parent.runningCmd as RemotePipeline;
if (rCmdPipeline != null)
{
rCmdPipeline.ResumeIncomingData();
}
else
{
_exec.ResumeCommandOutput();
}
}
private bool HandleErrors(Exception e, string line, bool inBlockMode, ref StringBuilder inputBlock)
{
Dbg.Assert(e != null, "Exception reference should not be null.");
if (IsIncompleteParseException(e))
{
if (!inBlockMode)
{
inBlockMode = true;
inputBlock = new StringBuilder(line);
}
else
{
inputBlock.Append(line);
}
}
else
{
// an exception occurred when the command was executed. Tell the user about it.
_parent.ReportException(e, _exec);
}
return inBlockMode;
}
private DebuggerCommandResults ProcessDebugCommand(string cmd, out Exception e)
{
DebuggerCommandResults results = null;
try
{
_parent.DebuggerCanStopCommand = true;
// Use PowerShell object to write streaming data to host.
using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create())
{
PSInvocationSettings settings = new PSInvocationSettings()
{
Host = _parent
};
PSDataCollection<PSObject> output = new PSDataCollection<PSObject>();
ps.AddCommand("Out-Default");
IAsyncResult async = ps.BeginInvoke<PSObject>(output, settings, null, null);
// Let debugger evaluate command and stream output data.
results = _parent.Runspace.Debugger.ProcessCommand(
new PSCommand(
new Command(cmd, true)),
output);
output.Complete();
ps.EndInvoke(async);
}
e = null;
}
catch (Exception ex)
{
e = ex;
results = new DebuggerCommandResults(null, false);
}
finally
{
_parent.DebuggerCanStopCommand = false;
}
// Exit debugger if command fails to evaluate.
return results ?? new DebuggerCommandResults(DebuggerResumeAction.Continue, false);
}
private static bool IsIncompleteParseException(Exception e)
{
// Check e's type.
if (e is IncompleteParseException)
{
return true;
}
// If it is remote exception ferret out the real exception.
RemoteException remoteException = e as RemoteException;
if (remoteException == null || remoteException.ErrorRecord == null)
{
return false;
}
return remoteException.ErrorRecord.CategoryInfo.Reason == nameof(IncompleteParseException);
}
private void EvaluateSuggestions(ConsoleHostUserInterface ui)
{
// Output any training suggestions
try
{
List<string> suggestions = HostUtilities.GetSuggestion(_parent.Runspace);
if (suggestions.Count > 0)
{
ui.WriteLine();
}
bool first = true;
foreach (string suggestion in suggestions)
{
if (!first)
ui.WriteLine();
ui.WriteLine(suggestion);
first = false;
}
}
catch (TerminateException)
{
// A variable breakpoint may be hit by HostUtilities.GetSuggestion. The debugger throws TerminateExceptions to stop the execution
// of the current statement; we do not want to treat these exceptions as errors.
}
catch (Exception e)
{
// Catch-all OK. This is a third-party call-out.
ui.WriteErrorLine(e.Message);
LocalRunspace localRunspace = (LocalRunspace)_parent.Runspace;
localRunspace.GetExecutionContext.AppendDollarError(e);
}
}
private string EvaluatePrompt()
{
Exception unused = null;
string promptString = _promptExec.ExecuteCommandAndGetResultAsString("prompt", out unused);
if (string.IsNullOrEmpty(promptString))
{
promptString = ConsoleHostStrings.DefaultPrompt;
}
// Check for the pushed runspace scenario.
if (_isRunspacePushed)
{
RemoteRunspace remoteRunspace = _parent.Runspace as RemoteRunspace;
if (remoteRunspace != null)
{
promptString = HostUtilities.GetRemotePrompt(remoteRunspace, promptString, _parent._inPushedConfiguredSession);
}
}
else
{
if (_runspacePopped)
{
_runspacePopped = false;
}
}
// Return composed prompt string.
return promptString;
}
private string EvaluateDebugPrompt()
{
PSDataCollection<PSObject> output = new PSDataCollection<PSObject>();
try
{
_parent.Runspace.Debugger.ProcessCommand(
new PSCommand(new Command("prompt")),
output);
}
catch (Exception ex)
{
_parent.ReportException(ex, _exec);
}
PSObject prompt = output.ReadAndRemoveAt0();
string promptString = (prompt != null) ? (prompt.BaseObject as string) : null;
if (promptString != null)
{
RemoteRunspace remoteRunspace = _parent.Runspace as RemoteRunspace;
if (remoteRunspace != null)
{
promptString = HostUtilities.GetRemotePrompt(remoteRunspace, promptString, _parent._inPushedConfiguredSession);
}
}
return promptString;
}
private readonly ConsoleHost _parent;
private readonly bool _isNested;
private bool _shouldExit;
private readonly Executor _exec;
private readonly Executor _promptExec;
private readonly object _syncObject = new object();
private bool _isRunspacePushed = false;
private bool _runspacePopped = false;
// The instance stack is used to keep track of which InputLoop instance should be told to exit
// when PSHost.ExitNestedPrompt is called.
// threadsafety guaranteed by enclosing class
private static readonly Stack<InputLoop> s_instanceStack = new Stack<InputLoop>();
}
[Serializable]
[SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic", Justification =
"This exception cannot be used outside of the console host application. It is not thrown by a library routine, only by an application.")]
private sealed class ConsoleHostStartupException : Exception
{
internal
ConsoleHostStartupException()
: base()
{
}
internal
ConsoleHostStartupException(string message)
: base(message)
{
}
private
ConsoleHostStartupException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
: base(info, context)
{
}
internal
ConsoleHostStartupException(string message, Exception innerException)
: base(message, innerException)
{
}
}
#endregion aux classes
/// <summary>
/// By declaring runspace as ObjectRef&lt;Runspace&gt; we are able to hide the real runspace with
/// a remote runspace in the PushRunspace scenario. By declaring it as a mask, the variable
/// runspace becomes an indirect reference to the actual runspace which we can override with
/// a remote runspace while it is pushed. Also we can easily revert back to the original
/// runspace when the PopRunspace command is invoked.
/// </summary>
private RunspaceRef _runspaceRef;
#if !UNIX
private GCHandle breakHandlerGcHandle;
// Set to Unknown so that we avoid saving/restoring the console mode if we don't have a console.
private ConsoleControl.ConsoleModes _savedConsoleMode = ConsoleControl.ConsoleModes.Unknown;
private readonly ConsoleControl.ConsoleModes _initialConsoleMode = ConsoleControl.ConsoleModes.Unknown;
#endif
private Thread _breakHandlerThread;
private bool _isDisposed;
internal ConsoleHostUserInterface ui;
internal Lazy<TextReader> ConsoleIn { get; } = new Lazy<TextReader>(static () => Console.In);
private string _savedWindowTitle = string.Empty;
private readonly Version _ver = PSVersionInfo.PSVersion;
private int _exitCodeFromRunspace;
private bool _noExit = true;
private bool _setShouldExitCalled;
private bool _isRunningPromptLoop;
private bool _wasInitialCommandEncoded;
private bool? _screenReaderActive;
// hostGlobalLock is used to sync public method calls (in case multiple threads call into the host) and access to
// state that persists across method calls, like progress data. It's internal because the ui object also
// uses this same object.
internal object hostGlobalLock = new object();
// These members are possibly accessed from multiple threads (the break handler thread, a pipeline thread, or the main
// thread). We use hostGlobalLock to sync access to them.
private bool _shouldEndSession;
private int _beginApplicationNotifyCount;
private readonly ConsoleTextWriter _consoleWriter;
private WrappedSerializer _outputSerializer;
private WrappedSerializer _errorSerializer;
private bool _displayDebuggerBanner;
private DebuggerStopEventArgs _debuggerStopEventArgs;
private bool _inPushedConfiguredSession;
internal Pipeline runningCmd;
// The ConsoleHost class is a singleton. Note that there is not a thread-safety issue with these statics as there can
// only be one console host per process.
private static ConsoleHost s_theConsoleHost;
internal static InitialSessionState DefaultInitialSessionState;
[TraceSource("ConsoleHost", "ConsoleHost subclass of S.M.A.PSHost")]
private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("ConsoleHost", "ConsoleHost subclass of S.M.A.PSHost");
[TraceSource("ConsoleHostRunspaceInit", "Initialization code for ConsoleHost's Runspace")]
private static readonly PSTraceSource s_runspaceInitTracer =
PSTraceSource.GetTracer("ConsoleHostRunspaceInit", "Initialization code for ConsoleHost's Runspace", false);
}
/// <summary>
/// Defines arguments passed to ConsoleHost.CreateRunspace.
/// </summary>
internal sealed class RunspaceCreationEventArgs : EventArgs
{
/// <summary>
/// Constructs RunspaceCreationEventArgs.
/// </summary>
internal RunspaceCreationEventArgs(
string initialCommand,
bool skipProfiles,
bool staMode,
string configurationName,
Collection<CommandParameter> initialCommandArgs)
{
InitialCommand = initialCommand;
SkipProfiles = skipProfiles;
StaMode = staMode;
ConfigurationName = configurationName;
InitialCommandArgs = initialCommandArgs;
}
internal string InitialCommand { get; set; }
internal bool SkipProfiles { get; set; }
internal bool StaMode { get; set; }
internal string ConfigurationName { get; set; }
internal Collection<CommandParameter> InitialCommandArgs { get; set; }
}
} // namespace