PowerShell/src/System.Management.Automation/engine/MshCommandRuntime.cs
xtqqczze 883ca98dd7
Seal private classes (#15725)
* Seal private classes

* Fix CS0509

* Fix CS0628
2021-07-19 14:09:12 +05:00

3810 lines
155 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma warning disable 1634, 1691
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Management.Automation.Host;
using System.Management.Automation.Internal;
using System.Management.Automation.Internal.Host;
using System.Management.Automation.Remoting;
using System.Management.Automation.Runspaces;
using System.Threading;
using Dbg = System.Management.Automation.Diagnostics;
namespace System.Management.Automation
{
/// <summary>
/// Monad internal implementation of the ICommandRuntime2 interface
/// used for execution in the monad engine environment.
///
/// There will be one instance of this class for each cmdlet added to
/// a pipeline. When the cmdlet calls its WriteObject API, that API will call
/// the WriteObject implementation in this class which, in turn, calls
/// the downstream cmdlet.
/// </summary>
internal class MshCommandRuntime : ICommandRuntime2
{
#region private_members
/// <summary>
/// Gets/Set the execution context value for this runtime object.
/// </summary>
internal ExecutionContext Context { get; set; }
private SessionState _state = null;
internal InternalHost CBhost;
/// <summary>
/// The host object for this object.
/// </summary>
public PSHost Host { get; }
// Output pipes.
private Pipe _inputPipe;
private Pipe _outputPipe;
private Pipe _errorOutputPipe;
/// <summary>
/// IsClosed indicates to the Cmdlet whether its upstream partner
/// could still write more data to its incoming queue.
/// Note that there may still be data in the incoming queue.
/// </summary>
internal bool IsClosed { get; set; }
/// <summary>
/// True if we're not closed and the input pipe is non-null...
/// </summary>
internal bool IsPipelineInputExpected
{
get
{
// No objects in the input pipe
// The pipe is closed. So there can't be any more object
if (IsClosed && (_inputPipe == null || _inputPipe.Empty))
{
return false;
}
return true;
}
}
/// <summary>
/// This allows all success output to be set to a variable. Similar to the way -errorvariable sets
/// all errors to a variable name. Semantically this is equivalent to : cmd |set-var varname -passthru
/// but it should be MUCH faster as there is no binding that takes place.
/// </summary>
/// <exception cref="System.ArgumentNullException">
/// may not be set to null
/// </exception>
/// <remarks>
/// This is a common parameter via class CommonParameters.
/// </remarks>
internal string OutVariable { get; set; }
internal IList OutVarList { get { return _outVarList; } set { _outVarList = value; } }
private IList _outVarList = null;
internal PipelineProcessor PipelineProcessor { get; set; }
private readonly CommandInfo _commandInfo;
private readonly InternalCommand _thisCommand;
#endregion private_members
internal MshCommandRuntime(ExecutionContext context, CommandInfo commandInfo, InternalCommand thisCommand)
{
Context = context;
Host = context.EngineHostInterface;
this.CBhost = (InternalHost)context.EngineHostInterface;
_commandInfo = commandInfo;
_thisCommand = thisCommand;
LogPipelineExecutionDetail = InitShouldLogPipelineExecutionDetail();
}
/// <summary>
/// For diagnostic purposes.
/// </summary>
/// <returns></returns>
public override string ToString()
{
if (_commandInfo != null)
return _commandInfo.ToString();
return "<NullCommandInfo>"; // does not require localization
}
private InvocationInfo _myInvocation;
/// <summary>
/// Return the invocation data object for this command.
/// </summary>
/// <value>The invocation object for this command.</value>
internal InvocationInfo MyInvocation
{
get { return _myInvocation ??= _thisCommand.MyInvocation; }
}
/// <summary>
/// Internal helper. Indicates whether stop has been requested on this command.
/// </summary>
internal bool IsStopping
{
get { return (this.PipelineProcessor != null && this.PipelineProcessor.Stopping); }
}
#region Write
// Trust: WriteObject needs to respect EmitTrustCategory
/// <summary>
/// Writes the object to the output pipe.
/// </summary>
/// <param name="sendToPipeline">
/// The object that needs to be written. This will be written as
/// a single object, even if it is an enumeration.
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread.
/// WriteObject may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
/// </exception>
/// <seealso cref="System.Management.Automation.ICommandRuntime.WriteObject(object,bool)"/>
/// <seealso cref="System.Management.Automation.ICommandRuntime.WriteError(ErrorRecord)"/>
public void WriteObject(object sendToPipeline)
{
// This check will be repeated in _WriteObjectSkipAllowCheck,
// but we want PipelineStoppedException to take precedence
// over InvalidOperationException if the pipeline has been
// closed.
ThrowIfStopping();
#if CORECLR
// SecurityContext is not supported in CoreCLR
DoWriteObject(sendToPipeline);
#else
if (UseSecurityContextRun)
{
if (PipelineProcessor == null || PipelineProcessor.SecurityContext == null)
throw PSTraceSource.NewInvalidOperationException(PipelineStrings.WriteNotPermitted);
ContextCallback delegateCallback =
new ContextCallback(DoWriteObject);
SecurityContext.Run(
PipelineProcessor.SecurityContext.CreateCopy(),
delegateCallback,
sendToPipeline);
}
else
{
DoWriteObject(sendToPipeline);
}
#endif
}
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread
/// </exception>
private void DoWriteObject(object sendToPipeline)
{
ThrowIfWriteNotPermitted(true);
_WriteObjectSkipAllowCheck(sendToPipeline);
}
/// <summary>
/// Writes one or more objects to the output pipe.
/// If the object is a collection and the enumerateCollection flag
/// is true, the objects in the collection
/// will be written individually.
/// </summary>
/// <param name="sendToPipeline">
/// The object that needs to be written to the pipeline.
/// </param>
/// <param name="enumerateCollection">
/// true if the collection should be enumerated
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread.
/// WriteObject may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
/// </exception>
/// <seealso cref="System.Management.Automation.ICommandRuntime.WriteObject(object)"/>
/// <seealso cref="System.Management.Automation.ICommandRuntime.WriteError(ErrorRecord)"/>
public void WriteObject(object sendToPipeline, bool enumerateCollection)
{
if (!enumerateCollection)
{
WriteObject(sendToPipeline);
return;
}
// This check will be repeated in _WriteObjectsSkipAllowCheck,
// but we want PipelineStoppedException to take precedence
// over InvalidOperationException if the pipeline has been
// closed.
ThrowIfStopping();
#if CORECLR
// SecurityContext is not supported in CoreCLR
DoWriteEnumeratedObject(sendToPipeline);
#else
if (UseSecurityContextRun)
{
if (PipelineProcessor == null || PipelineProcessor.SecurityContext == null)
throw PSTraceSource.NewInvalidOperationException(PipelineStrings.WriteNotPermitted);
ContextCallback delegateCallback =
new ContextCallback(DoWriteObjects);
SecurityContext.Run(
PipelineProcessor.SecurityContext.CreateCopy(),
delegateCallback,
sendToPipeline);
}
else
{
DoWriteObjects(sendToPipeline);
}
#endif
}
/// <summary>
/// Writes an object enumerated from a collection to the output pipe.
/// </summary>
/// <param name="sendToPipeline">
/// The enumerated object that needs to be written to the pipeline.
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread.
/// </exception>
private void DoWriteEnumeratedObject(object sendToPipeline)
{
// NOTICE-2004/06/08-JonN 959638
ThrowIfWriteNotPermitted(true);
_EnumerateAndWriteObjectSkipAllowCheck(sendToPipeline);
}
// Trust: public void WriteObject(object sendToPipeline, DataTrustCategory trustCategory); // enumerateCollection defaults to false
// Trust: public void WriteObject(object sendToPipeline, bool enumerateCollection, DataTrustCategory trustCategory);
// Variables needed to generate a unique SourceId for
// WriteProgress(ProgressRecord).
private static Int64 s_lastUsedSourceId /* = 0 */;
private Int64 _sourceId /* = 0 */;
/// <summary>
/// Display progress information.
/// </summary>
/// <param name="progressRecord">Progress information.</param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread.
/// WriteProgress may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
/// </exception>
/// <remarks>
/// Use WriteProgress to display progress information about
/// the activity of your Cmdlet, when the operation of your Cmdlet
/// could potentially take a long time.
///
/// By default, progress output will
/// be displayed, although this can be configured with the
/// ProgressPreference shell variable.
/// </remarks>
/// <seealso cref="System.Management.Automation.Cmdlet.WriteDebug(string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.WriteWarning(string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.WriteVerbose(string)"/>
public void WriteProgress(ProgressRecord progressRecord)
{
this.WriteProgress(progressRecord, false);
}
internal void WriteProgress(ProgressRecord progressRecord, bool overrideInquire)
{
// NTRAID#Windows Out Of Band Releases-918023-2005/08/22-JonN
ThrowIfStopping();
//
// WriteError/WriteObject have a check that prevents them to be called from outside
// Begin/Process/End. This is done because the Pipeline needs to be ready before these
// functions can be called.
//
// WriteDebug/Warning/Verbose/Process used to do the same check, even though it is not
// strictly needed. If we ever implement pipelines for these objects we may need to
// enforce the check again.
//
// See bug 583774 in the Windows 7 database for more details.
//
ThrowIfWriteNotPermitted(false);
// Bug909439: We need a unique sourceId to send to
// WriteProgress. The following logic ensures that
// there is a unique id for each Cmdlet instance.
if (_sourceId == 0)
{
_sourceId = Interlocked.Increment(ref s_lastUsedSourceId);
}
this.WriteProgress(_sourceId, progressRecord, overrideInquire);
}
/// <summary>
/// Displays progress output if enabled.
/// </summary>
/// <param name="sourceId">
/// Identifies which command is reporting progress
/// </param>
/// <param name="progressRecord">
/// Progress status to be displayed
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <remarks>
/// If the pipeline is terminated due to ActionPreference.Stop
/// or ActionPreference.Inquire, this method will throw
/// <see cref="System.Management.Automation.PipelineStoppedException"/>,
/// but the command failure will ultimately be
/// <see cref="System.Management.Automation.ActionPreferenceStopException"/>,
/// </remarks>
public void WriteProgress(
Int64 sourceId,
ProgressRecord progressRecord)
{
WriteProgress(sourceId, progressRecord, false);
}
internal void WriteProgress(
Int64 sourceId,
ProgressRecord progressRecord,
bool overrideInquire)
{
if (progressRecord == null)
{
throw PSTraceSource.NewArgumentNullException(nameof(progressRecord));
}
if (Host == null || Host.UI == null)
{
Diagnostics.Assert(false, "No host in CommandBase.WriteProgress()");
throw PSTraceSource.NewInvalidOperationException();
}
InternalHostUserInterface ui = Host.UI as InternalHostUserInterface;
ActionPreference preference = ProgressPreference;
if (overrideInquire && preference == ActionPreference.Inquire)
{
preference = ActionPreference.Continue;
}
if (WriteHelper_ShouldWrite(
preference, lastProgressContinueStatus))
{
// Break into the debugger if requested
if (preference == ActionPreference.Break)
{
CBhost?.Runspace?.Debugger?.Break(progressRecord);
}
ui.WriteProgress(sourceId, progressRecord);
}
lastProgressContinueStatus = WriteHelper(
null,
null,
preference,
lastProgressContinueStatus,
"ProgressPreference",
progressRecord.Activity);
}
/// <summary>
/// Display debug information.
/// </summary>
/// <param name="text">Debug output.</param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread.
/// WriteDebug may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
/// </exception>
/// <remarks>
/// Use WriteDebug to display debug information on the inner workings
/// of your Cmdlet. By default, debug output will
/// not be displayed, although this can be configured with the
/// DebugPreference shell variable or the -Debug command-line option.
/// </remarks>
/// <remarks>
/// If the pipeline is terminated due to ActionPreference.Stop
/// or ActionPreference.Inquire, this method will throw
/// <see cref="System.Management.Automation.PipelineStoppedException"/>,
/// but the command failure will ultimately be
/// <see cref="System.Management.Automation.ActionPreferenceStopException"/>,
/// </remarks>
/// <seealso cref="System.Management.Automation.Cmdlet.WriteVerbose(string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.WriteWarning(string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.WriteProgress(ProgressRecord)"/>
public void WriteDebug(string text)
{
WriteDebug(new DebugRecord(text));
}
/// <summary>
/// Display debug information.
/// </summary>
internal void WriteDebug(DebugRecord record, bool overrideInquire = false)
{
ActionPreference preference = DebugPreference;
if (overrideInquire && preference == ActionPreference.Inquire)
preference = ActionPreference.Continue;
if (WriteHelper_ShouldWrite(preference, lastDebugContinueStatus))
{
if (record.InvocationInfo == null)
{
record.SetInvocationInfo(MyInvocation);
}
// Break into the debugger if requested
if (preference == ActionPreference.Break)
{
CBhost?.Runspace?.Debugger?.Break(record);
}
if (DebugOutputPipe != null)
{
if (CBhost != null && CBhost.InternalUI != null &&
DebugOutputPipe.NullPipe)
{
// If redirecting to a null pipe, still write to
// information buffers.
CBhost.InternalUI.WriteDebugInfoBuffers(record);
}
// Set WriteStream so that the debug output is formatted correctly.
PSObject debugWrap = PSObject.AsPSObject(record);
debugWrap.WriteStream = WriteStreamType.Debug;
DebugOutputPipe.Add(debugWrap);
}
else
{
//
// If no pipe, write directly to host.
//
if (Host == null || Host.UI == null)
{
Diagnostics.Assert(false, "No host in CommandBase.WriteDebug()");
throw PSTraceSource.NewInvalidOperationException();
}
CBhost.InternalUI.TranscribeResult(StringUtil.Format(InternalHostUserInterfaceStrings.DebugFormatString, record.Message));
CBhost.InternalUI.WriteDebugRecord(record);
}
}
lastDebugContinueStatus = WriteHelper(
null,
null,
preference,
lastDebugContinueStatus,
"DebugPreference",
record.Message);
}
/// <summary>
/// Display verbose information.
/// </summary>
/// <param name="text">Verbose output.</param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread.
/// WriteVerbose may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
/// </exception>
/// <remarks>
/// Use WriteVerbose to display more detailed information about
/// the activity of your Cmdlet. By default, verbose output will
/// not be displayed, although this can be configured with the
/// VerbosePreference shell variable
/// or the -Verbose and -Debug command-line options.
/// </remarks>
/// <seealso cref="System.Management.Automation.Cmdlet.WriteDebug(string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.WriteWarning(string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.WriteProgress(ProgressRecord)"/>
public void WriteVerbose(string text)
{
WriteVerbose(new VerboseRecord(text));
}
/// <summary>
/// Display verbose information.
/// </summary>
internal void WriteVerbose(VerboseRecord record, bool overrideInquire = false)
{
ActionPreference preference = VerbosePreference;
if (overrideInquire && preference == ActionPreference.Inquire)
preference = ActionPreference.Continue;
if (WriteHelper_ShouldWrite(preference, lastVerboseContinueStatus))
{
if (record.InvocationInfo == null)
{
record.SetInvocationInfo(MyInvocation);
}
// Break into the debugger if requested
if (preference == ActionPreference.Break)
{
CBhost?.Runspace?.Debugger?.Break(record);
}
if (VerboseOutputPipe != null)
{
if (CBhost != null && CBhost.InternalUI != null &&
VerboseOutputPipe.NullPipe)
{
// If redirecting to a null pipe, still write to
// information buffers.
CBhost.InternalUI.WriteVerboseInfoBuffers(record);
}
// Add WriteStream so that the verbose output is formatted correctly.
PSObject verboseWrap = PSObject.AsPSObject(record);
verboseWrap.WriteStream = WriteStreamType.Verbose;
VerboseOutputPipe.Add(verboseWrap);
}
else
{
//
// If no pipe, write directly to host.
//
if (Host == null || Host.UI == null)
{
Diagnostics.Assert(false, "No host in CommandBase.WriteVerbose()");
throw PSTraceSource.NewInvalidOperationException();
}
CBhost.InternalUI.TranscribeResult(StringUtil.Format(InternalHostUserInterfaceStrings.VerboseFormatString, record.Message));
CBhost.InternalUI.WriteVerboseRecord(record);
}
}
lastVerboseContinueStatus = WriteHelper(
null,
null,
preference,
lastVerboseContinueStatus,
"VerbosePreference",
record.Message);
}
/// <summary>
/// Display warning information.
/// </summary>
/// <param name="text">Warning output.</param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread.
/// WriteWarning may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
/// </exception>
/// <remarks>
/// Use WriteWarning to display warnings about
/// the activity of your Cmdlet. By default, warning output will
/// be displayed, although this can be configured with the
/// WarningPreference shell variable
/// or the -Verbose and -Debug command-line options.
/// </remarks>
/// <seealso cref="System.Management.Automation.Cmdlet.WriteDebug(string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.WriteVerbose(string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.WriteProgress(ProgressRecord)"/>
public void WriteWarning(string text)
{
WriteWarning(new WarningRecord(text));
}
/// <summary>
/// Display warning information.
/// </summary>
internal void WriteWarning(WarningRecord record, bool overrideInquire = false)
{
ActionPreference preference = WarningPreference;
if (overrideInquire && preference == ActionPreference.Inquire)
preference = ActionPreference.Continue;
if (WriteHelper_ShouldWrite(preference, lastWarningContinueStatus))
{
if (record.InvocationInfo == null)
{
record.SetInvocationInfo(MyInvocation);
}
// Break into the debugger if requested
if (preference == ActionPreference.Break)
{
CBhost?.Runspace?.Debugger?.Break(record);
}
if (WarningOutputPipe != null)
{
if (CBhost != null && CBhost.InternalUI != null &&
WarningOutputPipe.NullPipe)
{
// If redirecting to a null pipe, still write to
// information buffers.
CBhost.InternalUI.WriteWarningInfoBuffers(record);
}
// Add WriteStream so that the warning output is formatted correctly.
PSObject warningWrap = PSObject.AsPSObject(record);
warningWrap.WriteStream = WriteStreamType.Warning;
WarningOutputPipe.AddWithoutAppendingOutVarList(warningWrap);
}
else
{
//
// If no pipe, write directly to host.
//
if (Host == null || Host.UI == null)
{
Diagnostics.Assert(false, "No host in CommandBase.WriteWarning()");
throw PSTraceSource.NewInvalidOperationException();
}
CBhost.InternalUI.TranscribeResult(StringUtil.Format(InternalHostUserInterfaceStrings.WarningFormatString, record.Message));
CBhost.InternalUI.WriteWarningRecord(record);
}
}
AppendWarningVarList(record);
lastWarningContinueStatus = WriteHelper(
null,
null,
preference,
lastWarningContinueStatus,
"WarningPreference",
record.Message);
}
/// <summary>
/// Display tagged object information.
/// </summary>
public void WriteInformation(InformationRecord informationRecord)
{
WriteInformation(informationRecord, false);
}
/// <summary>
/// Display tagged object information.
/// </summary>
internal void WriteInformation(InformationRecord record, bool overrideInquire = false)
{
ActionPreference preference = InformationPreference;
if (overrideInquire && preference == ActionPreference.Inquire)
preference = ActionPreference.Continue;
// Break into the debugger if requested
if (preference == ActionPreference.Break)
{
CBhost?.Runspace?.Debugger?.Break(record);
}
if (preference != ActionPreference.Ignore)
{
if (InformationOutputPipe != null)
{
if (CBhost != null && CBhost.InternalUI != null &&
InformationOutputPipe.NullPipe)
{
// If redirecting to a null pipe, still write to
// information buffers.
CBhost.InternalUI.WriteInformationInfoBuffers(record);
}
// Add WriteStream so that the information output is formatted correctly.
PSObject informationWrap = PSObject.AsPSObject(record);
informationWrap.WriteStream = WriteStreamType.Information;
InformationOutputPipe.Add(informationWrap);
}
else
{
//
// If no pipe, write directly to host.
//
if (Host == null || Host.UI == null)
{
throw PSTraceSource.NewInvalidOperationException("No host in CommandBase.WriteInformation()");
}
CBhost.InternalUI.WriteInformationRecord(record);
if ((record.Tags.Contains("PSHOST") && (!record.Tags.Contains("FORWARDED")))
|| (preference == ActionPreference.Continue))
{
HostInformationMessage hostOutput = record.MessageData as HostInformationMessage;
if (hostOutput != null)
{
string message = hostOutput.Message;
ConsoleColor? foregroundColor = null;
ConsoleColor? backgroundColor = null;
bool noNewLine = false;
if (hostOutput.ForegroundColor.HasValue)
{
foregroundColor = hostOutput.ForegroundColor.Value;
}
if (hostOutput.BackgroundColor.HasValue)
{
backgroundColor = hostOutput.BackgroundColor.Value;
}
if (hostOutput.NoNewLine.HasValue)
{
noNewLine = hostOutput.NoNewLine.Value;
}
if (foregroundColor.HasValue || backgroundColor.HasValue)
{
// It is possible for either one or the other to be empty if run from a
// non-interactive host, but only one was specified in Write-Host.
// So fill them with defaults if they are empty.
if (!foregroundColor.HasValue)
{
foregroundColor = ConsoleColor.Gray;
}
if (!backgroundColor.HasValue)
{
backgroundColor = ConsoleColor.Black;
}
if (noNewLine)
{
CBhost.InternalUI.Write(foregroundColor.Value, backgroundColor.Value, message);
}
else
{
CBhost.InternalUI.WriteLine(foregroundColor.Value, backgroundColor.Value, message);
}
}
else
{
if (noNewLine)
{
CBhost.InternalUI.Write(message);
}
else
{
CBhost.InternalUI.WriteLine(message);
}
}
}
else
{
CBhost.InternalUI.WriteLine(record.ToString());
}
}
}
// Both informational and PSHost-targeted messages are transcribed here.
// The only difference between these two is that PSHost-targeted messages are transcribed
// even if InformationAction is SilentlyContinue.
if (record.Tags.Contains("PSHOST") || (preference != ActionPreference.SilentlyContinue))
{
CBhost.InternalUI.TranscribeResult(record.ToString());
}
}
AppendInformationVarList(record);
lastInformationContinueStatus = WriteHelper(
null,
null,
preference,
lastInformationContinueStatus,
"InformationPreference",
record.ToString());
}
/// <summary>
/// Write text into pipeline execution log.
/// </summary>
/// <param name="text">Text to be written to log.</param>
/// <remarks>
/// Use WriteCommandDetail to write important information about cmdlet execution to
/// pipeline execution log.
///
/// If LogPipelineExecutionDetail is turned on, this information will be written
/// to monad log under log category "Pipeline execution detail"
/// </remarks>
/// <seealso cref="System.Management.Automation.ICommandRuntime.WriteDebug(string)"/>
/// <seealso cref="System.Management.Automation.ICommandRuntime.WriteVerbose(string)"/>
/// <seealso cref="System.Management.Automation.ICommandRuntime.WriteProgress(ProgressRecord)"/>
public void WriteCommandDetail(string text)
{
this.PipelineProcessor.LogExecutionInfo(_thisCommand.MyInvocation, text);
}
internal bool LogPipelineExecutionDetail { get; } = false;
private bool InitShouldLogPipelineExecutionDetail()
{
CmdletInfo cmdletInfo = _commandInfo as CmdletInfo;
if (cmdletInfo != null)
{
if (string.Equals("Add-Type", cmdletInfo.Name, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (cmdletInfo.Module == null && cmdletInfo.PSSnapIn != null)
{
return cmdletInfo.PSSnapIn.LogPipelineExecutionDetails;
}
if (cmdletInfo.PSSnapIn == null && cmdletInfo.Module != null)
{
return cmdletInfo.Module.LogPipelineExecutionDetails;
}
return false;
}
// Logging should be enabled for functions from modules also
FunctionInfo functionInfo = _commandInfo as FunctionInfo;
if (functionInfo != null && functionInfo.Module != null)
{
return functionInfo.Module.LogPipelineExecutionDetails;
}
return false;
}
/// <summary>
/// This allows all success output to be set to a variable, where the variable is reset for each item returned by
/// the cmdlet. Semantically this is equivalent to : cmd | % { $pipelineVariable = $_; (...) }
/// </summary>
internal string PipelineVariable { get; set; }
private PSVariable _pipelineVarReference = null;
internal void SetupOutVariable()
{
if (string.IsNullOrEmpty(this.OutVariable))
{
return;
}
EnsureVariableParameterAllowed();
// Handle the creation of OutVariable in the case of Out-Default specially,
// as it needs to handle much of its OutVariable support itself.
if (
(!string.IsNullOrEmpty(this.OutVariable)) &&
(!(this.OutVariable.StartsWith('+'))) &&
string.Equals("Out-Default", _thisCommand.CommandInfo.Name, StringComparison.OrdinalIgnoreCase))
{
if (_state == null)
_state = new SessionState(Context.EngineSessionState);
IList oldValue = null;
oldValue = PSObject.Base(_state.PSVariable.GetValue(this.OutVariable)) as IList;
_outVarList = oldValue ?? new ArrayList();
if (_thisCommand is not PSScriptCmdlet)
{
this.OutputPipe.AddVariableList(VariableStreamKind.Output, _outVarList);
}
_state.PSVariable.Set(this.OutVariable, _outVarList);
}
else
{
SetupVariable(VariableStreamKind.Output, this.OutVariable, ref _outVarList);
}
}
internal void SetupPipelineVariable()
{
// This can't use the common SetupVariable implementation, as this needs to persist for an entire
// pipeline.
if (string.IsNullOrEmpty(this.PipelineVariable))
{
return;
}
EnsureVariableParameterAllowed();
if (_state == null)
_state = new SessionState(Context.EngineSessionState);
// Create the pipeline variable
_pipelineVarReference = new PSVariable(this.PipelineVariable);
_state.PSVariable.Set(_pipelineVarReference);
// Get the reference again in case we re-used one from the
// same scope.
_pipelineVarReference = _state.PSVariable.Get(this.PipelineVariable);
if (_thisCommand is not PSScriptCmdlet)
{
this.OutputPipe.SetPipelineVariable(_pipelineVarReference);
}
}
/// <summary>
/// Configures the number of objects to buffer before calling the downstream Cmdlet.
/// </summary>
/// <remarks>
/// This is a common parameter via class CommonParameters.
/// </remarks>
internal int OutBuffer
{
get { return OutputPipe.OutBufferCount; }
set { OutputPipe.OutBufferCount = value; }
}
#endregion Write
#region Should
#region ShouldProcess
/// <summary>
/// Confirm the operation with the user. Cmdlets which make changes
/// (e.g. delete files, stop services etc.) should call ShouldProcess
/// to give the user the opportunity to confirm that the operation
/// should actually be performed.
/// </summary>
/// <param name="target">
/// Name of the target resource being acted upon. This will
/// potentially be displayed to the user.
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread.
/// ShouldProcess may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
/// </exception>
/// <returns>
/// If ShouldProcess returns true, the operation should be performed.
/// If ShouldProcess returns false, the operation should not be
/// performed, and the Cmdlet should move on to the next target resource.
/// </returns>
/// <remarks>
/// A Cmdlet should declare
/// [Cmdlet( SupportsShouldProcess = true )]
/// if-and-only-if it calls ShouldProcess before making changes.
///
/// ShouldProcess may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
///
/// ShouldProcess will take into account command-line settings
/// and preference variables in determining what it should return
/// and whether it should prompt the user.
/// </remarks>
/// <remarks>
/// If the pipeline is terminated due to ActionPreference.Stop
/// or ActionPreference.Inquire,
/// <see cref="System.Management.Automation.Cmdlet.ShouldProcess(string)"/>
/// will throw
/// <see cref="System.Management.Automation.PipelineStoppedException"/>,
/// but the command failure will ultimately be
/// <see cref="System.Management.Automation.ActionPreferenceStopException"/>,
/// </remarks>
/// <example>
/// <snippet Code="C#">
/// namespace Microsoft.Samples.MSH.Cmdlet
/// {
/// [Cmdlet(VerbsCommon.Remove,"myobjecttype1")]
/// public class RemoveMyObjectType1 : PSCmdlet
/// {
/// [Parameter( Mandatory = true )]
/// public string Filename
/// {
/// get { return filename; }
/// set { filename = value; }
/// }
/// private string filename;
///
/// public override void ProcessRecord()
/// {
/// if (ShouldProcess(filename))
/// {
/// // delete the object
/// }
/// }
/// }
/// }
/// </snippet>
/// </example>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string,string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string,string,string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string,string,string,out ShouldProcessReason)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldContinue(string,string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldContinue(string,string,ref bool,ref bool)"/>
public bool ShouldProcess(string target)
{
string verboseDescription = StringUtil.Format(CommandBaseStrings.ShouldProcessMessage,
MyInvocation.MyCommand.Name,
target);
ShouldProcessReason shouldProcessReason;
return DoShouldProcess(verboseDescription, null, null, out shouldProcessReason);
}
/// <summary>
/// Confirm the operation with the user. Cmdlets which make changes
/// (e.g. delete files, stop services etc.) should call ShouldProcess
/// to give the user the opportunity to confirm that the operation
/// should actually be performed.
///
/// This variant allows the caller to specify text for both the
/// target resource and the action.
/// </summary>
/// <param name="target">
/// Name of the target resource being acted upon. This will
/// potentially be displayed to the user.
/// </param>
/// <param name="action">
/// Name of the action which is being performed. This will
/// potentially be displayed to the user. (default is Cmdlet name)
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread.
/// ShouldProcess may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
/// </exception>
/// <returns>
/// If ShouldProcess returns true, the operation should be performed.
/// If ShouldProcess returns false, the operation should not be
/// performed, and the Cmdlet should move on to the next target resource.
/// </returns>
/// <remarks>
/// A Cmdlet should declare
/// [Cmdlet( SupportsShouldProcess = true )]
/// if-and-only-if it calls ShouldProcess before making changes.
///
/// ShouldProcess may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
///
/// ShouldProcess will take into account command-line settings
/// and preference variables in determining what it should return
/// and whether it should prompt the user.
/// </remarks>
/// <remarks>
/// If the pipeline is terminated due to ActionPreference.Stop
/// or ActionPreference.Inquire, this method will throw
/// <see cref="System.Management.Automation.PipelineStoppedException"/>,
/// but the command failure will ultimately be
/// <see cref="System.Management.Automation.ActionPreferenceStopException"/>,
/// </remarks>
/// <example>
/// <snippet Code="C#">
/// namespace Microsoft.Samples.MSH.Cmdlet
/// {
/// [Cmdlet(VerbsCommon.Remove,"myobjecttype2")]
/// public class RemoveMyObjectType2 : PSCmdlet
/// {
/// [Parameter( Mandatory = true )]
/// public string Filename
/// {
/// get { return filename; }
/// set { filename = value; }
/// }
/// private string filename;
///
/// public override void ProcessRecord()
/// {
/// if (ShouldProcess(filename, "delete"))
/// {
/// // delete the object
/// }
/// }
/// }
/// }
/// </snippet>
/// </example>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string,string,string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string,string,string,out ShouldProcessReason)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldContinue(string,string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldContinue(string,string,ref bool,ref bool)"/>
public bool ShouldProcess(string target, string action)
{
string verboseDescription = StringUtil.Format(CommandBaseStrings.ShouldProcessMessage,
action,
target,
null);
ShouldProcessReason shouldProcessReason;
return DoShouldProcess(verboseDescription, null, null, out shouldProcessReason);
}
/// <summary>
/// Confirm the operation with the user. Cmdlets which make changes
/// (e.g. delete files, stop services etc.) should call ShouldProcess
/// to give the user the opportunity to confirm that the operation
/// should actually be performed.
///
/// This variant allows the caller to specify the complete text
/// describing the operation, rather than just the name and action.
/// </summary>
/// <param name="verboseDescription">
/// Textual description of the action to be performed.
/// This is what will be displayed to the user for
/// ActionPreference.Continue.
/// </param>
/// <param name="verboseWarning">
/// Textual query of whether the action should be performed,
/// usually in the form of a question.
/// This is what will be displayed to the user for
/// ActionPreference.Inquire.
/// </param>
/// <param name="caption">
/// Caption of the window which may be displayed
/// if the user is prompted whether or not to perform the action.
/// <paramref name="caption"/> may be displayed by some hosts, but not all.
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread.
/// ShouldProcess may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
/// </exception>
/// <returns>
/// If ShouldProcess returns true, the operation should be performed.
/// If ShouldProcess returns false, the operation should not be
/// performed, and the Cmdlet should move on to the next target resource.
/// </returns>
/// <remarks>
/// A Cmdlet should declare
/// [Cmdlet( SupportsShouldProcess = true )]
/// if-and-only-if it calls ShouldProcess before making changes.
///
/// ShouldProcess may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
///
/// ShouldProcess will take into account command-line settings
/// and preference variables in determining what it should return
/// and whether it should prompt the user.
/// </remarks>
/// <remarks>
/// If the pipeline is terminated due to ActionPreference.Stop
/// or ActionPreference.Inquire, this method will throw
/// <see cref="System.Management.Automation.PipelineStoppedException"/>,
/// but the command failure will ultimately be
/// <see cref="System.Management.Automation.ActionPreferenceStopException"/>,
/// </remarks>
/// <example>
/// <snippet Code="C#">
/// namespace Microsoft.Samples.MSH.Cmdlet
/// {
/// [Cmdlet(VerbsCommon.Remove,"myobjecttype3")]
/// public class RemoveMyObjectType3 : PSCmdlet
/// {
/// [Parameter( Mandatory = true )]
/// public string Filename
/// {
/// get { return filename; }
/// set { filename = value; }
/// }
/// private string filename;
///
/// public override void ProcessRecord()
/// {
/// if (ShouldProcess(
/// string.Format("Deleting file {0}",filename),
/// string.Format("Are you sure you want to delete file {0}?", filename),
/// "Delete file"))
/// {
/// // delete the object
/// }
/// }
/// }
/// }
/// </snippet>
/// </example>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string,string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string,string,string, out ShouldProcessReason)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldContinue(string,string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldContinue(string,string,ref bool,ref bool)"/>
public bool ShouldProcess(
string verboseDescription,
string verboseWarning,
string caption)
{
ShouldProcessReason shouldProcessReason;
return DoShouldProcess(
verboseDescription,
verboseWarning,
caption,
out shouldProcessReason);
}
/// <summary>
/// Confirm the operation with the user. Cmdlets which make changes
/// (e.g. delete files, stop services etc.) should call ShouldProcess
/// to give the user the opportunity to confirm that the operation
/// should actually be performed.
///
/// This variant allows the caller to specify the complete text
/// describing the operation, rather than just the name and action.
/// </summary>
/// <param name="verboseDescription">
/// Textual description of the action to be performed.
/// This is what will be displayed to the user for
/// ActionPreference.Continue.
/// </param>
/// <param name="verboseWarning">
/// Textual query of whether the action should be performed,
/// usually in the form of a question.
/// This is what will be displayed to the user for
/// ActionPreference.Inquire.
/// </param>
/// <param name="caption">
/// Caption of the window which may be displayed
/// if the user is prompted whether or not to perform the action.
/// <paramref name="caption"/> may be displayed by some hosts, but not all.
/// </param>
/// <param name="shouldProcessReason">
/// Indicates the reason(s) why ShouldProcess returned what it returned.
/// Only the reasons enumerated in
/// <see cref="System.Management.Automation.ShouldProcessReason"/>
/// are returned.
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread.
/// ShouldProcess may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
/// </exception>
/// <returns>
/// If ShouldProcess returns true, the operation should be performed.
/// If ShouldProcess returns false, the operation should not be
/// performed, and the Cmdlet should move on to the next target resource.
/// </returns>
/// <remarks>
/// A Cmdlet should declare
/// [Cmdlet( SupportsShouldProcess = true )]
/// if-and-only-if it calls ShouldProcess before making changes.
///
/// ShouldProcess may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
///
/// ShouldProcess will take into account command-line settings
/// and preference variables in determining what it should return
/// and whether it should prompt the user.
/// </remarks>
/// <remarks>
/// If the pipeline is terminated due to ActionPreference.Stop
/// or ActionPreference.Inquire, this method will throw
/// <see cref="System.Management.Automation.PipelineStoppedException"/>,
/// but the command failure will ultimately be
/// <see cref="System.Management.Automation.ActionPreferenceStopException"/>,
/// </remarks>
/// <example>
/// <snippet Code="C#">
/// namespace Microsoft.Samples.MSH.Cmdlet
/// {
/// [Cmdlet(VerbsCommon.Remove,"myobjecttype3")]
/// public class RemoveMyObjectType3 : PSCmdlet
/// {
/// [Parameter( Mandatory = true )]
/// public string Filename
/// {
/// get { return filename; }
/// set { filename = value; }
/// }
/// private string filename;
///
/// public override void ProcessRecord()
/// {
/// ShouldProcessReason shouldProcessReason;
/// if (ShouldProcess(
/// string.Format("Deleting file {0}",filename),
/// string.Format("Are you sure you want to delete file {0}?", filename),
/// "Delete file",
/// out shouldProcessReason))
/// {
/// // delete the object
/// }
/// }
/// }
/// }
/// </snippet>
/// </example>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string,string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string,string,string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldContinue(string,string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldContinue(string,string,ref bool,ref bool)"/>
public bool ShouldProcess(
string verboseDescription,
string verboseWarning,
string caption,
out ShouldProcessReason shouldProcessReason)
{
return DoShouldProcess(
verboseDescription,
verboseWarning,
caption,
out shouldProcessReason);
}
private bool CanShouldProcessAutoConfirm()
{
// retrieve ConfirmImpact from commandInfo
CommandMetadata commandMetadata = _commandInfo.CommandMetadata;
if (commandMetadata == null)
{
Dbg.Assert(false, "Expected CommandMetadata");
return true;
}
ConfirmImpact cmdletConfirmImpact = commandMetadata.ConfirmImpact;
// compare to ConfirmPreference
ConfirmImpact threshold = ConfirmPreference;
if ((threshold == ConfirmImpact.None) || (threshold > cmdletConfirmImpact))
{
return true;
}
return false;
}
/// <summary>
/// Helper function for ShouldProcess APIs.
/// </summary>
/// <param name="verboseDescription">
/// Description of operation, to be printed for Continue or WhatIf
/// </param>
/// <param name="verboseWarning">
/// Warning prompt, to be printed for Inquire
/// </param>
/// <param name="caption">
/// This is the caption of the window which may be displayed
/// if the user is prompted whether or not to perform the action.
/// It may be displayed by some hosts, but not all.
/// </param>
/// <param name="shouldProcessReason">
/// Indicates the reason(s) why ShouldProcess returned what it returned.
/// Only the reasons enumerated in
/// <see cref="System.Management.Automation.ShouldProcessReason"/>
/// are returned.
/// </param>
/// <remarks>true iff the action should be performed</remarks>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <remarks>
/// If the pipeline is terminated due to ActionPreference.Stop
/// or ActionPreference.Inquire, this method will throw
/// <see cref="System.Management.Automation.PipelineStoppedException"/>,
/// but the command failure will ultimately be
/// <see cref="System.Management.Automation.ActionPreferenceStopException"/>,
/// </remarks>
private bool DoShouldProcess(
string verboseDescription,
string verboseWarning,
string caption,
out ShouldProcessReason shouldProcessReason)
{
ThrowIfStopping();
shouldProcessReason = ShouldProcessReason.None;
switch (lastShouldProcessContinueStatus)
{
case ContinueStatus.NoToAll:
return false;
case ContinueStatus.YesToAll:
return true;
}
if (WhatIf)
{
// 2005/05/24 908827
// WriteDebug/WriteVerbose/WriteProgress/WriteWarning should only be callable from the main thread
//
// WriteError/WriteObject have a check that prevents them to be called from outside
// Begin/Process/End. This is done because the Pipeline needs to be ready before these
// functions can be called.
//
// WriteDebug/Warning/Verbose/Process used to do the same check, even though it is not
// strictly needed. If we ever implement pipelines for these objects we may need to
// enforce the check again.
//
// See bug 583774 in the Windows 7 database for more details.
//
ThrowIfWriteNotPermitted(false);
shouldProcessReason = ShouldProcessReason.WhatIf;
string whatIfMessage =
StringUtil.Format(CommandBaseStrings.ShouldProcessWhatIfMessage,
verboseDescription);
CBhost.InternalUI.TranscribeResult(whatIfMessage);
CBhost.UI.WriteLine(whatIfMessage);
return false;
}
if (this.CanShouldProcessAutoConfirm())
{
if (this.Verbose)
{
// 2005/05/24 908827
// WriteDebug/WriteVerbose/WriteProgress/WriteWarning should only be callable from the main thread
//
// WriteError/WriteObject have a check that prevents them to be called from outside
// Begin/Process/End. This is done because the Pipeline needs to be ready before these
// functions can be called.
//
// WriteDebug/Warning/Verbose/Process used to do the same check, even though it is not
// strictly needed. If we ever implement pipelines for these objects we may need to
// enforce the check again.
//
// See bug 583774 in the Windows 7 database for more details.
//
ThrowIfWriteNotPermitted(false);
WriteVerbose(verboseDescription);
}
return true;
}
if (string.IsNullOrEmpty(verboseWarning))
verboseWarning = StringUtil.Format(CommandBaseStrings.ShouldProcessWarningFallback,
verboseDescription);
// 2005/05/24 908827
// WriteDebug/WriteVerbose/WriteProgress/WriteWarning should only be callable from the main thread
//
// WriteError/WriteObject have a check that prevents them to be called from outside
// Begin/Process/End. This is done because the Pipeline needs to be ready before these
// functions can be called.
//
// WriteDebug/Warning/Verbose/Process used to do the same check, even though it is not
// strictly needed. If we ever implement pipelines for these objects we may need to
// enforce the check again.
//
// See bug 583774 in the Windows 7 database for more details.
//
ThrowIfWriteNotPermitted(false);
lastShouldProcessContinueStatus = InquireHelper(
verboseWarning,
caption,
true, // allowYesToAll
true, // allowNoToAll
false, // replaceNoWithHalt
false // hasSecurityImpact
);
switch (lastShouldProcessContinueStatus)
{
case ContinueStatus.No:
case ContinueStatus.NoToAll:
return false;
}
return true;
}
internal enum ShouldProcessPossibleOptimization
{
AutoYes_CanSkipShouldProcessCall,
AutoYes_CanCallShouldProcessAsynchronously,
AutoNo_CanCallShouldProcessAsynchronously,
NoOptimizationPossible,
}
internal ShouldProcessPossibleOptimization CalculatePossibleShouldProcessOptimization()
{
if (this.WhatIf)
{
return ShouldProcessPossibleOptimization.AutoNo_CanCallShouldProcessAsynchronously;
}
if (this.CanShouldProcessAutoConfirm())
{
if (this.Verbose)
{
return ShouldProcessPossibleOptimization.AutoYes_CanCallShouldProcessAsynchronously;
}
else
{
return ShouldProcessPossibleOptimization.AutoYes_CanSkipShouldProcessCall;
}
}
return ShouldProcessPossibleOptimization.NoOptimizationPossible;
}
#endregion ShouldProcess
#region ShouldContinue
/// <summary>
/// Confirm an operation or grouping of operations with the user.
/// This differs from ShouldProcess in that it is not affected by
/// preference settings or command-line parameters,
/// it always does the query.
/// This variant only offers Yes/No, not YesToAll/NoToAll.
/// </summary>
/// <param name="query">
/// Textual query of whether the action should be performed,
/// usually in the form of a question.
/// </param>
/// <param name="caption">
/// Caption of the window which may be displayed
/// when the user is prompted whether or not to perform the action.
/// It may be displayed by some hosts, but not all.
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread.
/// ShouldContinue may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
/// </exception>
/// <returns>
/// If ShouldContinue returns true, the operation should be performed.
/// If ShouldContinue returns false, the operation should not be
/// performed, and the Cmdlet should move on to the next target resource.
/// </returns>
/// <remarks>
/// Cmdlets using ShouldContinue should also offer a "bool Force"
/// parameter which bypasses the calls to ShouldContinue
/// and ShouldProcess.
/// If this is not done, it will be difficult to use the Cmdlet
/// from scripts and non-interactive hosts.
///
/// Cmdlets using ShouldContinue must still verify operations
/// which will make changes using ShouldProcess.
/// This will assure that settings such as -WhatIf work properly.
/// You may call ShouldContinue either before or after ShouldProcess.
///
/// ShouldContinue may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
///
/// Cmdlets may have different "classes" of confirmations. For example,
/// "del" confirms whether files in a particular directory should be
/// deleted, whether read-only files should be deleted, etc.
/// Cmdlets can use ShouldContinue to store YesToAll/NoToAll members
/// for each such "class" to keep track of whether the user has
/// confirmed "delete all read-only files" etc.
/// ShouldProcess offers YesToAll/NoToAll automatically,
/// but answering YesToAll or NoToAll applies to all subsequent calls
/// to ShouldProcess for the Cmdlet instance.
/// </remarks>
/// <example>
/// <snippet Code="C#">
/// namespace Microsoft.Samples.MSH.Cmdlet
/// {
/// [Cmdlet(VerbsCommon.Remove,"myobjecttype4")]
/// public class RemoveMyObjectType4 : PSCmdlet
/// {
/// [Parameter( Mandatory = true )]
/// public string Filename
/// {
/// get { return filename; }
/// set { filename = value; }
/// }
/// private string filename;
///
/// [Parameter]
/// public SwitchParameter Force
/// {
/// get { return force; }
/// set { force = value; }
/// }
/// private bool force;
///
/// public override void ProcessRecord()
/// {
/// if (ShouldProcess(
/// string.Format("Deleting file {0}",filename),
/// string.Format("Are you sure you want to delete file {0}", filename),
/// "Delete file"))
/// {
/// if (IsReadOnly(filename))
/// {
/// if (!Force &amp;&amp; !ShouldContinue(
/// string.Format("File {0} is read-only. Are you sure you want to delete read-only file {0}?", filename),
/// "Delete file"))
/// )
/// {
/// return;
/// }
/// }
/// // delete the object
/// }
/// }
/// }
/// }
/// </snippet>
/// </example>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldContinue(string,string,ref bool,ref bool)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string,string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string,string,string)"/>
public bool ShouldContinue(string query, string caption)
{
bool yesToAll = false;
bool noToAll = false;
return DoShouldContinue(
query,
caption,
hasSecurityImpact: false,
supportsToAllOptions: false,
ref yesToAll,
ref noToAll);
}
/// <summary>
/// Confirm an operation or grouping of operations with the user.
/// This differs from ShouldProcess in that it is not affected by
/// preference settings or command-line parameters,
/// it always does the query.
/// This variant offers Yes, No, YesToAll and NoToAll.
/// </summary>
/// <param name="query">
/// Textual query of whether the action should be performed,
/// usually in the form of a question.
/// </param>
/// <param name="caption">
/// Caption of the window which may be displayed
/// when the user is prompted whether or not to perform the action.
/// It may be displayed by some hosts, but not all.
/// </param>
/// <param name="hasSecurityImpact">
/// true if the operation being confirmed has a security impact. If specified,
/// the default option selected in the selection menu is 'No'.
/// </param>
/// <param name="yesToAll">
/// true iff user selects YesToAll. If this is already true,
/// ShouldContinue will bypass the prompt and return true.
/// </param>
/// <param name="noToAll">
/// true iff user selects NoToAll. If this is already true,
/// ShouldContinue will bypass the prompt and return false.
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread.
/// ShouldContinue may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
/// </exception>
/// <returns>
/// If ShouldContinue returns true, the operation should be performed.
/// If ShouldContinue returns false, the operation should not be
/// performed, and the Cmdlet should move on to the next target resource.
/// </returns>
public bool ShouldContinue(
string query, string caption, bool hasSecurityImpact, ref bool yesToAll, ref bool noToAll)
{
return DoShouldContinue(query, caption, hasSecurityImpact, true, ref yesToAll, ref noToAll);
}
/// <summary>
/// Confirm an operation or grouping of operations with the user.
/// This differs from ShouldProcess in that it is not affected by
/// preference settings or command-line parameters,
/// it always does the query.
/// This variant offers Yes, No, YesToAll and NoToAll.
/// </summary>
/// <param name="query">
/// Textual query of whether the action should be performed,
/// usually in the form of a question.
/// </param>
/// <param name="caption">
/// Caption of the window which may be displayed
/// when the user is prompted whether or not to perform the action.
/// It may be displayed by some hosts, but not all.
/// </param>
/// <param name="yesToAll">
/// true iff user selects YesToAll. If this is already true,
/// ShouldContinue will bypass the prompt and return true.
/// </param>
/// <param name="noToAll">
/// true iff user selects NoToAll. If this is already true,
/// ShouldContinue will bypass the prompt and return false.
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread.
/// ShouldContinue may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
/// </exception>
/// <returns>
/// If ShouldContinue returns true, the operation should be performed.
/// If ShouldContinue returns false, the operation should not be
/// performed, and the Cmdlet should move on to the next target resource.
/// </returns>
/// <remarks>
/// Cmdlets using ShouldContinue should also offer a "bool Force"
/// parameter which bypasses the calls to ShouldContinue
/// and ShouldProcess.
/// If this is not done, it will be difficult to use the Cmdlet
/// from scripts and non-interactive hosts.
///
/// Cmdlets using ShouldContinue must still verify operations
/// which will make changes using ShouldProcess.
/// This will assure that settings such as -WhatIf work properly.
/// You may call ShouldContinue either before or after ShouldProcess.
///
/// ShouldContinue may only be called during a call to this Cmdlet's
/// implementation of ProcessRecord, BeginProcessing or EndProcessing,
/// and only from that thread.
///
/// Cmdlets may have different "classes" of confirmations. For example,
/// "del" confirms whether files in a particular directory should be
/// deleted, whether read-only files should be deleted, etc.
/// Cmdlets can use ShouldContinue to store YesToAll/NoToAll members
/// for each such "class" to keep track of whether the user has
/// confirmed "delete all read-only files" etc.
/// ShouldProcess offers YesToAll/NoToAll automatically,
/// but answering YesToAll or NoToAll applies to all subsequent calls
/// to ShouldProcess for the Cmdlet instance.
/// </remarks>
/// <example>
/// <snippet Code="C#">
/// namespace Microsoft.Samples.MSH.Cmdlet
/// {
/// [Cmdlet(VerbsCommon.Remove,"myobjecttype4")]
/// public class RemoveMyObjectType5 : PSCmdlet
/// {
/// [Parameter( Mandatory = true )]
/// public string Filename
/// {
/// get { return filename; }
/// set { filename = value; }
/// }
/// private string filename;
///
/// [Parameter]
/// public SwitchParameter Force
/// {
/// get { return force; }
/// set { force = value; }
/// }
/// private bool force;
///
/// private bool yesToAll;
/// private bool noToAll;
///
/// public override void ProcessRecord()
/// {
/// if (ShouldProcess(
/// string.Format("Deleting file {0}",filename),
/// string.Format("Are you sure you want to delete file {0}", filename),
/// "Delete file"))
/// {
/// if (IsReadOnly(filename))
/// {
/// if (!Force &amp;&amp; !ShouldContinue(
/// string.Format("File {0} is read-only. Are you sure you want to delete read-only file {0}?", filename),
/// "Delete file"),
/// ref yesToAll,
/// ref noToAll
/// )
/// {
/// return;
/// }
/// }
/// // delete the object
/// }
/// }
/// }
/// }
/// </snippet>
/// </example>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldContinue(string,string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string,string)"/>
/// <seealso cref="System.Management.Automation.Cmdlet.ShouldProcess(string,string,string)"/>
public bool ShouldContinue(
string query, string caption, ref bool yesToAll, ref bool noToAll)
{
return DoShouldContinue(query, caption, false, true, ref yesToAll, ref noToAll);
}
private bool DoShouldContinue(
string query,
string caption,
bool hasSecurityImpact,
bool supportsToAllOptions,
ref bool yesToAll,
ref bool noToAll)
{
ThrowIfStopping();
//
// WriteError/WriteObject have a check that prevents them to be called from outside
// Begin/Process/End. This is done because the Pipeline needs to be ready before these
// functions can be called.
//
// WriteDebug/Warning/Verbose/Process used to do the same check, even though it is not
// strictly needed. If we ever implement pipelines for these objects we may need to
// enforce the check again.
//
// See bug 583774 in the Windows 7 database for more details.
//
ThrowIfWriteNotPermitted(false);
if (noToAll)
return false;
else if (yesToAll)
return true;
ContinueStatus continueStatus = InquireHelper(
query,
caption,
supportsToAllOptions, // allowYesToAll
supportsToAllOptions, // allowNoToAll
false, // replaceNoWithHalt
hasSecurityImpact // hasSecurityImpact
);
switch (continueStatus)
{
case ContinueStatus.No:
return false;
case ContinueStatus.NoToAll:
noToAll = true;
return false;
case ContinueStatus.YesToAll:
yesToAll = true;
break;
}
return true;
}
#endregion ShouldContinue
#endregion Should
#region Transaction Support
/// <summary>
/// Returns true if a transaction is available for use.
/// </summary>
public bool TransactionAvailable()
{
return UseTransactionFlagSet && Context.TransactionManager.HasTransaction;
}
/// <summary>
/// Gets an object that surfaces the current PowerShell transaction.
/// When this object is disposed, PowerShell resets the active transaction.
/// </summary>
public PSTransactionContext CurrentPSTransaction
{
get
{
if (!TransactionAvailable())
{
string error = null;
if (!UseTransactionFlagSet)
error = TransactionStrings.CmdletRequiresUseTx;
else
error = TransactionStrings.NoTransactionAvailable;
// We want to throw in this situation, and want to use a
// property because it mimics the C# using(TransactionScope ...) syntax
#pragma warning suppress 56503
throw new InvalidOperationException(error);
}
return new PSTransactionContext(Context.TransactionManager);
}
}
#endregion Transaction Support
#region Misc
/// <summary>
/// Implementation of ThrowTerminatingError.
/// </summary>
/// <param name="errorRecord">
/// The error which caused the command to be terminated
/// </param>
/// <exception cref="PipelineStoppedException">
/// always
/// </exception>
/// <remarks>
/// <see cref="System.Management.Automation.Cmdlet.ThrowTerminatingError"/>
/// terminates the command, where
/// <see cref="System.Management.Automation.ICommandRuntime.WriteError"/>
/// allows the command to continue.
///
/// The cmdlet can also terminate the command by simply throwing
/// any exception. When the cmdlet's implementation of
/// <see cref="System.Management.Automation.Cmdlet.ProcessRecord"/>,
/// <see cref="System.Management.Automation.Cmdlet.BeginProcessing"/> or
/// <see cref="System.Management.Automation.Cmdlet.EndProcessing"/>
/// throws an exception, the Engine will always catch the exception
/// and report it as a terminating error.
/// However, it is preferred for the cmdlet to call
/// <see cref="System.Management.Automation.Cmdlet.ThrowTerminatingError"/>,
/// so that the additional information in
/// <see cref="System.Management.Automation.ErrorRecord"/>
/// is available.
/// <see cref="System.Management.Automation.Cmdlet.ThrowTerminatingError"/>
/// always throws
/// <see cref="System.Management.Automation.PipelineStoppedException"/>,
/// regardless of what error was specified in <paramref name="errorRecord"/>.
/// The Cmdlet should generally just allow
/// <see cref="System.Management.Automation.PipelineStoppedException"/>.
/// to percolate up to the caller of
/// <see cref="System.Management.Automation.Cmdlet.ProcessRecord"/>.
/// etc.
/// </remarks>
[System.Diagnostics.CodeAnalysis.DoesNotReturn]
public void ThrowTerminatingError(ErrorRecord errorRecord)
{
ThrowIfStopping();
if (errorRecord == null)
{
throw PSTraceSource.NewArgumentNullException(nameof(errorRecord));
}
errorRecord.SetInvocationInfo(MyInvocation);
if (errorRecord.ErrorDetails != null
&& errorRecord.ErrorDetails.TextLookupError != null)
{
Exception textLookupError = errorRecord.ErrorDetails.TextLookupError;
errorRecord.ErrorDetails.TextLookupError = null;
MshLog.LogCommandHealthEvent(
Context,
textLookupError,
Severity.Warning);
}
// This code forces the stack trace and source fields to be populated
if (errorRecord.Exception != null
&& string.IsNullOrEmpty(errorRecord.Exception.StackTrace))
{
try
{
throw errorRecord.Exception;
}
catch (Exception)
{
// no need to worry about severe exceptions since
// it wasn't really thrown originally
}
}
CmdletInvocationException e =
new CmdletInvocationException(errorRecord);
// If the error action preference is set to break, break immediately
// into the debugger
if (ErrorAction == ActionPreference.Break)
{
Context.Debugger?.Break(e.InnerException ?? e);
}
// Code sees only that execution stopped
throw ManageException(e);
}
#endregion Misc
#region Data Merging
/// <summary>
/// Data streams available for merging.
/// </summary>
internal enum MergeDataStream
{
/// <summary>
/// No data stream available for merging.
/// </summary>
None = 0,
/// <summary>
/// All data streams.
/// </summary>
All = 1,
/// <summary>
/// Success output.
/// </summary>
Output = 2,
/// <summary>
/// Error output.
/// </summary>
Error = 3,
/// <summary>
/// Warning output.
/// </summary>
Warning = 4,
/// <summary>
/// Verbose output.
/// </summary>
Verbose = 5,
/// <summary>
/// Debug output.
/// </summary>
Debug = 6,
/// <summary>
/// Host output.
/// </summary>
Host = 7,
/// <summary>
/// Information output.
/// </summary>
Information = 8
}
/// <summary>
/// Get/sets error data stream merge state.
/// </summary>
internal MergeDataStream ErrorMergeTo { get; set; }
/// <summary>
/// Method to set data stream merging based on passed in runtime object.
/// </summary>
/// <param name="fromRuntime">MshCommandRuntime object.</param>
internal void SetMergeFromRuntime(MshCommandRuntime fromRuntime)
{
this.ErrorMergeTo = fromRuntime.ErrorMergeTo;
if (fromRuntime.WarningOutputPipe != null)
{
this.WarningOutputPipe = fromRuntime.WarningOutputPipe;
}
if (fromRuntime.VerboseOutputPipe != null)
{
this.VerboseOutputPipe = fromRuntime.VerboseOutputPipe;
}
if (fromRuntime.DebugOutputPipe != null)
{
this.DebugOutputPipe = fromRuntime.DebugOutputPipe;
}
if (fromRuntime.InformationOutputPipe != null)
{
this.InformationOutputPipe = fromRuntime.InformationOutputPipe;
}
}
//
// Legacy merge hints.
//
/// <summary>
/// Claims the unclaimed error output of all previous commands.
/// </summary>
internal bool MergeUnclaimedPreviousErrorResults { get; set; } = false;
#endregion
#region Internal Pipes
/// <summary>
/// Gets or sets the input pipe.
/// </summary>
internal Pipe InputPipe
{
get { return _inputPipe ??= new Pipe(); }
set { _inputPipe = value; }
}
/// <summary>
/// Gets or sets the output pipe.
/// </summary>
internal Pipe OutputPipe
{
get { return _outputPipe ??= new Pipe(); }
set { _outputPipe = value; }
}
internal object[] GetResultsAsArray()
{
if (_outputPipe == null)
return StaticEmptyArray;
return _outputPipe.ToArray();
}
/// <summary>
/// An empty array that is declared statically so we don't keep
/// allocating them over and over...
/// </summary>
internal static readonly object[] StaticEmptyArray = Array.Empty<object>();
/// <summary>
/// Gets or sets the error pipe.
/// </summary>
internal Pipe ErrorOutputPipe
{
get { return _errorOutputPipe ??= new Pipe(); }
set { _errorOutputPipe = value; }
}
/// <summary>
/// Gets or sets the warning output pipe.
/// </summary>
internal Pipe WarningOutputPipe { get; set; }
/// <summary>
/// Gets or sets the verbose output pipe.
/// </summary>
internal Pipe VerboseOutputPipe { get; set; }
/// <summary>
/// Gets or sets the debug output pipe.
/// </summary>
internal Pipe DebugOutputPipe { get; set; }
/// <summary>
/// Gets or sets the informational output pipe.
/// </summary>
internal Pipe InformationOutputPipe { get; set; }
#endregion
#region Internal helpers
/// <summary>
/// Throws if the pipeline is stopping.
/// </summary>
/// <exception cref="System.Management.Automation.PipelineStoppedException"></exception>
internal void ThrowIfStopping()
{
if (IsStopping)
throw new PipelineStoppedException();
}
/// <summary>
/// Throws if the caller is trying to call WriteObject/WriteError
/// from the wrong thread, or not during a call to
/// BeginProcessing/ProcessRecord/EndProcessing.
/// </summary>
/// <exception cref="System.InvalidOperationException"></exception>
internal void ThrowIfWriteNotPermitted(bool needsToWriteToPipeline)
{
if (this.PipelineProcessor == null
|| _thisCommand != this.PipelineProcessor._permittedToWrite
|| needsToWriteToPipeline && !this.PipelineProcessor._permittedToWriteToPipeline
|| Thread.CurrentThread != this.PipelineProcessor._permittedToWriteThread
)
{
// Only generate these exceptions if a pipeline has already been declared as the 'writing' pipeline.
// Otherwise, these are probably infrastructure messages and can be ignored.
if (this.PipelineProcessor?._permittedToWrite != null)
{
throw PSTraceSource.NewInvalidOperationException(
PipelineStrings.WriteNotPermitted);
}
}
}
/// <summary>
/// WriteObject/WriteObjecs/WriteError are only allowed during this scope.
/// Be sure to use this object only in "using" so that it is reliably
/// disposed and follows stack semantics.
/// </summary>
/// <returns>IDisposable.</returns>
internal IDisposable AllowThisCommandToWrite(bool permittedToWriteToPipeline)
{
return new AllowWrite(_thisCommand, permittedToWriteToPipeline);
}
private sealed class AllowWrite : IDisposable
{
/// <summary>
/// Begin the scope where WriteObject/WriteError is permitted.
/// </summary>
internal AllowWrite(InternalCommand permittedToWrite, bool permittedToWriteToPipeline)
{
if (permittedToWrite == null)
throw PSTraceSource.NewArgumentNullException(nameof(permittedToWrite));
if (!(permittedToWrite.commandRuntime is MshCommandRuntime mcr))
throw PSTraceSource.NewArgumentNullException("permittedToWrite.CommandRuntime");
_pp = mcr.PipelineProcessor;
if (_pp == null)
throw PSTraceSource.NewArgumentNullException("permittedToWrite.CommandRuntime.PipelineProcessor");
_wasPermittedToWrite = _pp._permittedToWrite;
_wasPermittedToWriteToPipeline = _pp._permittedToWriteToPipeline;
_wasPermittedToWriteThread = _pp._permittedToWriteThread;
_pp._permittedToWrite = permittedToWrite;
_pp._permittedToWriteToPipeline = permittedToWriteToPipeline;
_pp._permittedToWriteThread = Thread.CurrentThread;
}
/// <summary>
/// End the scope where WriteObject/WriteError is permitted.
/// </summary>
/// <!--
/// Not a true public, since the class is internal.
/// This is public only due to C# interface rules.
/// -->
public void Dispose()
{
_pp._permittedToWrite = _wasPermittedToWrite;
_pp._permittedToWriteToPipeline = _wasPermittedToWriteToPipeline;
_pp._permittedToWriteThread = _wasPermittedToWriteThread;
GC.SuppressFinalize(this);
}
// There is no finalizer, by design. This class relies on always
// being disposed and always following stack semantics.
private readonly PipelineProcessor _pp = null;
private readonly InternalCommand _wasPermittedToWrite = null;
private readonly bool _wasPermittedToWriteToPipeline = false;
private readonly Thread _wasPermittedToWriteThread = null;
}
/// <summary>
/// Stores the exception to be returned from
/// PipelineProcessor.SynchronousExecute,
/// and writes it to the error variable.
/// The general pattern is to call
/// throw ManageException(e);
/// </summary>
/// <param name="e">The exception.</param>
/// <returns>PipelineStoppedException.</returns>
public Exception ManageException(Exception e)
{
if (e == null)
throw PSTraceSource.NewArgumentNullException(nameof(e));
if (PipelineProcessor != null)
{
PipelineProcessor.RecordFailure(e, _thisCommand);
}
// 1021203-2005/05/09-JonN
// HaltCommandException will cause the command
// to stop, but not be reported as an error.
// 913088-2005/06/06
// PipelineStoppedException should not get added to $Error
// 2008/06/25 - narrieta: ExistNestedPromptException should not be added to $error either
// 2019/10/18 - StopUpstreamCommandsException should not be added either
if (e is not HaltCommandException
&& e is not PipelineStoppedException
&& e is not ExitNestedPromptException
&& e is not StopUpstreamCommandsException)
{
try
{
AppendErrorToVariables(e);
}
catch
{
// Catch all OK, the error variables might be corrupted.
}
// Log a command health event
MshLog.LogCommandHealthEvent(
Context,
e,
Severity.Warning);
}
// Upstream Cmdlets see only that execution stopped
return new PipelineStoppedException();
}
#endregion Internal helpers
#region Error PSVariable
private IList _errorVarList;
/// <summary>
/// ErrorVariable tells which variable to populate with the errors.
/// Use +varname to append to the variable rather than clearing it.
/// </summary>
/// <remarks>
/// This is a common parameter via class CommonParameters.
/// </remarks>
internal string ErrorVariable { get; set; }
internal void SetupErrorVariable()
{
SetupVariable(VariableStreamKind.Error, this.ErrorVariable, ref _errorVarList);
}
private void EnsureVariableParameterAllowed()
{
if ((Context.LanguageMode == PSLanguageMode.NoLanguage) ||
(Context.LanguageMode == PSLanguageMode.RestrictedLanguage))
{
throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException),
null, "VariableReferenceNotSupportedInDataSection",
ParserStrings.VariableReferenceNotSupportedInDataSection,
ParserStrings.DefaultAllowedVariablesInDataSection);
}
}
/// <summary>
/// Append an error to the ErrorVariable if specified, and also to $ERROR.
/// </summary>
/// <param name="obj">Exception or ErrorRecord.</param>
/// <exception cref="System.Management.Automation.ExtendedTypeSystemException">
/// (An error occurred working with the error variable or $ERROR.
/// </exception>
internal void AppendErrorToVariables(object obj)
{
if (obj == null)
return;
AppendDollarError(obj);
this.OutputPipe.AppendVariableList(VariableStreamKind.Error, obj);
}
/// <summary>
/// Appends the object to $global:error. Non-terminating errors
/// are always added (even if they are redirected to another
/// Cmdlet), but terminating errors are only added if they are
/// at the top-level scope (the LocalPipeline scope).
/// We insert at position 0 and delete from position 63.
/// </summary>
/// <param name="obj">
/// ErrorRecord or Exception to be written to $global:error
/// </param>
/// <exception cref="System.Management.Automation.ExtendedTypeSystemException">
/// An error occurred accessing $ERROR.
/// </exception>
private void AppendDollarError(object obj)
{
if (obj is Exception)
{
if (this.PipelineProcessor == null || !this.PipelineProcessor.TopLevel)
return; // not outermost scope
}
Context.AppendDollarError(obj);
}
#endregion Error PSVariable
#region Warning PSVariable
private IList _warningVarList;
/// <summary>
/// WarningVariable tells which variable to populate with the warnings.
/// Use +varname to append to the variable rather than clearing it.
/// </summary>
/// <remarks>
/// This is a common parameter via class CommonParameters.
/// </remarks>
internal string WarningVariable { get; set; }
internal void SetupWarningVariable()
{
SetupVariable(VariableStreamKind.Warning, this.WarningVariable, ref _warningVarList);
}
/// <summary>
/// Append a warning to WarningVariable if specified.
/// </summary>
/// <param name="obj">The warning message.</param>
internal void AppendWarningVarList(object obj)
{
this.OutputPipe.AppendVariableList(VariableStreamKind.Warning, obj);
}
#endregion Warning PSVariable
#region Information PSVariable
private IList _informationVarList;
/// <summary>
/// InformationVariable tells which variable to populate with informational output.
/// Use +varname to append to the variable rather than clearing it.
/// </summary>
/// <remarks>
/// This is a common parameter via class CommonParameters.
/// </remarks>
internal string InformationVariable { get; set; }
internal void SetupInformationVariable()
{
SetupVariable(VariableStreamKind.Information, this.InformationVariable, ref _informationVarList);
}
internal void SetupVariable(VariableStreamKind streamKind, string variableName, ref IList varList)
{
if (string.IsNullOrEmpty(variableName))
{
return;
}
EnsureVariableParameterAllowed();
if (_state == null)
_state = new SessionState(Context.EngineSessionState);
if (variableName.StartsWith('+'))
{
variableName = variableName.Substring(1);
object oldValue = PSObject.Base(_state.PSVariable.GetValue(variableName));
varList = oldValue as IList;
if (varList == null)
{
varList = new ArrayList();
if (oldValue != null && AutomationNull.Value != oldValue)
{
IEnumerable enumerable = LanguagePrimitives.GetEnumerable(oldValue);
if (enumerable != null)
{
foreach (object o in enumerable)
{
varList.Add(o);
}
}
else
{
varList.Add(oldValue);
}
}
}
else if (varList.IsFixedSize)
{
ArrayList varListNew = new ArrayList();
varListNew.AddRange(varList);
varList = varListNew;
}
}
else
{
varList = new ArrayList();
}
if (_thisCommand is not PSScriptCmdlet)
{
this.OutputPipe.AddVariableList(streamKind, varList);
}
_state.PSVariable.Set(variableName, varList);
}
/// <summary>
/// Append a Information to InformationVariable if specified.
/// </summary>
/// <param name="obj">The Information message.</param>
internal void AppendInformationVarList(object obj)
{
this.OutputPipe.AppendVariableList(VariableStreamKind.Information, obj);
}
#endregion Information PSVariable
#region Write
internal bool UseSecurityContextRun = true;
/// <summary>
/// Writes an object to the output pipe, skipping the ThrowIfWriteNotPermitted check.
/// </summary>
/// <param name="sendToPipeline">
/// The object to write to the output pipe.
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
internal void _WriteObjectSkipAllowCheck(object sendToPipeline)
{
ThrowIfStopping();
if (AutomationNull.Value == sendToPipeline)
return;
sendToPipeline = LanguagePrimitives.AsPSObjectOrNull(sendToPipeline);
this.OutputPipe.Add(sendToPipeline);
}
/// <summary>
/// Enumerates and writes an object to the output pipe, skipping the ThrowIfWriteNotPermitted check.
/// </summary>
/// <param name="sendToPipeline">
/// The object to enumerate and write to the output pipe.
/// </param>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
internal void _EnumerateAndWriteObjectSkipAllowCheck(object sendToPipeline)
{
IEnumerable enumerable = LanguagePrimitives.GetEnumerable(sendToPipeline);
if (enumerable == null)
{
_WriteObjectSkipAllowCheck(sendToPipeline);
return;
}
ThrowIfStopping();
ArrayList convertedList = new ArrayList();
foreach (object toConvert in enumerable)
{
if (AutomationNull.Value == toConvert)
{
continue;
}
object converted = LanguagePrimitives.AsPSObjectOrNull(toConvert);
convertedList.Add(converted);
}
// Writing normal output with "2>&1"
// bypasses ErrorActionPreference, as intended.
this.OutputPipe.AddItems(convertedList);
}
#endregion Write
#region WriteError
/// <summary>
/// Internal variant: Writes the specified error to the error pipe.
/// </summary>
/// <remarks>
/// Do not call WriteError(e.ErrorRecord).
/// The ErrorRecord contained in the ErrorRecord property of
/// an exception which implements IContainsErrorRecord
/// should not be passed directly to WriteError, since it contains
/// a <see cref="System.Management.Automation.ParentContainsErrorRecordException"/>
/// rather than the real exception.
/// </remarks>
/// <param name="errorRecord">Error.</param>
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread
/// </exception>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <remarks>
/// <see cref="System.Management.Automation.Cmdlet.ThrowTerminatingError"/>
/// terminates the command, where
/// <see cref="System.Management.Automation.ICommandRuntime.WriteError"/>
/// allows the command to continue.
///
/// If the pipeline is terminated due to ActionPreference.Stop
/// or ActionPreference.Inquire, this method will throw
/// <see cref="System.Management.Automation.PipelineStoppedException"/>,
/// but the command failure will ultimately be
/// <see cref="System.Management.Automation.ActionPreferenceStopException"/>,
/// </remarks>
public void WriteError(ErrorRecord errorRecord)
{
WriteError(errorRecord, false);
}
internal void WriteError(ErrorRecord errorRecord, bool overrideInquire)
{
// This check will be repeated in _WriteErrorSkipAllowCheck,
// but we want PipelineStoppedException to take precedence
// over InvalidOperationException if the pipeline has been
// closed.
ThrowIfStopping();
ActionPreference preference = ErrorAction;
if (overrideInquire && preference == ActionPreference.Inquire)
{
preference = ActionPreference.Continue;
}
// Break into the debugger if requested
if (preference == ActionPreference.Break)
{
CBhost?.Runspace?.Debugger?.Break(errorRecord);
}
#if CORECLR
// SecurityContext is not supported in CoreCLR
DoWriteError(new KeyValuePair<ErrorRecord, ActionPreference>(errorRecord, preference));
#else
if (UseSecurityContextRun)
{
if (PipelineProcessor == null || PipelineProcessor.SecurityContext == null)
throw PSTraceSource.NewInvalidOperationException(PipelineStrings.WriteNotPermitted);
ContextCallback delegateCallback =
new ContextCallback(DoWriteError);
SecurityContext.Run(
PipelineProcessor.SecurityContext.CreateCopy(),
delegateCallback,
new KeyValuePair<ErrorRecord, ActionPreference>(errorRecord, preference));
}
else
{
DoWriteError(new KeyValuePair<ErrorRecord, ActionPreference>(errorRecord, preference));
}
#endif
}
/// <exception cref="System.InvalidOperationException">
/// Not permitted at this time or from this thread
/// </exception>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <remarks>
/// If the pipeline is terminated due to ActionPreference.Stop
/// or ActionPreference.Inquire, this method will throw
/// <see cref="System.Management.Automation.PipelineStoppedException"/>,
/// but the command failure will ultimately be
/// <see cref="System.Management.Automation.ActionPreferenceStopException"/>,
/// </remarks>
private void DoWriteError(object obj)
{
KeyValuePair<ErrorRecord, ActionPreference> pair = (KeyValuePair<ErrorRecord, ActionPreference>)obj;
ErrorRecord errorRecord = pair.Key;
ActionPreference preference = pair.Value;
if (errorRecord == null)
{
throw PSTraceSource.NewArgumentNullException("errorRecord");
}
// If this error came from a transacted cmdlet,
// rollback the transaction
if (UseTransaction)
{
if (
(Context.TransactionManager.RollbackPreference != RollbackSeverity.TerminatingError) &&
(Context.TransactionManager.RollbackPreference != RollbackSeverity.Never))
{
Context.TransactionManager.Rollback(true);
}
}
// 2005/07/14-913791 "write-error output is confusing and misleading"
// set InvocationInfo to the script not the command
if (errorRecord.PreserveInvocationInfoOnce)
errorRecord.PreserveInvocationInfoOnce = false;
else
errorRecord.SetInvocationInfo(MyInvocation);
// NOTICE-2004/06/08-JonN 959638
ThrowIfWriteNotPermitted(true);
_WriteErrorSkipAllowCheck(errorRecord, preference);
}
// NOTICE-2004/06/08-JonN 959638
// Use this variant to skip the ThrowIfWriteNotPermitted check
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <remarks>
/// If the pipeline is terminated due to ActionPreference.Stop
/// or ActionPreference.Inquire, this method will throw
/// <see cref="System.Management.Automation.PipelineStoppedException"/>,
/// but the command failure will ultimately be
/// <see cref="System.Management.Automation.ActionPreferenceStopException"/>,
/// </remarks>
internal void _WriteErrorSkipAllowCheck(ErrorRecord errorRecord, ActionPreference? actionPreference = null, bool isNativeError = false)
{
ThrowIfStopping();
if (errorRecord.ErrorDetails != null
&& errorRecord.ErrorDetails.TextLookupError != null)
{
Exception textLookupError = errorRecord.ErrorDetails.TextLookupError;
errorRecord.ErrorDetails.TextLookupError = null;
MshLog.LogCommandHealthEvent(
Context,
textLookupError,
Severity.Warning);
}
if (LogPipelineExecutionDetail)
{
this.PipelineProcessor.LogExecutionError(_thisCommand.MyInvocation, errorRecord);
}
if (!(ExperimentalFeature.IsEnabled("PSNotApplyErrorActionToStderr") && isNativeError))
{
this.PipelineProcessor.ExecutionFailed = true;
ActionPreference preference = ErrorAction;
if (actionPreference.HasValue)
{
preference = actionPreference.Value;
}
// No trace of the error in the 'Ignore' case
if (preference == ActionPreference.Ignore)
{
return; // do not write or record to output pipe
}
// 2004/05/26-JonN
// The object is not written in the SilentlyContinue case
if (preference == ActionPreference.SilentlyContinue)
{
AppendErrorToVariables(errorRecord);
return; // do not write to output pipe
}
if (lastErrorContinueStatus == ContinueStatus.YesToAll)
{
preference = ActionPreference.Continue;
}
switch (preference)
{
case ActionPreference.Stop:
ActionPreferenceStopException e =
new ActionPreferenceStopException(
MyInvocation,
errorRecord,
StringUtil.Format(CommandBaseStrings.ErrorPreferenceStop,
"ErrorActionPreference",
errorRecord.ToString()));
throw ManageException(e);
case ActionPreference.Inquire:
// ignore return value
// this will throw if the user chooses not to continue
lastErrorContinueStatus = InquireHelper(
RuntimeException.RetrieveMessage(errorRecord),
null,
true, // allowYesToAll
false, // allowNoToAll
true, // replaceNoWithHalt
false // hasSecurityImpact
);
break;
}
AppendErrorToVariables(errorRecord);
}
// Add this note property and set its value to true for F&O
// to decide whether to call WriteErrorLine or WriteLine.
// We want errors to print in red in both cases.
PSObject errorWrap = PSObject.AsPSObject(errorRecord);
// It's possible we've already added the member (this method is recursive sometimes
// when tracing), so don't add the member again.
// We don't add a note property on messages that comes from stderr stream.
if (!isNativeError)
{
errorWrap.WriteStream = WriteStreamType.Error;
}
// 2003/11/19-JonN Previously, PSObject instances in ErrorOutputPipe
// wrapped the TargetObject and held the CoreException as a note.
// Now, they wrap the CoreException and hold the TargetObject as a note.
if (ErrorMergeTo != MergeDataStream.None)
{
Dbg.Assert(ErrorMergeTo == MergeDataStream.Output, "Only merging to success output is supported.");
this.OutputPipe.AddWithoutAppendingOutVarList(errorWrap);
}
else
{
// If this is an error pipe for a hosting application and we are logging,
// then create a temporary PowerShell to log the error.
if (Context.InternalHost.UI.IsTranscribing)
{
Context.InternalHost.UI.TranscribeError(Context, errorRecord.InvocationInfo, errorWrap);
}
this.ErrorOutputPipe.AddWithoutAppendingOutVarList(errorWrap);
}
}
#endregion WriteError
#region Preference
// These are a set of preference variables which affect the inner
// workings of the command and when what information will get output.
// See "User Feedback Mechanisms - Note.doc" for details.
private bool _isConfirmPreferenceCached = false;
private ConfirmImpact _confirmPreference = InitialSessionState.DefaultConfirmPreference;
/// <summary>
/// Preference setting controlling behavior of ShouldProcess()
/// </summary>
/// <remarks>
/// This is not an independent parameter, it just emerges from the
/// Verbose, Debug, Confirm, and WhatIf parameters and the
/// $ConfirmPreference shell variable.
///
/// We only read $ConfirmPreference once, then cache the value.
/// </remarks>
internal ConfirmImpact ConfirmPreference
{
get
{
// WhatIf not relevant, it never gets this far in that case
if (Confirm)
return ConfirmImpact.Low;
if (Debug)
{
if (IsConfirmFlagSet) // -Debug -Confirm:$false
return ConfirmImpact.None;
return ConfirmImpact.Low;
}
if (IsConfirmFlagSet) // -Confirm:$false
return ConfirmImpact.None;
if (!_isConfirmPreferenceCached)
{
bool defaultUsed = false;
_confirmPreference = Context.GetEnumPreference(SpecialVariables.ConfirmPreferenceVarPath, _confirmPreference, out defaultUsed);
_isConfirmPreferenceCached = true;
}
return _confirmPreference;
}
}
private bool _isDebugPreferenceSet = false;
private ActionPreference _debugPreference = InitialSessionState.DefaultDebugPreference;
private bool _isDebugPreferenceCached = false;
/// <summary>
/// Preference setting.
/// </summary>
/// <exception cref="System.Management.Automation.ExtendedTypeSystemException">
/// (get-only) An error occurred accessing $DebugPreference.
/// </exception>
internal ActionPreference DebugPreference
{
get
{
if (_isDebugPreferenceSet)
{
return _debugPreference;
}
if (IsDebugFlagSet)
{
return Debug ? ActionPreference.Continue : ActionPreference.SilentlyContinue;
}
if (!_isDebugPreferenceCached)
{
bool defaultUsed = false;
_debugPreference = Context.GetEnumPreference(SpecialVariables.DebugPreferenceVarPath, _debugPreference, out defaultUsed);
// If the host couldn't prompt for the debug action anyways, change it to 'Continue'.
// This lets hosts still see debug output without having to implement the prompting logic.
if ((CBhost.ExternalHost.UI == null) && (_debugPreference == ActionPreference.Inquire))
{
_debugPreference = ActionPreference.Continue;
}
_isDebugPreferenceCached = true;
}
return _debugPreference;
}
set
{
if (value == ActionPreference.Suspend)
{
throw PSTraceSource.NewNotSupportedException(ErrorPackage.ActionPreferenceReservedForFutureUseError, value);
}
_debugPreference = value;
_isDebugPreferenceSet = true;
}
}
private readonly bool _isVerbosePreferenceCached = false;
private ActionPreference _verbosePreference = InitialSessionState.DefaultVerbosePreference;
/// <summary>
/// Preference setting.
/// </summary>
/// <exception cref="System.Management.Automation.ExtendedTypeSystemException">
/// An error occurred accessing $VerbosePreference.
/// </exception>
internal ActionPreference VerbosePreference
{
get
{
if (IsVerboseFlagSet)
{
if (Verbose)
return ActionPreference.Continue;
else
return ActionPreference.SilentlyContinue;
}
if (Debug)
{
// If the host couldn't prompt for the debug action anyways, use 'Continue'.
// This lets hosts still see debug output without having to implement the prompting logic.
if (CBhost.ExternalHost.UI == null)
{
return ActionPreference.Continue;
}
else
{
return ActionPreference.Inquire;
}
}
if (!_isVerbosePreferenceCached)
{
bool defaultUsed = false;
_verbosePreference = Context.GetEnumPreference(
SpecialVariables.VerbosePreferenceVarPath,
_verbosePreference,
out defaultUsed);
}
return _verbosePreference;
}
}
internal bool IsWarningActionSet { get; private set; } = false;
private readonly bool _isWarningPreferenceCached = false;
private ActionPreference _warningPreference = InitialSessionState.DefaultWarningPreference;
/// <summary>
/// Preference setting.
/// </summary>
/// <exception cref="System.Management.Automation.ExtendedTypeSystemException">
/// An error occurred accessing $WarningPreference.
/// </exception>
internal ActionPreference WarningPreference
{
get
{
// Setting CommonParameters.WarningAction has highest priority
if (IsWarningActionSet)
return _warningPreference;
if (Debug)
return ActionPreference.Inquire;
if (Verbose)
return ActionPreference.Continue;
// Debug:$false and Verbose:$false ignored
if (!_isWarningPreferenceCached)
{
bool defaultUsed = false;
_warningPreference = Context.GetEnumPreference(SpecialVariables.WarningPreferenceVarPath, _warningPreference, out defaultUsed);
}
return _warningPreference;
}
set
{
if (value == ActionPreference.Suspend)
{
throw PSTraceSource.NewNotSupportedException(ErrorPackage.ActionPreferenceReservedForFutureUseError, value);
}
_warningPreference = value;
IsWarningActionSet = true;
}
}
// This is used so that people can tell whether the verbose switch
// was specified. This is useful in the Cmdlet-calling-Cmdlet case
// where you'd like the underlying Cmdlet to have the same switches.
private bool _verboseFlag = false;
/// <summary>
/// Echo tells the command to articulate the actions it performs while executing.
/// </summary>
/// <remarks>
/// This is a common parameter via class CommonParameters.
/// </remarks>
internal bool Verbose
{
get
{
return _verboseFlag;
}
set
{
_verboseFlag = value;
IsVerboseFlagSet = true;
}
}
internal bool IsVerboseFlagSet { get; private set; } = false;
private bool _confirmFlag = false;
/// <summary>
/// Confirm tells the command to ask the admin before performing an action.
/// </summary>
/// <remarks>
/// This is a common parameter via class ShouldProcessParameters.
/// </remarks>
internal SwitchParameter Confirm
{
get
{
return _confirmFlag;
}
set
{
_confirmFlag = value;
IsConfirmFlagSet = true;
}
}
internal bool IsConfirmFlagSet { get; private set; } = false;
private bool _useTransactionFlag = false;
/// <summary>
/// UseTransaction tells the command to activate the current PowerShell transaction.
/// </summary>
/// <remarks>
/// This is a common parameter via class TransactionParameters.
/// </remarks>
internal SwitchParameter UseTransaction
{
get
{
return _useTransactionFlag;
}
set
{
_useTransactionFlag = value;
UseTransactionFlagSet = true;
}
}
internal bool UseTransactionFlagSet { get; private set; } = false;
// This is used so that people can tell whether the debug switch was specified. This
// Is useful in the Cmdlet-calling-Cmdlet case where you'd like the underlying Cmdlet to
// have the same switches.
private bool _debugFlag = false;
/// <summary>
/// Debug tell the command system to provide Programmer/Support type messages to understand what is really occuring
/// and give the user the opportunity to stop or debug the situation.
/// </summary>
/// <remarks>
/// This is a common parameter via class CommonParameters.
/// </remarks>
internal bool Debug
{
get
{
return _debugFlag;
}
set
{
_debugFlag = value;
IsDebugFlagSet = true;
}
}
internal bool IsDebugFlagSet { get; private set; } = false;
private bool _whatIfFlag = InitialSessionState.DefaultWhatIfPreference;
private bool _isWhatIfPreferenceCached /* = false */;
/// <summary>
/// WhatIf indicates that the command should not
/// perform any changes to persistent state outside Monad.
/// </summary>
/// <remarks>
/// This is a common parameter via class ShouldProcessParameters.
/// </remarks>
internal SwitchParameter WhatIf
{
get
{
if (!IsWhatIfFlagSet && !_isWhatIfPreferenceCached)
{
bool defaultUsed = false;
_whatIfFlag = Context.GetBooleanPreference(SpecialVariables.WhatIfPreferenceVarPath, _whatIfFlag, out defaultUsed);
_isWhatIfPreferenceCached = true;
}
return _whatIfFlag;
}
set
{
_whatIfFlag = value;
IsWhatIfFlagSet = true;
}
}
internal bool IsWhatIfFlagSet { get; private set; }
private ActionPreference _errorAction = InitialSessionState.DefaultErrorActionPreference;
private bool _isErrorActionPreferenceCached = false;
/// <summary>
/// ErrorAction tells the command what to do when an error occurs.
/// </summary>
/// <exception cref="System.Management.Automation.ExtendedTypeSystemException">
/// (get-only) An error occurred accessing $ErrorAction.
/// </exception>
/// <remarks>
/// This is a common parameter via class CommonParameters.
/// </remarks>
internal ActionPreference ErrorAction
{
get
{
// Setting CommonParameters.ErrorAction has highest priority
if (IsErrorActionSet)
return _errorAction;
if (!_isErrorActionPreferenceCached)
{
bool defaultUsed = false;
_errorAction = Context.GetEnumPreference(SpecialVariables.ErrorActionPreferenceVarPath, _errorAction, out defaultUsed);
_isErrorActionPreferenceCached = true;
}
return _errorAction;
}
set
{
if (value == ActionPreference.Suspend)
{
throw PSTraceSource.NewNotSupportedException(ErrorPackage.ActionPreferenceReservedForFutureUseError, value);
}
_errorAction = value;
IsErrorActionSet = true;
}
}
internal bool IsErrorActionSet { get; private set; } = false;
/// <summary>
/// Preference setting for displaying ProgressRecords when WriteProgress is called.
/// </summary>
/// <value></value>
internal ActionPreference ProgressPreference
{
get
{
if (_isProgressPreferenceSet)
return _progressPreference;
if (!_isProgressPreferenceCached)
{
bool defaultUsed = false;
_progressPreference = Context.GetEnumPreference(SpecialVariables.ProgressPreferenceVarPath, _progressPreference, out defaultUsed);
_isProgressPreferenceCached = true;
}
return _progressPreference;
}
set
{
if (value == ActionPreference.Suspend)
{
throw PSTraceSource.NewNotSupportedException(ErrorPackage.ActionPreferenceReservedForFutureUseError, value);
}
_progressPreference = value;
_isProgressPreferenceSet = true;
}
}
private ActionPreference _progressPreference = InitialSessionState.DefaultProgressPreference;
private bool _isProgressPreferenceSet = false;
private bool _isProgressPreferenceCached = false;
/// <summary>
/// Preference setting for displaying InformationRecords when WriteInformation is called.
/// </summary>
/// <value></value>
internal ActionPreference InformationPreference
{
get
{
if (IsInformationActionSet)
return _informationPreference;
if (!_isInformationPreferenceCached)
{
bool defaultUsed = false;
_informationPreference = Context.GetEnumPreference(SpecialVariables.InformationPreferenceVarPath, _informationPreference, out defaultUsed);
_isInformationPreferenceCached = true;
}
return _informationPreference;
}
set
{
if (value == ActionPreference.Suspend)
{
throw PSTraceSource.NewNotSupportedException(ErrorPackage.ActionPreferenceReservedForFutureUseError, value);
}
_informationPreference = value;
IsInformationActionSet = true;
}
}
private ActionPreference _informationPreference = InitialSessionState.DefaultInformationPreference;
internal bool IsInformationActionSet { get; private set; } = false;
private bool _isInformationPreferenceCached = false;
internal PagingParameters PagingParameters { get; set; }
#endregion Preference
#region Continue/Confirm
#region Helpers
/// <summary>
/// ContinueStatus indicates the last reply from the user
/// whether or not the command should process an object.
/// </summary>
internal enum ContinueStatus
{
Yes,
No,
YesToAll,
NoToAll
}
internal ContinueStatus lastShouldProcessContinueStatus = ContinueStatus.Yes;
internal ContinueStatus lastErrorContinueStatus = ContinueStatus.Yes;
internal ContinueStatus lastDebugContinueStatus = ContinueStatus.Yes;
internal ContinueStatus lastVerboseContinueStatus = ContinueStatus.Yes;
internal ContinueStatus lastWarningContinueStatus = ContinueStatus.Yes;
internal ContinueStatus lastProgressContinueStatus = ContinueStatus.Yes;
internal ContinueStatus lastInformationContinueStatus = ContinueStatus.Yes;
/// <summary>
/// Should the verbose/debug/progress message be printed?
/// </summary>
/// <param name="preference"></param>
/// <param name="lastContinueStatus"></param>
/// <returns></returns>
/// <exception cref="System.Management.Automation.PipelineStoppedException"></exception>
/// <exception cref="System.InvalidOperationException"></exception>
internal bool WriteHelper_ShouldWrite(
ActionPreference preference,
ContinueStatus lastContinueStatus)
{
ThrowIfStopping();
// 2005/05/24 908827
// WriteDebug/WriteVerbose/WriteProgress/WriteWarning should only be callable from the main thread
//
// WriteError/WriteObject have a check that prevents them to be called from outside
// Begin/Process/End. This is done because the Pipeline needs to be ready before these
// functions can be called.
//
// WriteDebug/Warning/Verbose/Process used to do the same check, even though it is not
// strictly needed. If we ever implement pipelines for these objects we may need to
// enforce the check again.
//
// See bug 583774 in the Windows 7 database for more details.
//
ThrowIfWriteNotPermitted(false);
switch (lastContinueStatus)
{
case ContinueStatus.NoToAll: // previously answered NoToAll
return false;
case ContinueStatus.YesToAll: // previously answered YesToAll
return true;
}
switch (preference)
{
case ActionPreference.Ignore:
case ActionPreference.SilentlyContinue:
return false;
case ActionPreference.Continue:
case ActionPreference.Stop:
case ActionPreference.Inquire:
case ActionPreference.Break:
return true;
default:
Dbg.Assert(false, "Bad preference value" + preference);
return true;
}
}
/// <summary>
/// Complete implementation of WriteDebug/WriteVerbose/WriteProgress.
/// </summary>
/// <param name="inquireCaption"></param>
/// <param name="inquireMessage"></param>
/// <param name="preference"></param>
/// <param name="lastContinueStatus"></param>
/// <param name="preferenceVariableName"></param>
/// <param name="message"></param>
/// <returns>Did Inquire return YesToAll?.</returns>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <remarks>
/// If the pipeline is terminated due to ActionPreference.Stop
/// or ActionPreference.Inquire, this method will throw
/// <see cref="System.Management.Automation.PipelineStoppedException"/>,
/// but the command failure will ultimately be
/// <see cref="System.Management.Automation.ActionPreferenceStopException"/>,
/// </remarks>
internal ContinueStatus WriteHelper(
string inquireCaption,
string inquireMessage,
ActionPreference preference,
ContinueStatus lastContinueStatus,
string preferenceVariableName,
string message)
{
switch (lastContinueStatus)
{
case ContinueStatus.NoToAll: // previously answered NoToAll
return ContinueStatus.NoToAll;
case ContinueStatus.YesToAll: // previously answered YesToAll
return ContinueStatus.YesToAll;
}
switch (preference)
{
case ActionPreference.Ignore: // YesToAll
case ActionPreference.SilentlyContinue:
case ActionPreference.Continue:
case ActionPreference.Break:
return ContinueStatus.Yes;
case ActionPreference.Stop:
ActionPreferenceStopException e =
new ActionPreferenceStopException(
MyInvocation,
StringUtil.Format(CommandBaseStrings.ErrorPreferenceStop, preferenceVariableName, message));
throw ManageException(e);
case ActionPreference.Inquire:
break;
default:
Dbg.Assert(false, "Bad preference value" + preference);
ActionPreferenceStopException apse =
new ActionPreferenceStopException(
MyInvocation,
StringUtil.Format(CommandBaseStrings.PreferenceInvalid, preferenceVariableName, preference));
throw ManageException(apse);
}
return InquireHelper(
inquireMessage,
inquireCaption,
true, // allowYesToAll
false, // allowNoToAll
true, // replaceNoWithHalt
false // hasSecurityImpact
);
}
/// <summary>
/// Helper for continue prompt, handles Inquire.
/// </summary>
/// <param name="inquireMessage">May be null.</param>
/// <param name="inquireCaption">May be null.</param>
/// <param name="allowYesToAll"></param>
/// <param name="allowNoToAll"></param>
/// <param name="replaceNoWithHalt"></param>
/// <param name="hasSecurityImpact"></param>
/// <returns>User's selection.</returns>
/// <exception cref="System.Management.Automation.PipelineStoppedException">
/// The pipeline has already been terminated, or was terminated
/// during the execution of this method.
/// The Cmdlet should generally just allow PipelineStoppedException
/// to percolate up to the caller of ProcessRecord etc.
/// </exception>
/// <remarks>
/// If the pipeline is terminated due to ActionPreference.Stop
/// or ActionPreference.Inquire, this method will throw
/// <see cref="System.Management.Automation.PipelineStoppedException"/>,
/// but the command failure will ultimately be
/// <see cref="System.Management.Automation.ActionPreferenceStopException"/>,
/// </remarks>
internal ContinueStatus InquireHelper(
string inquireMessage,
string inquireCaption,
bool allowYesToAll,
bool allowNoToAll,
bool replaceNoWithHalt,
bool hasSecurityImpact
)
{
Collection<ChoiceDescription> choices =
new Collection<ChoiceDescription>();
int currentOption = 0;
int continueOneOption = Int32.MaxValue,
continueAllOption = Int32.MaxValue,
haltOption = Int32.MaxValue,
skipOneOption = Int32.MaxValue,
skipAllOption = Int32.MaxValue,
pauseOption = Int32.MaxValue;
string continueOneLabel = CommandBaseStrings.ContinueOneLabel;
string continueOneHelpMsg = CommandBaseStrings.ContinueOneHelpMessage;
choices.Add(new ChoiceDescription(continueOneLabel, continueOneHelpMsg));
continueOneOption = currentOption++;
if (allowYesToAll)
{
string continueAllLabel = CommandBaseStrings.ContinueAllLabel;
string continueAllHelpMsg = CommandBaseStrings.ContinueAllHelpMessage;
choices.Add(new ChoiceDescription(continueAllLabel, continueAllHelpMsg));
continueAllOption = currentOption++;
}
if (replaceNoWithHalt)
{
string haltLabel = CommandBaseStrings.HaltLabel;
string haltHelpMsg = CommandBaseStrings.HaltHelpMessage;
choices.Add(new ChoiceDescription(haltLabel, haltHelpMsg));
haltOption = currentOption++;
}
else
{
string skipOneLabel = CommandBaseStrings.SkipOneLabel;
string skipOneHelpMsg = CommandBaseStrings.SkipOneHelpMessage;
choices.Add(new ChoiceDescription(skipOneLabel, skipOneHelpMsg));
skipOneOption = currentOption++;
}
if (allowNoToAll)
{
string skipAllLabel = CommandBaseStrings.SkipAllLabel;
string skipAllHelpMsg = CommandBaseStrings.SkipAllHelpMessage;
choices.Add(new ChoiceDescription(skipAllLabel, skipAllHelpMsg));
skipAllOption = currentOption++;
}
// Hide the "Suspend" option in the remoting case since that is not supported. If the user chooses
// Suspend that will produce an error message. Why show the user an option that the user cannot use?
// Related to bug Win7/116823.
if (IsSuspendPromptAllowed())
{
string pauseLabel = CommandBaseStrings.PauseLabel;
string pauseHelpMsg = StringUtil.Format(CommandBaseStrings.PauseHelpMessage, "exit");
choices.Add(new ChoiceDescription(pauseLabel, pauseHelpMsg));
pauseOption = currentOption++;
}
if (string.IsNullOrEmpty(inquireMessage))
{
inquireMessage = CommandBaseStrings.ShouldContinuePromptCaption;
}
if (string.IsNullOrEmpty(inquireCaption))
{
inquireCaption = CommandBaseStrings.InquireCaptionDefault;
}
while (true)
{
// Transcribe the confirmation message
CBhost.InternalUI.TranscribeResult(inquireCaption);
CBhost.InternalUI.TranscribeResult(inquireMessage);
Text.StringBuilder textChoices = new Text.StringBuilder();
foreach (ChoiceDescription choice in choices)
{
if (textChoices.Length > 0)
{
textChoices.Append(" ");
}
textChoices.Append(choice.Label);
}
CBhost.InternalUI.TranscribeResult(textChoices.ToString());
int defaultOption = 0;
if (hasSecurityImpact)
{
defaultOption = skipOneOption;
}
int response = this.CBhost.UI.PromptForChoice(
inquireCaption, inquireMessage, choices, defaultOption);
string chosen = choices[response].Label;
int labelIndex = chosen.IndexOf('&');
if (labelIndex > -1)
{
chosen = chosen[labelIndex + 1].ToString();
}
CBhost.InternalUI.TranscribeResult(chosen);
if (continueOneOption == response)
return ContinueStatus.Yes;
else if (continueAllOption == response)
return ContinueStatus.YesToAll;
else if (haltOption == response)
{
ActionPreferenceStopException e =
new ActionPreferenceStopException(
MyInvocation,
CommandBaseStrings.InquireHalt);
throw ManageException(e);
}
else if (skipOneOption == response)
return ContinueStatus.No;
else if (skipAllOption == response)
return ContinueStatus.NoToAll;
else if (pauseOption == response)
{
// This call returns when the user exits the nested prompt.
CBhost.EnterNestedPrompt(_thisCommand);
// continue loop
}
else if (response == -1)
{
ActionPreferenceStopException e =
new ActionPreferenceStopException(
MyInvocation,
CommandBaseStrings.InquireCtrlC);
throw ManageException(e);
}
else
{
Dbg.Assert(false, "all cases should be checked");
InvalidOperationException e =
PSTraceSource.NewInvalidOperationException();
throw ManageException(e);
}
}
}
/// <summary>
/// Determines if this is being run in the context of a remote host or not.
/// </summary>
private bool IsSuspendPromptAllowed()
{
Dbg.Assert(this.CBhost != null, "Expected this.CBhost != null");
Dbg.Assert(this.CBhost.ExternalHost != null, "Expected this.CBhost.ExternalHost != null");
if (this.CBhost.ExternalHost is ServerRemoteHost)
{
return false;
}
return true;
}
#endregion Helpers
#endregion Continue/Confirm
internal void SetVariableListsInPipe()
{
Diagnostics.Assert(_thisCommand is PSScriptCmdlet, "this is only done for script cmdlets");
if (_outVarList != null)
{
this.OutputPipe.AddVariableList(VariableStreamKind.Output, _outVarList);
}
if (_errorVarList != null)
{
this.OutputPipe.AddVariableList(VariableStreamKind.Error, _errorVarList);
}
if (_warningVarList != null)
{
this.OutputPipe.AddVariableList(VariableStreamKind.Warning, _warningVarList);
}
if (_informationVarList != null)
{
this.OutputPipe.AddVariableList(VariableStreamKind.Information, _informationVarList);
}
if (this.PipelineVariable != null)
{
// _state can be null if the current script block is dynamicparam, etc.
if (_state != null)
{
// Create the pipeline variable
_state.PSVariable.Set(_pipelineVarReference);
// Get the reference again in case we re-used one from the
// same scope.
_pipelineVarReference = _state.PSVariable.Get(this.PipelineVariable);
}
this.OutputPipe.SetPipelineVariable(_pipelineVarReference);
}
}
internal void RemoveVariableListsInPipe()
{
// Diagnostics.Assert(thisCommand is PSScriptCmdlet, "this is only done for script cmdlets");
if (_outVarList != null)
{
this.OutputPipe.RemoveVariableList(VariableStreamKind.Output, _outVarList);
}
if (_errorVarList != null)
{
this.OutputPipe.RemoveVariableList(VariableStreamKind.Error, _errorVarList);
}
if (_warningVarList != null)
{
this.OutputPipe.RemoveVariableList(VariableStreamKind.Warning, _warningVarList);
}
if (_informationVarList != null)
{
this.OutputPipe.RemoveVariableList(VariableStreamKind.Information, _informationVarList);
}
if (this.PipelineVariable != null)
{
this.OutputPipe.RemovePipelineVariable();
// '_state' could be null when a 'DynamicParam' block runs because the 'DynamicParam' block runs in 'DoPrepare',
// before 'PipelineProcessor.SetupParameterVariables' is called, where '_state' is initialized.
_state?.PSVariable.Remove(this.PipelineVariable);
}
}
}
}