/********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. --********************************************************************/ using System.Management.Automation.Language; using System.Management.Automation.Host; using System.Management.Automation.Runspaces; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Security; using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation.Internal.Host { internal partial class InternalHostUserInterface : PSHostUserInterface, IHostUISupportsMultipleChoiceSelection { internal InternalHostUserInterface(PSHostUserInterface externalUI, InternalHost parentHost) { // externalUI may be null _externalUI = externalUI; // parent may not be null, however Dbg.Assert(parentHost != null, "parent may not be null"); if (parentHost == null) { throw PSTraceSource.NewArgumentNullException("parentHost"); } _parent = parentHost; PSHostRawUserInterface rawui = null; if (externalUI != null) { rawui = externalUI.RawUI; } _internalRawUI = new InternalHostRawUserInterface(rawui, _parent); } private void ThrowNotInteractive() { _internalRawUI.ThrowNotInteractive(); } private void ThrowPromptNotInteractive(string promptMessage) { string message = StringUtil.Format(HostInterfaceExceptionsStrings.HostFunctionPromptNotImplemented, promptMessage); HostException e = new HostException( message, null, "HostFunctionNotImplemented", ErrorCategory.NotImplemented); throw e; } /// /// /// See base class /// /// /// /// public override System.Management.Automation.Host.PSHostRawUserInterface RawUI { get { return _internalRawUI; } } public override bool SupportsVirtualTerminal { get { return _externalUI.SupportsVirtualTerminal; } } /// /// /// See base class /// /// /// /// /// if the UI property of the external host is null, possibly because the PSHostUserInterface is not /// implemented by the external host /// /// public override string ReadLine() { if (_externalUI == null) { ThrowNotInteractive(); } string result = null; try { result = _externalUI.ReadLine(); } catch (PipelineStoppedException) { //PipelineStoppedException is thrown by host when it wants //to stop the pipeline. LocalPipeline lpl = (LocalPipeline)((RunspaceBase)_parent.Context.CurrentRunspace).GetCurrentlyRunningPipeline(); if (lpl == null) { throw; } lpl.Stopper.Stop(); } return result; } /// /// /// See base class /// /// /// /// /// if the UI property of the external host is null, possibly because the PSHostUserInterface is not /// implemented by the external host /// /// public override SecureString ReadLineAsSecureString() { if (_externalUI == null) { ThrowNotInteractive(); } SecureString result = null; try { result = _externalUI.ReadLineAsSecureString(); } catch (PipelineStoppedException) { //PipelineStoppedException is thrown by host when it wants //to stop the pipeline. LocalPipeline lpl = (LocalPipeline)((RunspaceBase)_parent.Context.CurrentRunspace).GetCurrentlyRunningPipeline(); if (lpl == null) { throw; } lpl.Stopper.Stop(); } return result; } /// /// /// See base class /// /// /// /// /// /// /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// /// public override void Write(string value) { if (value == null) { return; } if (_externalUI == null) { return; } _externalUI.Write(value); } /// /// /// See base class /// /// /// /// /// /// /// /// /// /// /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// /// public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) { if (value == null) { return; } if (_externalUI == null) { return; } _externalUI.Write(foregroundColor, backgroundColor, value); } /// /// /// See base class /// /// /// /// /// /// /// if the UI property of the external host is null, possibly because the PSHostUserInterface is not /// implemented by the external host /// /// public override void WriteLine() { if (_externalUI == null) { return; } _externalUI.WriteLine(); } /// /// /// See base class /// /// /// /// /// /// /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// /// public override void WriteLine(string value) { if (value == null) { return; } if (_externalUI == null) { return; } _externalUI.WriteLine(value); } public override void WriteErrorLine(string value) { if (value == null) { return; } if (_externalUI == null) { return; } _externalUI.WriteErrorLine(value); } /// /// /// See base class /// /// /// /// /// /// /// /// /// /// /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// /// public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) { if (value == null) { return; } if (_externalUI == null) { return; } _externalUI.WriteLine(foregroundColor, backgroundColor, value); } /// /// /// See base class /// /// /// /// /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// /// public override void WriteDebugLine(string message) { WriteDebugLineHelper(message); } /// /// /// internal void WriteDebugRecord(DebugRecord record) { WriteDebugInfoBuffers(record); if (_externalUI == null) { return; } _externalUI.WriteDebugLine(record.Message); } /// /// Writes the DebugRecord to informational buffers. /// /// DebugRecord internal void WriteDebugInfoBuffers(DebugRecord record) { if (_informationalBuffers != null) { _informationalBuffers.AddDebug(record); } } /// /// Helper function for WriteDebugLine /// /// /// /// /// /// If the debug preference is set to ActionPreference.Stop /// /// /// /// /// If the debug preference is set to ActionPreference.Inquire and user requests to stop execution. /// /// /// /// /// If the debug preference is not a valid ActionPrefernce value. /// /// internal void WriteDebugLine(string message, ref ActionPreference preference) { string errorMsg = null; ErrorRecord errorRecord = null; switch (preference) { case ActionPreference.Continue: WriteDebugLineHelper(message); break; case ActionPreference.SilentlyContinue: case ActionPreference.Ignore: break; case ActionPreference.Inquire: if (!DebugShouldContinue(message, ref preference)) { // user asked to exit with an error errorMsg = InternalHostUserInterfaceStrings.WriteDebugLineStoppedError; errorRecord = new ErrorRecord(new ParentContainsErrorRecordException(errorMsg), "UserStopRequest", ErrorCategory.OperationStopped, null); ActionPreferenceStopException e = new ActionPreferenceStopException(errorRecord); // We cannot call ThrowTerminatingError since this is not a cmdlet or provider throw e; } else { WriteDebugLineHelper(message); } break; case ActionPreference.Stop: WriteDebugLineHelper(message); errorMsg = InternalHostUserInterfaceStrings.WriteDebugLineStoppedError; errorRecord = new ErrorRecord(new ParentContainsErrorRecordException(errorMsg), "ActionPreferenceStop", ErrorCategory.OperationStopped, null); ActionPreferenceStopException ense = new ActionPreferenceStopException(errorRecord); // We cannot call ThrowTerminatingError since this is not a cmdlet or provider throw ense; default: Dbg.Assert(false, "all preferences should be checked"); throw PSTraceSource.NewArgumentException("preference", InternalHostUserInterfaceStrings.UnsupportedPreferenceError, preference); // break; } } /// /// If informationBuffers is not null, the respective messages will also /// be written to the buffers along with external host. /// /// /// Buffers to which Debug, Verbose, Warning, Progress, Information messages /// will be writtern to. /// /// /// This method is not thread safe. Caller should make sure of the /// assosciated risks. /// internal void SetInformationalMessageBuffers(PSInformationalBuffers informationalBuffers) { _informationalBuffers = informationalBuffers; } /// /// Gets the informational message buffers of the host /// /// informational message buffers internal PSInformationalBuffers GetInformationalMessageBuffers() { return _informationalBuffers; } private void WriteDebugLineHelper(string message) { if (message == null) { return; } WriteDebugRecord(new DebugRecord(message)); } /// /// /// Ask the user whether to continue/stop or break to a nested prompt. /// /// /// /// /// Message to display to the user. This routine will append the text "Continue" to ensure that people know what question /// they are answering. /// /// /// /// /// Preference setting which determines the behaviour. This is by-ref and will be modified based upon what the user /// types. (e.g. YesToAll will change Inquire => NotifyContinue) /// /// private bool DebugShouldContinue(string message, ref ActionPreference actionPreference) { Dbg.Assert(actionPreference == ActionPreference.Inquire, "Why are you inquiring if your preference is not to?"); bool shouldContinue = false; Collection choices = new Collection(); choices.Add(new ChoiceDescription(InternalHostUserInterfaceStrings.ShouldContinueYesLabel, InternalHostUserInterfaceStrings.ShouldContinueYesHelp)); choices.Add(new ChoiceDescription(InternalHostUserInterfaceStrings.ShouldContinueYesToAllLabel, InternalHostUserInterfaceStrings.ShouldContinueYesToAllHelp)); choices.Add(new ChoiceDescription(InternalHostUserInterfaceStrings.ShouldContinueNoLabel, InternalHostUserInterfaceStrings.ShouldContinueNoHelp)); choices.Add(new ChoiceDescription(InternalHostUserInterfaceStrings.ShouldContinueNoToAllLabel, InternalHostUserInterfaceStrings.ShouldContinueNoToAllHelp)); choices.Add(new ChoiceDescription(InternalHostUserInterfaceStrings.ShouldContinueSuspendLabel, InternalHostUserInterfaceStrings.ShouldContinueSuspendHelp)); bool endLoop = true; do { endLoop = true; switch ( PromptForChoice( InternalHostUserInterfaceStrings.ShouldContinuePromptMessage, message, choices, 0)) { case 0: shouldContinue = true; break; case 1: actionPreference = ActionPreference.Continue; shouldContinue = true; break; case 2: shouldContinue = false; break; case 3: // No to All means that we want to stop everytime WriteDebug is called. Since No throws an error, I // think that ordinarily, the caller will terminate. So I don't think the caller will ever get back // calling WriteDebug again, and thus "No to All" might not be a useful option to have. actionPreference = ActionPreference.Stop; shouldContinue = false; break; case 4: // This call returns when the user exits the nested prompt. _parent.EnterNestedPrompt(); endLoop = false; break; }//switch } while (endLoop != true); return shouldContinue; } /// /// /// See base class /// /// /// /// /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// /// public override void WriteProgress(Int64 sourceId, ProgressRecord record) { if (record == null) { throw PSTraceSource.NewArgumentNullException("record"); } // Write to Information Buffers if (null != _informationalBuffers) { _informationalBuffers.AddProgress(record); } if (_externalUI == null) { return; } _externalUI.WriteProgress(sourceId, record); } /// /// /// See base class /// /// /// /// /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// /// public override void WriteVerboseLine(string message) { if (message == null) { return; } WriteVerboseRecord(new VerboseRecord(message)); } /// /// /// internal void WriteVerboseRecord(VerboseRecord record) { WriteVerboseInfoBuffers(record); if (_externalUI == null) { return; } _externalUI.WriteVerboseLine(record.Message); } /// /// Writes the VerboseRecord to informational buffers. /// /// VerboseRecord internal void WriteVerboseInfoBuffers(VerboseRecord record) { if (_informationalBuffers != null) { _informationalBuffers.AddVerbose(record); } } /// /// /// See base class /// /// /// /// /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// /// public override void WriteWarningLine(string message) { if (message == null) { return; } WriteWarningRecord(new WarningRecord(message)); } /// /// /// internal void WriteWarningRecord(WarningRecord record) { WriteWarningInfoBuffers(record); if (_externalUI == null) { return; } _externalUI.WriteWarningLine(record.Message); } /// /// Writes the WarningRecord to informational buffers. /// /// WarningRecord internal void WriteWarningInfoBuffers(WarningRecord record) { if (_informationalBuffers != null) { _informationalBuffers.AddWarning(record); } } /// /// /// internal void WriteInformationRecord(InformationRecord record) { WriteInformationInfoBuffers(record); if (_externalUI == null) { return; } _externalUI.WriteInformation(record); } /// /// Writes the InformationRecord to informational buffers. /// /// WarningRecord internal void WriteInformationInfoBuffers(InformationRecord record) { if (_informationalBuffers != null) { _informationalBuffers.AddInformation(record); } } internal static Type GetFieldType(FieldDescription field) { Type result; if (TypeResolver.TryResolveType(field.ParameterAssemblyFullName, out result) || TypeResolver.TryResolveType(field.ParameterTypeFullName, out result)) { return result; } return null; } internal static bool IsSecuritySensitiveType(string typeName) { if (typeName.Equals(typeof(PSCredential).Name, StringComparison.OrdinalIgnoreCase)) { return true; } if (typeName.Equals(typeof(SecureString).Name, StringComparison.OrdinalIgnoreCase)) { return true; } return false; } /// /// /// See base class /// /// /// /// /// /// /// /// /// /// /// If is null. /// /// /// /// /// If .Count is less than 1. /// /// /// /// /// if the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// /// public override Dictionary Prompt(string caption, string message, Collection descriptions) { if (descriptions == null) { throw PSTraceSource.NewArgumentNullException("descriptions"); } if (descriptions.Count < 1) { throw PSTraceSource.NewArgumentException("descriptions", InternalHostUserInterfaceStrings.PromptEmptyDescriptionsError, "descriptions"); } if (_externalUI == null) { ThrowPromptNotInteractive(message); } Dictionary result = null; try { result = _externalUI.Prompt(caption, message, descriptions); } catch (PipelineStoppedException) { //PipelineStoppedException is thrown by host when it wants //to stop the pipeline. LocalPipeline lpl = (LocalPipeline)((RunspaceBase)_parent.Context.CurrentRunspace).GetCurrentlyRunningPipeline(); if (lpl == null) { throw; } lpl.Stopper.Stop(); } return result; } /// /// /// See base class /// /// /// /// /// /// /// /// /// /// if the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// /// public override int PromptForChoice(string caption, string message, Collection choices, int defaultChoice) { if (_externalUI == null) { ThrowPromptNotInteractive(message); } int result = -1; try { result = _externalUI.PromptForChoice(caption, message, choices, defaultChoice); } catch (PipelineStoppedException) { //PipelineStoppedException is thrown by host when it wants //to stop the pipeline. LocalPipeline lpl = (LocalPipeline)((RunspaceBase)_parent.Context.CurrentRunspace).GetCurrentlyRunningPipeline(); if (lpl == null) { throw; } lpl.Stopper.Stop(); } return result; } /// /// Presents a dialog allowing the user to choose options from a set of options. /// /// /// Caption to preceed or title the prompt. E.g. "Parameters for get-foo (instance 1 of 2)" /// /// /// A message that describes what the choice is for. /// /// /// An Collection of ChoiceDescription objects that describe each choice. /// /// /// The index of the labels in the choices collection element to be presented to the user as /// the default choice(s). /// /// /// The indices of the choice elements that corresponds to the options selected. /// /// public Collection PromptForChoice(string caption, string message, Collection choices, IEnumerable defaultChoices) { if (_externalUI == null) { ThrowPromptNotInteractive(message); } IHostUISupportsMultipleChoiceSelection hostForMultipleChoices = _externalUI as IHostUISupportsMultipleChoiceSelection; Collection result = null; try { if (null == hostForMultipleChoices) { // host did not implement this new interface.. // so work with V1 host API to get the behavior.. // this will allow Hosts that were developed with // V1 API to interact with PowerShell V2. result = EmulatePromptForMultipleChoice(caption, message, choices, defaultChoices); } else { result = hostForMultipleChoices.PromptForChoice(caption, message, choices, defaultChoices); } } catch (PipelineStoppedException) { //PipelineStoppedException is thrown by host when it wants //to stop the pipeline. LocalPipeline lpl = (LocalPipeline)((RunspaceBase)_parent.Context.CurrentRunspace).GetCurrentlyRunningPipeline(); if (lpl == null) { throw; } lpl.Stopper.Stop(); } return result; } /// /// This method is added to be backward compatible with V1 hosts w.r.t /// new PromptForChoice method added in PowerShell V2. /// /// /// /// /// /// /// /// 1. Choices is null. /// 2. Choices.Count = 0 /// 3. DefaultChoice is either less than 0 or greater than Choices.Count /// private Collection EmulatePromptForMultipleChoice(string caption, string message, Collection choices, IEnumerable defaultChoices) { Dbg.Assert(null != _externalUI, "externalUI cannot be null."); if (choices == null) { throw PSTraceSource.NewArgumentNullException("choices"); } if (choices.Count == 0) { throw PSTraceSource.NewArgumentException("choices", InternalHostUserInterfaceStrings.EmptyChoicesError, "choices"); } Dictionary defaultChoiceKeys = new Dictionary(); if (null != defaultChoices) { foreach (int defaultChoice in defaultChoices) { if ((defaultChoice < 0) || (defaultChoice >= choices.Count)) { throw PSTraceSource.NewArgumentOutOfRangeException("defaultChoice", defaultChoice, InternalHostUserInterfaceStrings.InvalidDefaultChoiceForMultipleSelection, "defaultChoice", "choices", defaultChoice); } if (!defaultChoiceKeys.ContainsKey(defaultChoice)) { defaultChoiceKeys.Add(defaultChoice, true); } } } // Construct the caption + message + list of choices + default choices Text.StringBuilder choicesMessage = new Text.StringBuilder(); char newLine = '\n'; if (!string.IsNullOrEmpty(caption)) { choicesMessage.Append(caption); choicesMessage.Append(newLine); } if (!string.IsNullOrEmpty(message)) { choicesMessage.Append(message); choicesMessage.Append(newLine); } string[,] hotkeysAndPlainLabels = null; HostUIHelperMethods.BuildHotkeysAndPlainLabels(choices, out hotkeysAndPlainLabels); string choiceTemplate = "[{0}] {1} "; for (int i = 0; i < hotkeysAndPlainLabels.GetLength(1); ++i) { string choice = String.Format( Globalization.CultureInfo.InvariantCulture, choiceTemplate, hotkeysAndPlainLabels[0, i], hotkeysAndPlainLabels[1, i]); choicesMessage.Append(choice); choicesMessage.Append(newLine); } // default choices string defaultPrompt = ""; if (defaultChoiceKeys.Count > 0) { string prepend = ""; Text.StringBuilder defaultChoicesBuilder = new Text.StringBuilder(); foreach (int defaultChoice in defaultChoiceKeys.Keys) { string defaultStr = hotkeysAndPlainLabels[0, defaultChoice]; if (string.IsNullOrEmpty(defaultStr)) { defaultStr = hotkeysAndPlainLabels[1, defaultChoice]; } defaultChoicesBuilder.Append(string.Format(Globalization.CultureInfo.InvariantCulture, "{0}{1}", prepend, defaultStr)); prepend = ","; } string defaultChoicesStr = defaultChoicesBuilder.ToString(); if (defaultChoiceKeys.Count == 1) { defaultPrompt = StringUtil.Format(InternalHostUserInterfaceStrings.DefaultChoice, defaultChoicesStr); } else { defaultPrompt = StringUtil.Format(InternalHostUserInterfaceStrings.DefaultChoicesForMultipleChoices, defaultChoicesStr); } } string messageToBeDisplayed = choicesMessage.ToString() + defaultPrompt + newLine; // read choices from the user Collection result = new Collection(); int choicesSelected = 0; do { string choiceMsg = StringUtil.Format(InternalHostUserInterfaceStrings.ChoiceMessage, choicesSelected); messageToBeDisplayed += choiceMsg; _externalUI.WriteLine(messageToBeDisplayed); string response = _externalUI.ReadLine(); // they just hit enter if (response.Length == 0) { // this may happen when // 1. user wants to go with the defaults // 2. user selected some choices and wanted those // choices to be picked. // user did not pick up any choices..choose the default if ((result.Count == 0) && (defaultChoiceKeys.Keys.Count >= 0)) { // if there's a default, pick that one. foreach (int defaultChoice in defaultChoiceKeys.Keys) { result.Add(defaultChoice); } } // allow for no choice selection. break; } int choicePicked = HostUIHelperMethods.DetermineChoicePicked(response.Trim(), choices, hotkeysAndPlainLabels); if (choicePicked >= 0) { result.Add(choicePicked); choicesSelected++; } // reset messageToBeDisplayed messageToBeDisplayed = ""; } while (true); return result; } private PSHostUserInterface _externalUI = null; private InternalHostRawUserInterface _internalRawUI = null; private InternalHost _parent = null; private PSInformationalBuffers _informationalBuffers = null; } } // namespace